Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
77 changes: 77 additions & 0 deletions src/__tests__/modules/apps/link.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
114 changes: 63 additions & 51 deletions src/modules/apps/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}`)
Expand All @@ -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()
}

Expand Down
Loading