From 5cbf32d925063b4960ce4e8bcb899df836bd200d Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Thu, 13 Nov 2025 18:04:53 -0300 Subject: [PATCH 1/7] Revert the implementation on encoding files for link Buffering the files that started causing issues on links. Adding an early return for cases different than .npmrc to use the previous implementation --- src/modules/apps/link.ts | 46 +++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/modules/apps/link.ts b/src/modules/apps/link.ts index 66c999a37..6524cdda1 100644 --- a/src/modules/apps/link.ts +++ b/src/modules/apps/link.ts @@ -182,29 +182,35 @@ const watchAndSendChanges = async ( } 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), + } + } + 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') + 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') From 3327c3497de37f6cbbbefab29f9e89ea1a28db5a Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Thu, 13 Nov 2025 18:06:31 -0300 Subject: [PATCH 2/7] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5258c2362..ef9945224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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] + +### Fixed +- Stop buffering files used on links + ## [4.3.0] ### Added From b1f7fb93ce84a01dc61c3848083f3b0f97032b65 Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Fri, 14 Nov 2025 11:04:23 -0300 Subject: [PATCH 3/7] Decouple function to make it testable --- src/modules/apps/link.ts | 120 ++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/src/modules/apps/link.ts b/src/modules/apps/link.ts index 6524cdda1..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,62 +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) - - if (!path.endsWith('.npmrc')) { - const content = readFileSync(filePath).toString('base64') - const byteSize = Buffer.byteLength(content) - return { - content, - byteSize, - path: pathModifier(path), - } - } - - 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), - } - } - const sendChanges = debounce(async () => { try { log.info(`Link ID: ${linkID}`) @@ -247,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() } From 3b0cc0a5387fc157b066315053fbdb877b129561 Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Fri, 14 Nov 2025 11:04:42 -0300 Subject: [PATCH 4/7] Add test to ensure file type sent on link --- src/__tests__/modules/apps/link.test.ts | 77 +++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/__tests__/modules/apps/link.test.ts diff --git a/src/__tests__/modules/apps/link.test.ts b/src/__tests__/modules/apps/link.test.ts new file mode 100644 index 000000000..555f5c15b --- /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) + }) +}) \ No newline at end of file From 39d79013af2bb433699d66c92c6e14ee959ea4c9 Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Fri, 14 Nov 2025 11:12:53 -0300 Subject: [PATCH 5/7] Add instructions to create and use the binaries locally --- docs/contributing.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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. From 5a91ae7526bf4e8ff1d8cccc62f8a19aa5661ac5 Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Fri, 14 Nov 2025 11:17:25 -0300 Subject: [PATCH 6/7] Run prettier --- src/__tests__/modules/apps/link.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/modules/apps/link.test.ts b/src/__tests__/modules/apps/link.test.ts index 555f5c15b..21acee1d5 100644 --- a/src/__tests__/modules/apps/link.test.ts +++ b/src/__tests__/modules/apps/link.test.ts @@ -74,4 +74,4 @@ describe('pathToChange', () => { expect(decoded).toBe('registry=${MISSING_VAR}') expect(log.warn).toHaveBeenCalledTimes(1) }) -}) \ No newline at end of file +}) From 1879dccf6f84d2eab76a0fa5ef6d81386ea5e25e Mon Sep 17 00:00:00 2001 From: Filadelfo Date: Fri, 14 Nov 2025 11:41:14 -0300 Subject: [PATCH 7/7] Release version 4.3.1 --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9945224..33c69e6ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.3.1] + ### Fixed + - Stop buffering files used on links ## [4.3.0] 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",