fix(js): resolve asset paths relative to workspace root instead of cwd (#31664)
## Description This PR fixes an issue where asset files copied during a build using the `@nx/js:tsc` executor are placed in the wrong directory depending on the current working directory from which the `nx` command is executed. This behavior becomes particularly problematic in scenarios like release workflows that rely on `preVersionCommand` to run E2E tests. For instance, when using tools like Jest from the root of an E2E project, scripts like `start-local-registry` may trigger a build and run the `preVersionCommand`. However, instead of placing assets in the expected `dist` folder of the project, they are incorrectly copied relative to the E2E folder’s location. ## Reproduction Steps 1. Create a new Nx workspace: ```bash npx --yes create-nx-workspace assets-issue --preset=ts --no-interactive cd assets-issue ``` 2. Add the Nx Plugin package: ```bash nx add @nx/plugin ``` 3. Generate a new plugin: ```bash nx g @nx/plugin:plugin packages/my-plugin --linter eslint --unitTestRunner jest ``` 4. Add a generator to the plugin: ```bash nx g @nx/plugin:generator packages/my-plugin/src/generators/my-generator ``` 5. Build the plugin from the workspace root: ```bash nx build my-plugin ``` ✅ Assets are copied correctly: ``` dist/packages/my-plugin/generators/files/src/index.ts.template dist/packages/my-plugin/generators/schema.json dist/packages/my-plugin/generators/schema.d.ts ``` 6. Now build the same project from a nested folder: ```bash mkdir e2e && cd e2e nx build my-plugin --skip-nx-cache ``` ❌ Assets are copied relative to the current folder: ``` e2e/packages/my-plugin/dist/generators/files/src/index.ts.template e2e/packages/my-plugin/dist/generators/schema.json e2e/packages/my-plugin/dist/generators/schema.d.ts ``` ## Expected Behavior The build output—especially copied assets—should always respect the project’s `outputPath` configuration regardless of where the `nx` command is invoked from. The behavior should be consistent and **not influenced by `process.cwd()`**.
This commit is contained in:
parent
57e70d0e91
commit
fd31fa633d
@ -66,8 +66,12 @@ describe('AssetInputOutputHandler', () => {
|
|||||||
let projectDir: string;
|
let projectDir: string;
|
||||||
let outputDir: string;
|
let outputDir: string;
|
||||||
let callback: jest.SpyInstance;
|
let callback: jest.SpyInstance;
|
||||||
|
let originalCwd: string;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// Store original cwd to restore later
|
||||||
|
originalCwd = process.cwd();
|
||||||
|
|
||||||
// Resolve to real paths to avoid symlink discrepancies with watcher.
|
// Resolve to real paths to avoid symlink discrepancies with watcher.
|
||||||
const tmp = fs.realpathSync(path.join(os.tmpdir()));
|
const tmp = fs.realpathSync(path.join(os.tmpdir()));
|
||||||
|
|
||||||
@ -102,6 +106,11 @@ describe('AssetInputOutputHandler', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original cwd
|
||||||
|
process.chdir(originalCwd);
|
||||||
|
});
|
||||||
|
|
||||||
test('watchAndProcessOnAssetChange', async () => {
|
test('watchAndProcessOnAssetChange', async () => {
|
||||||
const dispose = await sut.watchAndProcessOnAssetChange();
|
const dispose = await sut.watchAndProcessOnAssetChange();
|
||||||
|
|
||||||
@ -219,6 +228,61 @@ describe('AssetInputOutputHandler', () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should copy assets to correct location when running from nested directory', async () => {
|
||||||
|
// Create a nested directory structure to simulate running from a subdirectory
|
||||||
|
const nestedDir = path.join(rootDir, 'e2e', 'integration-tests');
|
||||||
|
fs.mkdirSync(nestedDir, { recursive: true });
|
||||||
|
|
||||||
|
// Change to nested directory to simulate running nx command from there
|
||||||
|
process.chdir(nestedDir);
|
||||||
|
|
||||||
|
// Create test files
|
||||||
|
fs.writeFileSync(path.join(rootDir, 'LICENSE'), 'license');
|
||||||
|
fs.writeFileSync(path.join(projectDir, 'README.md'), 'readme');
|
||||||
|
fs.writeFileSync(path.join(projectDir, 'docs/test1.md'), 'test');
|
||||||
|
|
||||||
|
// Create CopyAssetsHandler with relative outputDir (this is where the bug manifests)
|
||||||
|
const nestedSut = new CopyAssetsHandler({
|
||||||
|
rootDir,
|
||||||
|
projectDir,
|
||||||
|
outputDir: 'dist/mylib', // relative path - this triggers the bug
|
||||||
|
callback: callback as any,
|
||||||
|
assets: [
|
||||||
|
'mylib/*.md',
|
||||||
|
{
|
||||||
|
input: 'mylib/docs',
|
||||||
|
glob: '**/*.md',
|
||||||
|
output: 'docs',
|
||||||
|
},
|
||||||
|
'LICENSE',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await nestedSut.processAllAssetsOnce();
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
type: 'create',
|
||||||
|
src: path.join(rootDir, 'LICENSE'),
|
||||||
|
dest: path.join(rootDir, 'dist/mylib/LICENSE'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(callback).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
type: 'create',
|
||||||
|
src: path.join(rootDir, 'mylib/README.md'),
|
||||||
|
dest: path.join(rootDir, 'dist/mylib/README.md'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(callback).toHaveBeenCalledWith([
|
||||||
|
{
|
||||||
|
type: 'create',
|
||||||
|
src: path.join(rootDir, 'mylib/docs/test1.md'),
|
||||||
|
dest: path.join(rootDir, 'dist/mylib/docs/test1.md'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function wait(ms: number) {
|
function wait(ms: number) {
|
||||||
|
|||||||
@ -89,16 +89,21 @@ export class CopyAssetsHandler {
|
|||||||
let input: string;
|
let input: string;
|
||||||
let output: string;
|
let output: string;
|
||||||
let ignore: string[] | null = null;
|
let ignore: string[] | null = null;
|
||||||
|
|
||||||
|
const resolvedOutputDir = path.isAbsolute(opts.outputDir)
|
||||||
|
? opts.outputDir
|
||||||
|
: path.resolve(opts.rootDir, opts.outputDir);
|
||||||
|
|
||||||
if (typeof f === 'string') {
|
if (typeof f === 'string') {
|
||||||
pattern = f;
|
pattern = f;
|
||||||
input = path.relative(opts.rootDir, opts.projectDir);
|
input = path.relative(opts.rootDir, opts.projectDir);
|
||||||
output = path.relative(opts.rootDir, opts.outputDir);
|
output = path.relative(opts.rootDir, resolvedOutputDir);
|
||||||
} else {
|
} else {
|
||||||
isGlob = true;
|
isGlob = true;
|
||||||
pattern = pathPosix.join(f.input, f.glob);
|
pattern = pathPosix.join(f.input, f.glob);
|
||||||
input = f.input;
|
input = f.input;
|
||||||
output = pathPosix.join(
|
output = pathPosix.join(
|
||||||
path.relative(opts.rootDir, opts.outputDir),
|
path.relative(opts.rootDir, resolvedOutputDir),
|
||||||
f.output
|
f.output
|
||||||
);
|
);
|
||||||
if (f.ignore)
|
if (f.ignore)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user