diff --git a/CHANGELOG.md b/CHANGELOG.md index 5258c2362..33c69e6ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +## [4.3.1] + +### Fixed + +- Stop buffering files used on links + ## [4.3.0] ### Added diff --git a/docs/contributing.md b/docs/contributing.md index c8e74df24..62809e5d6 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -15,6 +15,25 @@ To be able to use it you'll have to add `$HOME/.vtex/dev/bin` to your `PATH` env That's it! Now you're able to run `yarn watch` and, on another terminal session, use the command `vtex-test` as an alias to what you're developing. +### Building the binaries locally and testing them +You can build the binaries and test it locally. You can run the commands: + +``` +yarn build && yarn oclif-dev pack +``` + +A folder named `dist` will be created with all the binaries for different OS. The command below enable to use the binaries for MacOS: + +``` +tar -xzf /Users/fila/Code/vtex/toolbelt/dist/vtex-v4.3.0/vtex-v4.3.0-darwin-x64.tar.gz -C ~/.local/vtex # or copy it to any other folder you want +``` + +Now, you can use the binaries in a VTEX IO App as: + +``` +~/.local/vtex/vtex/bin/vtex --version # or any other command +``` + ### Adding commands The VTEX CLI uses [Ocliff](https://oclif.io/) under the hood, making it very easy to add or improve commands. Follow [this guide](https://oclif.io/docs/commands) to learn how to develop on this project. diff --git a/package.json b/package.json index be119e027..e23e09f18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vtex", - "version": "4.3.0", + "version": "4.3.1", "description": "The platform for e-commerce apps", "bin": "bin/run", "main": "lib/api/index.js", diff --git a/src/__tests__/modules/apps/link.test.ts b/src/__tests__/modules/apps/link.test.ts new file mode 100644 index 000000000..21acee1d5 --- /dev/null +++ b/src/__tests__/modules/apps/link.test.ts @@ -0,0 +1,77 @@ +import fs from 'fs' +import os from 'os' +import path from 'path' + +// Adjust this import to the new location you exported pathToChange from. +import { pathToChange } from '../../../modules/apps/link' // or '../pathToChange' + +jest.mock('../../../api/logger', () => ({ + __esModule: true, + default: { + warn: jest.fn(), + info: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + }, +})) +import log from '../../../api/logger' + +const mockYarnFilesManager = { + maybeMapLocalYarnLinkedPathToProjectPath: (p: string) => p, + symlinkedDepsDirs: [], +} as any + +describe('pathToChange', () => { + let root: string + + beforeEach(() => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'ptc-')) + ;(log.warn as jest.Mock).mockClear() + }) + + afterEach(() => { + delete process.env.TEST_REGISTRY + fs.rmdir(root, () => {}) + }) + + test('regular file: returns base64, byteSize matches, path normalized', () => { + const rel = path.join('a', 'b', 'file.jsonc') + fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true }) + const contentPlain = 'hello world' + fs.writeFileSync(path.join(root, rel), contentPlain) + + const change = pathToChange(rel, root, mockYarnFilesManager) + expect(change.path).toBe('a/b/file.jsonc') + const decoded = Buffer.from(change.content as string, 'base64').toString() + expect(decoded).toBe(contentPlain) + expect(change.byteSize).toBe(Buffer.byteLength(change.content as string)) + }) + + test('remove flag: returns null content and zero size', () => { + const rel = 'gone.jsonc' + fs.writeFileSync(path.join(root, rel), 'x') + const change = pathToChange(rel, root, mockYarnFilesManager, true) + expect(change.content).toBeNull() + expect(change.byteSize).toBe(0) + expect(change.path).toBe('gone.jsonc') + }) + + test('.npmrc expands existing env vars', () => { + process.env.TEST_REGISTRY = 'https://registry.example.com' + const rel = '.npmrc' + fs.writeFileSync(path.join(root, rel), 'registry=${TEST_REGISTRY}') + const change = pathToChange(rel, root, mockYarnFilesManager) + const decoded = Buffer.from(change.content as string, 'base64').toString() + expect(decoded).toBe('registry=https://registry.example.com') + expect(log.warn).not.toHaveBeenCalled() + }) + + test('.npmrc fallback when env var missing logs warning and keeps original', () => { + const rel = '.npmrc' + fs.writeFileSync(path.join(root, rel), 'registry=${MISSING_VAR}') + const change = pathToChange(rel, root, mockYarnFilesManager) + const decoded = Buffer.from(change.content as string, 'base64').toString() + expect(decoded).toBe('registry=${MISSING_VAR}') + expect(log.warn).toHaveBeenCalledTimes(1) + }) +}) diff --git a/src/modules/apps/link.ts b/src/modules/apps/link.ts index 66c999a37..d0fe2dcea 100644 --- a/src/modules/apps/link.ts +++ b/src/modules/apps/link.ts @@ -147,6 +147,68 @@ const warnAndLinkFromStart = ( return null } +const pathModifier = pipe( + (path: string, root: string, yarnFilesManager: YarnFilesManager) => + yarnFilesManager.maybeMapLocalYarnLinkedPathToProjectPath(path, root), + path => path.split(sep).join('/') +) + +export const pathToChange = ( + path: string, + root: string, + yarnFilesManager: YarnFilesManager, + remove?: boolean +): ChangeToSend => { + if (remove) { + return { + content: null, + byteSize: 0, + path: pathModifier(path, root, yarnFilesManager), + } + } + + const filePath = resolvePath(root, path) + + if (!path.endsWith('.npmrc')) { + const content = readFileSync(filePath).toString('base64') + const byteSize = Buffer.byteLength(content) + return { + content, + byteSize, + path: pathModifier(path, root, yarnFilesManager), + } + } + + let fileContent: string + + // Handle .npmrc files with environment variable expansion + try { + const originalContent = readFileSync(filePath, 'utf8') + // Expand environment variables in .npmrc content + const expandedContent = originalContent.replace(/\$\{([^}]+)\}/g, (_, envVar) => { + const value = process.env[envVar] + if (value === undefined) { + throw new Error(`Environment variable ${envVar} is not defined`) + } + return value + }) + fileContent = expandedContent + } catch (error) { + // If environment variable expansion fails, fall back to original file + log.warn(`Warning: Failed to expand environment variables in ${path}: ${error.message}`) + fileContent = readFileSync(filePath, 'utf8') + } + + const content = Buffer.from(fileContent, 'utf8').toString('base64') + const byteSize = Buffer.byteLength(content) + + return { + content, + byteSize, + path: pathModifier(path, root, yarnFilesManager), + } +} + const watchAndSendChanges = async ( root: string, appId: string, @@ -167,56 +229,6 @@ const watchAndSendChanges = async ( const defaultPatterns = ['*/**', 'manifest.json', 'policies.json', '.npmrc', 'cypress.json'] const linkedDepsPatterns = map(path => join(path, '**'), yarnFilesManager.symlinkedDepsDirs) - const pathModifier = pipe( - (path: string) => yarnFilesManager.maybeMapLocalYarnLinkedPathToProjectPath(path, root), - path => path.split(sep).join('/') - ) - - const pathToChange = (path: string, remove?: boolean): ChangeToSend => { - if (remove) { - return { - content: null, - byteSize: 0, - path: pathModifier(path), - } - } - - const filePath = resolvePath(root, path) - let fileContent: string - - // Handle .npmrc files with environment variable expansion - if (path.endsWith('.npmrc')) { - try { - const originalContent = readFileSync(filePath, 'utf8') - // Expand environment variables in .npmrc content - const expandedContent = originalContent.replace(/\$\{([^}]+)\}/g, (_, envVar) => { - const value = process.env[envVar] - if (value === undefined) { - throw new Error(`Environment variable ${envVar} is not defined`) - } - return value - }) - fileContent = expandedContent - } catch (error) { - // If environment variable expansion fails, fall back to original file - log.warn(`Warning: Failed to expand environment variables in ${path}: ${error.message}`) - fileContent = readFileSync(filePath, 'utf8') - } - } else { - // For all other files, read as binary and convert to base64 - fileContent = readFileSync(filePath).toString('base64') - } - - const content = Buffer.from(fileContent, 'utf8').toString('base64') - const byteSize = Buffer.byteLength(content) - - return { - content, - byteSize, - path: pathModifier(path), - } - } - const sendChanges = debounce(async () => { try { log.info(`Link ID: ${linkID}`) @@ -241,7 +253,7 @@ const watchAndSendChanges = async ( const queueChange = (path: string, remove?: boolean) => { console.log(`${chalk.gray(moment().format('HH:mm:ss:SSS'))} - ${remove ? DELETE_SIGN : UPDATE_SIGN} ${path}`) - changeQueue.push(pathToChange(path, remove)) + changeQueue.push(pathToChange(path, root, yarnFilesManager, remove)) sendChanges() }