Compare commits

...

24 Commits

Author SHA1 Message Date
FrozenPandaz 739c427351 chore(misc): publish 15.8.3 2023-03-02 20:10:17 -05:00
Miroslav Jonaš f342baca5a fix(core): plugin should not override existing external nodes (#15219)
(cherry picked from commit 9a014c792c)
2023-03-02 17:53:46 -05:00
Miloš Lajtman fd71c6e288 fix(core): use globbing also for workspace file patterns without '*' (#11298)
(cherry picked from commit a43a99bea5)
2023-03-02 17:53:42 -05:00
Craigory Coppola d30413311b fix(nx-plugin): e2e utils should work (#15397)
(cherry picked from commit a4a9c27637)
2023-03-02 17:04:27 -05:00
Caleb Ukle 0761e4c7a9 fix(web): update snapshots from vite tsconfig changes (#15398)
(cherry picked from commit 76fcf7a319)
2023-03-02 17:04:19 -05:00
Caleb Ukle d15733d00e fix(vite): add missing types for tsconfigs (#15260)
(cherry picked from commit 2e449da177)
2023-03-02 17:04:16 -05:00
Craigory Coppola eba45a9f5b fix(core): standardize chunking elements passing to cli (#15322)
(cherry picked from commit bf08714ad5)
2023-03-02 17:04:13 -05:00
Ruben Casas 68d9df74f6 fix(react): fixes library type not passed to the module federation conf (#15385)
(cherry picked from commit c05425104e)
2023-03-02 17:04:02 -05:00
Jason Jean 1b4ac4b63e fix(devkit): allow ensured packages to resolve from workspace node_mo… (#15386)
(cherry picked from commit 66f498ac8a)
2023-03-02 17:04:00 -05:00
Craigory Coppola 2959f7f033 fix(core): last project should be visible in nx show projects (#15335)
(cherry picked from commit 0aff9dcd5b)
2023-03-02 17:03:57 -05:00
FrozenPandaz f997744b4d chore(misc): publish 15.8.2 2023-03-02 11:51:39 -05:00
Jack Hsu 04a38649b3 fix(core): mark package.json dependencies as static (#15381)
(cherry picked from commit aaed63ba18)
2023-03-02 10:21:35 -05:00
Jason Jean c351b34f62 fix(js): fix parsing link dependencies in yarn (#15364)
(cherry picked from commit 65aac8ea17)
2023-03-02 10:21:33 -05:00
Jason Jean f111580f28 fix(core): handle file renames properly for affected (#15340)
(cherry picked from commit 7aac67c783)
2023-03-02 10:21:28 -05:00
Craigory Coppola bf9844ec72 fix(core): encapsulated nx repos should invoke wrapper when invoked via npx or global install (#15337)
(cherry picked from commit c9244d2536)
2023-03-02 10:21:26 -05:00
Miroslav Jonaš 6c86cb770c fix(linter): ensure ast file detection supports tsx files (#15345)
(cherry picked from commit e767fa4e2a)
2023-03-02 10:21:20 -05:00
Jack Hsu c06edd9426 fix(bundling): use a range rather than ^ for vitest peer dependency … (#15362)
(cherry picked from commit b7ef261182)
2023-03-02 10:21:17 -05:00
Benjamin Cabanes 1c7406379b chore(core): fix browserslist typo (#15309)
(cherry picked from commit 872dcc8547)
2023-03-02 10:21:07 -05:00
Katerina Skroumpelou f1de248ff7 fix(vite): root for running the builder (#15359)
(cherry picked from commit 3d4e542823)
2023-03-02 10:20:55 -05:00
Chau Tran 5ba8d5aa12 fix(js): handle swcrc path mappings if specified (#15328)
(cherry picked from commit c981ac2e0d)
2023-03-02 10:20:52 -05:00
Nicholas Cunningham 18ff2dbcd4 fix(node): lint generator aware that app is root (#15355)
(cherry picked from commit b40c9fa88f)
2023-03-02 10:20:44 -05:00
Jonathan Cammisuli e9a59d60af fix(core): inline source maps for local nx plugins (#15336)
(cherry picked from commit b87f838e9a)
2023-03-02 10:20:39 -05:00
Caleb Ukle 908cf41d74 fix(testing): only install dependency for the desired --testEnvironment (#15244)
(cherry picked from commit 2d2804b0c1)
2023-03-02 10:20:27 -05:00
Caleb Ukle 2f8ca66797 fix(testing): walk all project deps to check if CT project is used in buildTarget (#15252)
(cherry picked from commit e8f19d8cb0)
2023-03-02 10:20:24 -05:00
61 changed files with 764 additions and 318 deletions
@@ -21,6 +21,13 @@
"description": "Do not add dependencies to `package.json`.",
"x-priority": "internal"
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node", "none"],
"description": "The test environment for jest. This controls which jest-environment-* package is installed",
"default": "jsdom",
"x-priority": "important"
},
"js": {
"type": "boolean",
"default": false,
@@ -41,7 +41,7 @@
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"enum": ["jsdom", "node", "none"],
"description": "The test environment for jest.",
"default": "jsdom",
"x-priority": "important"
+1 -1
View File
@@ -42,7 +42,7 @@ Sometimes broad configurations like `> 0.5%, not IE 11` can lead to surprising r
To see what browsers your configuration is supporting, run `npx browserslist` in the application's directory to get an output of browsers and versions to support.
```{% command="npx browserlist" %}
```{% command="npx browserslist" %}
and_chr 61
chrome 83
edge 83
@@ -8,6 +8,7 @@ import {
uniq,
updateFile,
updateProjectConfig,
removeFile,
} from '../../utils';
import { names } from '@nrwl/devkit';
@@ -19,154 +20,15 @@ describe('Angular Cypress Component Tests', () => {
beforeAll(async () => {
projectName = newProject({ name: uniq('cy-ng') });
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/angular:component fancy-component --project=${appName} --no-interactive`
);
runCLI(`generate @nrwl/angular:lib ${usedInAppLibName} --no-interactive`);
runCLI(
`generate @nrwl/angular:component btn --project=${usedInAppLibName} --inlineTemplate --inlineStyle --export --no-interactive`
);
runCLI(
`generate @nrwl/angular:component btn-standalone --project=${usedInAppLibName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
);
updateFile(
`libs/${usedInAppLibName}/src/lib/btn/btn.component.ts`,
`
import { Component, Input } from '@angular/core';
@Component({
selector: '${projectName}-btn',
template: '<button class="text-green-500">{{text}}</button>',
styles: []
})
export class BtnComponent {
@Input() text = 'something';
}
`
);
updateFile(
`libs/${usedInAppLibName}/src/lib/btn-standalone/btn-standalone.component.ts`,
`
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: '${projectName}-btn-standalone',
standalone: true,
imports: [CommonModule],
template: '<button class="text-green-500">standlone-{{text}}</button>',
styles: [],
})
export class BtnStandaloneComponent {
@Input() text = 'something';
}
`
);
// use lib in the app
createFile(
`apps/${appName}/src/app/app.component.html`,
`
<${projectName}-btn></${projectName}-btn>
<${projectName}-btn-standalone></${projectName}-btn-standalone>
<${projectName}-nx-welcome></${projectName}-nx-welcome>
`
);
const btnModuleName = names(usedInAppLibName).className;
updateFile(
`apps/${appName}/src/app/app.component.scss`,
`
@use 'styleguide' as *;
createApp(appName);
h1 {
@include headline;
}`
);
updateFile(
`apps/${appName}/src/app/app.module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {${btnModuleName}Module} from "@${projectName}/${usedInAppLibName}";
createLib(projectName, appName, usedInAppLibName);
useLibInApp(projectName, appName, usedInAppLibName);
import { AppComponent } from './app.component';
import { NxWelcomeComponent } from './nx-welcome.component';
createBuildableLib(projectName, buildableLibName);
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [BrowserModule, ${btnModuleName}Module],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
`
);
runCLI(
`generate @nrwl/angular:lib ${buildableLibName} --buildable --no-interactive`
);
runCLI(
`generate @nrwl/angular:component input --project=${buildableLibName} --inlineTemplate --inlineStyle --export --no-interactive`
);
runCLI(
`generate @nrwl/angular:component input-standalone --project=${buildableLibName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
);
updateFile(
`libs/${buildableLibName}/src/lib/input/input.component.ts`,
`
import {Component, Input} from '@angular/core';
@Component({
selector: '${projectName}-input',
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
styles : []
})
export class InputComponent{
@Input() readOnly = false;
}
`
);
updateFile(
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.ts`,
`
import {Component, Input} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: '${projectName}-input-standalone',
standalone: true,
imports: [CommonModule],
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
styles : []
})
export class InputStandaloneComponent{
@Input() readOnly = false;
}
`
);
// make sure assets from the workspace root work.
createFile('libs/assets/data.json', JSON.stringify({ data: 'data' }));
createFile(
'assets/styles/styleguide.scss',
`
@mixin headline {
font-weight: bold;
color: darkkhaki;
background: lightcoral;
font-weight: 24px;
}
`
);
updateProjectConfig(appName, (config) => {
config.targets['build'].options.stylePreprocessorOptions = {
includePaths: ['assets/styles'],
};
config.targets['build'].options.assets.push({
glob: '**/*',
input: 'libs/assets',
output: 'assets',
});
return config;
});
useWorkspaceAssetsInApp(appName);
});
afterAll(() => cleanupProject());
@@ -200,9 +62,226 @@ import {CommonModule} from '@angular/common';
`generate @nrwl/angular:cypress-component-configuration --project=${buildableLibName} --generate-tests --no-interactive`
);
}).toThrow();
createFile(
updateTestToAssertTailwindIsNotApplied(buildableLibName);
runCLI(
`generate @nrwl/angular:cypress-component-configuration --project=${buildableLibName} --generate-tests --build-target=${appName}:build --no-interactive`
);
if (runCypressTests()) {
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
'All specs passed!'
);
}
// add tailwind
runCLI(
`generate @nrwl/angular:setup-tailwind --project=${buildableLibName}`
);
updateFile(
`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`,
`
(content) => {
// text-green-500 should now apply
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
}
);
updateFile(
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
(content) => {
// text-green-500 should now apply
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
}
);
if (runCypressTests()) {
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
'All specs passed!'
);
checkFilesDoNotExist(`tmp/libs/${buildableLibName}/ct-styles.css`);
}
}, 300_000);
it('should test lib with implicit dep on buildTarget', () => {
// creates graph like buildableLib -> lib -> app
// updates the apps styles and they should apply to the buildableLib
// even though app is not directly connected to buildableLib
useBuildableLibInLib(projectName, buildableLibName, usedInAppLibName);
updateBuilableLibTestsToAssertAppStyles(appName, buildableLibName);
if (runCypressTests()) {
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
'All specs passed!'
);
}
});
});
function createApp(appName: string) {
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
runCLI(
`generate @nrwl/angular:component fancy-component --project=${appName} --no-interactive`
);
}
function createLib(projectName: string, appName: string, libName: string) {
runCLI(`generate @nrwl/angular:lib ${libName} --no-interactive`);
runCLI(
`generate @nrwl/angular:component btn --project=${libName} --inlineTemplate --inlineStyle --export --no-interactive`
);
runCLI(
`generate @nrwl/angular:component btn-standalone --project=${libName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
);
updateFile(
`libs/${libName}/src/lib/btn/btn.component.ts`,
`
import { Component, Input } from '@angular/core';
@Component({
selector: '${projectName}-btn',
template: '<button class="text-green-500">{{text}}</button>',
styles: []
})
export class BtnComponent {
@Input() text = 'something';
}
`
);
updateFile(
`libs/${libName}/src/lib/btn-standalone/btn-standalone.component.ts`,
`
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: '${projectName}-btn-standalone',
standalone: true,
imports: [CommonModule],
template: '<button class="text-green-500">standlone-{{text}}</button>',
styles: [],
})
export class BtnStandaloneComponent {
@Input() text = 'something';
}
`
);
}
function createBuildableLib(projectName: string, libName: string) {
// create lib
runCLI(`generate @nrwl/angular:lib ${libName} --buildable --no-interactive`);
// create cmp for lib
runCLI(
`generate @nrwl/angular:component input --project=${libName} --inlineTemplate --inlineStyle --export --no-interactive`
);
// create standlone cmp for lib
runCLI(
`generate @nrwl/angular:component input-standalone --project=${libName} --inlineTemplate --inlineStyle --export --standalone --no-interactive`
);
// update cmp implmentation to use tailwind clasasserting in tests
updateFile(
`libs/${libName}/src/lib/input/input.component.ts`,
`
import {Component, Input} from '@angular/core';
@Component({
selector: '${projectName}-input',
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
styles : []
})
export class InputComponent{
@Input() readOnly = false;
}
`
);
updateFile(
`libs/${libName}/src/lib/input-standalone/input-standalone.component.ts`,
`
import {Component, Input} from '@angular/core';
import {CommonModule} from '@angular/common';
@Component({
selector: '${projectName}-input-standalone',
standalone: true,
imports: [CommonModule],
template: \`<label class="text-green-500">Email: <input class="border-blue-500" type="email" [readOnly]="readOnly"></label>\`,
styles : []
})
export class InputStandaloneComponent{
@Input() readOnly = false;
}
`
);
}
function useLibInApp(projectName: string, appName: string, libName: string) {
createFile(
`apps/${appName}/src/app/app.component.html`,
`
<${projectName}-btn></${projectName}-btn>
<${projectName}-btn-standalone></${projectName}-btn-standalone>
<${projectName}-nx-welcome></${projectName}-nx-welcome>
`
);
const btnModuleName = names(libName).className;
updateFile(
`apps/${appName}/src/app/app.component.scss`,
`
@use 'styleguide' as *;
h1 {
@include headline;
}`
);
updateFile(
`apps/${appName}/src/app/app.module.ts`,
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import {${btnModuleName}Module} from "@${projectName}/${libName}";
import { AppComponent } from './app.component';
import { NxWelcomeComponent } from './nx-welcome.component';
@NgModule({
declarations: [AppComponent, NxWelcomeComponent],
imports: [BrowserModule, ${btnModuleName}Module],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
`
);
}
function useWorkspaceAssetsInApp(appName: string) {
// make sure assets from the workspace root work.
createFile('libs/assets/data.json', JSON.stringify({ data: 'data' }));
createFile(
'assets/styles/styleguide.scss',
`
@mixin headline {
font-weight: bold;
color: darkkhaki;
background: lightcoral;
font-weight: 24px;
}
`
);
updateProjectConfig(appName, (config) => {
config.targets['build'].options.stylePreprocessorOptions = {
includePaths: ['assets/styles'],
};
config.targets['build'].options.assets.push({
glob: '**/*',
input: 'libs/assets',
output: 'assets',
});
return config;
});
}
function updateTestToAssertTailwindIsNotApplied(libName: string) {
createFile(
`libs/${libName}/src/lib/input/input.component.cy.ts`,
`
import { MountConfig } from 'cypress/angular';
import { InputComponent } from './input.component';
@@ -229,11 +308,11 @@ describe(InputComponent.name, () => {
});
});
`
);
);
createFile(
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
`
createFile(
`libs/${libName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
`
import { MountConfig } from 'cypress/angular';
import { InputStandaloneComponent } from './input-standalone.component';
@@ -260,39 +339,50 @@ describe(InputStandaloneComponent.name, () => {
});
});
`
);
);
}
function useBuildableLibInLib(
projectName: string,
buildableLibName: string,
libName: string
) {
const buildLibNames = names(buildableLibName);
// use the buildable lib in lib so now buildableLib has an indirect dep on app
updateFile(
`libs/${libName}/src/lib/btn-standalone/btn-standalone.component.ts`,
`
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { InputStandaloneComponent } from '@${projectName}/${buildLibNames.fileName}';
@Component({
selector: '${projectName}-btn-standalone',
standalone: true,
imports: [CommonModule, InputStandaloneComponent],
template: '<button class="text-green-500">standlone-{{text}}</button>${projectName} <${projectName}-input-standalone></${projectName}-input-standalone>',
styles: [],
})
export class BtnStandaloneComponent {
@Input() text = 'something';
}
`
);
}
runCLI(
`generate @nrwl/angular:cypress-component-configuration --project=${buildableLibName} --generate-tests --build-target=${appName}:build --no-interactive`
);
if (runCypressTests()) {
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
'All specs passed!'
);
function updateBuilableLibTestsToAssertAppStyles(
appName: string,
buildableLibName: string
) {
updateFile(
`apps/${appName}/src/styles.css`,
`label {color: pink !important;}`
);
removeFile(`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`);
updateFile(
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
(content) => {
// app styles should now apply
return content.replace('rgb(34, 197, 94)', 'rgb(255, 192, 203)');
}
// add tailwind
runCLI(
`generate @nrwl/angular:setup-tailwind --project=${buildableLibName}`
);
updateFile(
`libs/${buildableLibName}/src/lib/input/input.component.cy.ts`,
(content) => {
// text-green-500 should now apply
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
}
);
updateFile(
`libs/${buildableLibName}/src/lib/input-standalone/input-standalone.component.cy.ts`,
(content) => {
// text-green-500 should now apply
return content.replace('rgb(0, 0, 0)', 'rgb(34, 197, 94)');
}
);
expect(runCLI(`component-test ${buildableLibName} --no-watch`)).toContain(
'All specs passed!'
);
checkFilesDoNotExist(`tmp/libs/${buildableLibName}/ct-styles.css`);
}, 300_000);
});
);
}
+52 -2
View File
@@ -1,3 +1,5 @@
import { satisfies } from 'semver';
import { execSync } from 'child_process';
import {
checkFilesDoNotExist,
checkFilesExist,
@@ -21,7 +23,9 @@ describe('js e2e', () => {
scope = newProject();
});
afterEach(() => cleanupProject());
afterEach(() => {
cleanupProject();
});
it('should create libs with js executors (--compiler=swc)', async () => {
const lib = uniq('lib');
@@ -103,7 +107,7 @@ describe('js e2e', () => {
const swcHelpersFromDist = readJson(`dist/libs/${lib}/package.json`)
.peerDependencies['@swc/helpers'];
expect(swcHelpersFromDist).toEqual(swcHelpersFromRoot);
expect(satisfies(swcHelpersFromDist, swcHelpersFromRoot)).toBeTruthy();
updateJson(`libs/${lib}/.swcrc`, (json) => {
json.jsc.externalHelpers = false;
@@ -116,4 +120,50 @@ describe('js e2e', () => {
'peerDependencies.@swc/helpers'
);
}, 240_000);
it('should handle swcrc path mappings', async () => {
const lib = uniq('lib');
runCLI(`generate @nrwl/js:lib ${lib} --compiler=swc --no-interactive`);
// add a dummy x.ts file for path mappings
updateFile(
`libs/${lib}/src/x.ts`,
`
export function x() {
console.log('x');
}
`
);
// update .swcrc to use path mappings
updateJson(`libs/${lib}/.swcrc`, (json) => {
json.jsc.paths = {
'src/*': ['src/*'],
};
return json;
});
// update lib.ts to use x
updateFile(`libs/${lib}/src/lib/${lib}.ts`, () => {
return `
// @ts-ignore
import { x } from 'src/x';
export function myLib() {
console.log(x());
}
myLib();
`;
});
// now run build
runCLI(`build ${lib}`);
// invoke the lib with node
const result = execSync(`node dist/libs/${lib}/src/lib/${lib}.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('x');
}, 240_000);
});
+5 -2
View File
@@ -137,8 +137,11 @@ describe('js e2e', () => {
const rootPackageJson = readJson(`package.json`);
expect(
readJson(`dist/libs/${lib}/package.json`).peerDependencies.tslib
).toEqual(rootPackageJson.dependencies.tslib);
satisfies(
readJson(`dist/libs/${lib}/package.json`).peerDependencies.tslib,
rootPackageJson.dependencies.tslib
)
).toBeTruthy();
updateJson(`libs/${lib}/tsconfig.json`, (json) => {
json.compilerOptions = { ...json.compilerOptions, importHelpers: false };
+31 -15
View File
@@ -27,6 +27,7 @@ import {
import { exec, execSync } from 'child_process';
import * as http from 'http';
import { getLockFileName } from 'nx/src/lock-file/lock-file';
import { satisfies } from 'semver';
function getData(port, path = '/api'): Promise<any> {
return new Promise((resolve) => {
@@ -263,21 +264,36 @@ describe('Build Node apps', () => {
})
);
expect(packageJson.dependencies['@nestjs/common']).toEqual(
rootPackageJson.dependencies['@nestjs/common']
);
expect(packageJson.dependencies['@nestjs/core']).toEqual(
rootPackageJson.dependencies['@nestjs/core']
);
expect(packageJson.dependencies['reflect-metadata']).toEqual(
rootPackageJson.dependencies['reflect-metadata']
);
expect(packageJson.dependencies['rxjs']).toEqual(
rootPackageJson.dependencies['rxjs']
);
expect(packageJson.dependencies['tslib']).toEqual(
rootPackageJson.dependencies['tslib']
);
expect(
satisfies(
packageJson.dependencies['@nestjs/common'],
rootPackageJson.dependencies['@nestjs/common']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['@nestjs/core'],
rootPackageJson.dependencies['@nestjs/core']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['reflect-metadata'],
rootPackageJson.dependencies['reflect-metadata']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['rxjs'],
rootPackageJson.dependencies['rxjs']
)
).toBeTruthy();
expect(
satisfies(
packageJson.dependencies['tslib'],
rootPackageJson.dependencies['tslib']
)
).toBeTruthy();
checkFilesExist(
`dist/apps/${nestapp}/${packageManagerLockFile[packageManager]}`
+1 -1
View File
@@ -35,7 +35,7 @@ describe('Nx Commands', () => {
const s = runCLI('show projects').split('\n');
expect(s.length).toEqual(4);
expect(s.length).toEqual(5);
expect(s).toContain(app1);
expect(s).toContain(app2);
expect(s).toContain(`${app1}-e2e`);
+6 -8
View File
@@ -73,14 +73,12 @@ describe('Nx Plugin', () => {
// doesn't use the collection we are building
// we should change it to point to the right collection using relative path
// TODO: Re-enable this to work with pnpm
xit(`should run the plugin's e2e tests`, async () => {
if (isNotWindows()) {
const plugin = uniq('plugin-name');
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`);
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
expect(e2eResults).toContain('Successfully ran target e2e');
expect(await killPorts()).toBeTruthy();
}
it(`should run the plugin's e2e tests`, async () => {
const plugin = uniq('plugin-name');
runCLI(`generate @nrwl/nx-plugin:plugin ${plugin} --linter=eslint`);
const e2eResults = runCLI(`e2e ${plugin}-e2e`);
expect(e2eResults).toContain('Successfully ran target e2e');
expect(await killPorts()).toBeTruthy();
}, 250000);
it('should be able to generate a migration', async () => {
+18 -2
View File
@@ -180,8 +180,6 @@ describe('Nx Affected and Graph Tests', () => {
mylib = uniq('mylib');
const nxJson: NxJsonConfiguration = readJson('nx.json');
delete nxJson.implicitDependencies;
updateFile('nx.json', JSON.stringify(nxJson));
runCommand(`git init`);
runCommand(`git config user.email "test@test.com"`);
@@ -266,6 +264,24 @@ describe('Nx Affected and Graph Tests', () => {
implicitDependencies: [],
}));
});
it('should handle file renames', () => {
generateAll();
// Move file
updateFile(
`apps/${myapp2}/src/index.html`,
readFile(`apps/${myapp}/src/index.html`)
);
removeFile(`apps/${myapp}/src/index.html`);
const affectedProjects = runCLI(
'print-affected --uncommitted --select projects'
).split(', ');
expect(affectedProjects).toContain(myapp);
expect(affectedProjects).toContain(myapp2);
});
});
describe('print-affected', () => {
+2 -1
View File
@@ -1,6 +1,7 @@
import {
checkFilesDoNotExist,
checkFilesExist,
cleanupProject,
getSize,
killPorts,
newProject,
@@ -107,7 +108,7 @@ describe('Build React libraries and apps', () => {
afterEach(() => {
killPorts();
// cleanupProject();
cleanupProject();
});
describe('Buildable libraries', () => {
+4
View File
@@ -535,6 +535,10 @@ export function runCypressTests() {
if (process.env.NX_E2E_RUN_CYPRESS === 'true') {
ensureCypressInstallation();
return true;
} else {
console.warn(
'Not running Cypress because NX_E2E_RUN_CYPRESS is not set to true.'
);
}
return false;
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
"version": "15.8.1",
"version": "15.8.3",
"granularPathspec": false,
"command": {
"publish": {
+1 -1
View File
@@ -177,7 +177,7 @@
"requires": {
"@angular/core": ">=15.0.0"
},
"description": "Remove browserlist config as it's handled by build-angular",
"description": "Remove browserslist config as it's handled by build-angular",
"factory": "./src/migrations/update-15-2-0/remove-browserlist-config"
},
"update-typescript-target": {
+15 -5
View File
@@ -22,7 +22,7 @@ import {
workspaceRoot,
} from '@nrwl/devkit';
import { existsSync, lstatSync, mkdirSync, writeFileSync } from 'fs';
import { dirname, join, relative } from 'path';
import { dirname, join, relative, sep } from 'path';
import type { BrowserBuilderSchema } from '../src/builders/webpack-browser/webpack-browser.impl';
/**
@@ -168,13 +168,22 @@ function normalizeBuildTargetOptions(
);
const buildOptions = withSchemaDefaults(options);
// polyfill entries might be local files or files that are resolved from node_modules
// like zone.js.
// prevents error from webpack saying can't find <offset>/zone.js.
const handlePolyfillPath = (polyfill: string) => {
const maybeFullPath = join(workspaceRoot, polyfill.split('/').join(sep));
if (existsSync(maybeFullPath)) {
return joinPathFragments(offset, polyfill);
}
return polyfill;
};
// paths need to be unix paths for angular devkit
buildOptions.polyfills =
Array.isArray(buildOptions.polyfills) && buildOptions.polyfills.length > 0
? (buildOptions.polyfills as string[]).map((p) =>
joinPathFragments(offset, p)
)
: joinPathFragments(offset, buildOptions.polyfills as string);
? (buildOptions.polyfills as string[]).map((p) => handlePolyfillPath(p))
: handlePolyfillPath(buildOptions.polyfills as string);
buildOptions.main = joinPathFragments(offset, buildOptions.main);
buildOptions.index =
typeof buildOptions.index === 'string'
@@ -197,6 +206,7 @@ function normalizeBuildTargetOptions(
// then we don't want to have the assets/scripts/styles be included to
// prevent inclusion of unintended stuff like tailwind
if (
buildContext.projectName === ctContext.projectName ||
isCtProjectUsingBuildProject(
ctContext.projectGraph,
buildContext.projectName,
+25 -9
View File
@@ -39,20 +39,36 @@ export function getTempTailwindPath(context: ExecutorContext) {
}
/**
* also returns true if the ct project and build project are the same.
* i.e. component testing inside an app.
*/
* Checks if the childProjectName is a decendent of the parentProjectName
* in the project graph
**/
export function isCtProjectUsingBuildProject(
graph: ProjectGraph,
parentProjectName: string,
childProjectName: string
) {
return (
parentProjectName === childProjectName ||
graph.dependencies[parentProjectName].some(
(p) => p.target === childProjectName
)
): boolean {
const isProjectDirectDep = graph.dependencies[parentProjectName].some(
(p) => p.target === childProjectName
);
if (isProjectDirectDep) {
return true;
}
const maybeIntermediateProjects = graph.dependencies[
parentProjectName
].filter((p) => !graph.externalNodes[p.target]);
for (const maybeIntermediateProject of maybeIntermediateProjects) {
if (
isCtProjectUsingBuildProject(
graph,
maybeIntermediateProject.target,
childProjectName
)
) {
return true;
}
}
return false;
}
export function getProjectConfigByPath(
+8 -1
View File
@@ -11,7 +11,8 @@ import { requireNx } from '../../nx';
import { dirSync } from 'tmp';
import { join } from 'path';
const { readJson, updateJson, getPackageManagerCommand } = requireNx();
const { readJson, updateJson, getPackageManagerCommand, workspaceRoot } =
requireNx();
const UNIDENTIFIED_VERSION = 'UNIDENTIFIED_VERSION';
const NON_SEMVER_TAGS = {
@@ -450,6 +451,7 @@ export function ensurePackage<T extends any = any>(
stdio: [0, 1, 2],
});
addToNodePath(join(workspaceRoot, 'node_modules'));
addToNodePath(join(tempDir, 'node_modules'));
// Re-initialize the added paths into require
@@ -473,6 +475,11 @@ function addToNodePath(dir: string) {
? process.env.NODE_PATH.split(delimiter)
: [];
// The path is already in the node path
if (paths.includes(dir)) {
return;
}
// Add the tmp path
paths.push(dir);
@@ -202,6 +202,13 @@ export function getRelativeImportPath(exportedMember, filePath, basePath) {
dirname(filePath),
`${modulePath}.ts`
);
if (!existsSync(moduleFilePath)) {
// might be a tsx file
moduleFilePath = joinPathFragments(
dirname(filePath),
`${modulePath}.tsx`
);
}
if (!existsSync(moduleFilePath)) {
// might be a index.ts
moduleFilePath = joinPathFragments(
@@ -131,6 +131,40 @@ export default {
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
expect(packageJson.devDependencies['ts-node']).toBeDefined();
expect(packageJson.devDependencies['jest-environment-jsdom']).toBeDefined();
expect(
packageJson.devDependencies['jest-environment-node']
).not.toBeDefined();
});
it('should add dependencies --testEnvironment=node', async () => {
await jestInitGenerator(tree, { testEnvironment: 'node' });
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies.jest).toBeDefined();
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
expect(packageJson.devDependencies['ts-node']).toBeDefined();
expect(packageJson.devDependencies['jest-environment-node']).toBeDefined();
expect(
packageJson.devDependencies['jest-environment-jsdom']
).not.toBeDefined();
});
it('should add dependencies --testEnvironment=none', async () => {
await jestInitGenerator(tree, { testEnvironment: 'none' });
const packageJson = readJson(tree, 'package.json');
expect(packageJson.devDependencies.jest).toBeDefined();
expect(packageJson.devDependencies['@nrwl/jest']).toBeDefined();
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
expect(packageJson.devDependencies['ts-node']).toBeDefined();
expect(
packageJson.devDependencies['jest-environment-jsdom']
).not.toBeDefined();
expect(
packageJson.devDependencies['jest-environment-node']
).not.toBeDefined();
});
it('should make js jest files', async () => {
+5 -1
View File
@@ -33,6 +33,7 @@ const schemaDefaults = {
compiler: 'tsc',
js: false,
rootProject: false,
testEnvironment: 'jsdom',
} as const;
function generateGlobalConfig(tree: Tree, isJS: boolean) {
@@ -146,7 +147,6 @@ function updateDependencies(tree: Tree, options: NormalizedSchema) {
const devDeps = {
'@nrwl/jest': nxVersion,
jest: jestVersion,
'jest-environment-jsdom': jestVersion,
// because the default jest-preset uses ts-jest,
// jest will throw an error if it's not installed
@@ -154,6 +154,10 @@ function updateDependencies(tree: Tree, options: NormalizedSchema) {
'ts-jest': tsJestVersion,
};
if (options.testEnvironment !== 'none') {
devDeps[`jest-environment-${options.testEnvironment}`] = jestVersion;
}
if (!options.js) {
devDeps['ts-node'] = tsNodeVersion;
devDeps['@types/jest'] = jestTypesVersion;
+1
View File
@@ -2,6 +2,7 @@ export interface JestInitSchema {
compiler?: 'tsc' | 'babel' | 'swc';
js?: boolean;
skipPackageJson?: boolean;
testEnvironment?: 'node' | 'jsdom' | 'none';
/**
* @deprecated
*/
@@ -18,6 +18,13 @@
"description": "Do not add dependencies to `package.json`.",
"x-priority": "internal"
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node", "none"],
"description": "The test environment for jest. This controls which jest-environment-* package is installed",
"default": "jsdom",
"x-priority": "important"
},
"js": {
"type": "boolean",
"default": false,
@@ -18,15 +18,13 @@ const schemaDefaults = {
skipSetupFile: false,
skipSerializers: false,
rootProject: false,
testEnvironment: 'jsdom',
} as const;
function normalizeOptions(options: JestProjectSchema) {
if (!options.testEnvironment) {
options.testEnvironment = 'jsdom';
}
if (options.testEnvironment === 'jsdom') {
options.testEnvironment = '';
}
if (!options.hasOwnProperty('supportTsx')) {
options.supportTsx = false;
@@ -31,6 +31,11 @@ export function createFiles(tree: Tree, options: JestProjectSchema) {
generateFiles(tree, join(__dirname, filesFolder), projectConfig.root, {
tmpl: '',
...options,
// jsdom is the default
testEnvironment:
options.testEnvironment === 'none' || options.testEnvironment === 'jsdom'
? ''
: options.testEnvironment,
transformer,
transformerOptions,
js: !!options.js,
+1 -1
View File
@@ -7,7 +7,7 @@ export interface JestProjectSchema {
skipSetupFile?: boolean;
setupFile?: 'angular' | 'web-components' | 'none';
skipSerializers?: boolean;
testEnvironment?: 'node' | 'jsdom' | '';
testEnvironment?: 'node' | 'jsdom' | 'none';
/**
* @deprecated use compiler: "babel" instead
*/
@@ -40,7 +40,7 @@
},
"testEnvironment": {
"type": "string",
"enum": ["jsdom", "node"],
"enum": ["jsdom", "node", "none"],
"description": "The test environment for jest.",
"default": "jsdom",
"x-priority": "important"
+1 -1
View File
@@ -42,7 +42,7 @@ module.exports = function (api: any, options: NxWebBabelPresetOptions = {}) {
process.env.NODE_ENV === 'test'
? { targets: { node: 'current' }, loose: true }
: {
// Allow importing core-js in entrypoint and use browserlist to select polyfills.
// Allow importing core-js in entrypoint and use browserslist to select polyfills.
useBuiltIns: options.useBuiltIns ?? 'entry',
corejs: 3,
// Do not transform modules to CJS
+18 -3
View File
@@ -1,4 +1,4 @@
import { ExecutorContext } from '@nrwl/devkit';
import { ExecutorContext, readJsonFile, writeJsonFile } from '@nrwl/devkit';
import {
assetGlobsToFiles,
FileInputOutput,
@@ -24,6 +24,7 @@ import {
import { compileSwc, compileSwcWatch } from '../../utils/swc/compile-swc';
import { getSwcrcPath } from '../../utils/swc/get-swcrc-path';
import { generateTmpSwcrc } from '../../utils/swc/inline';
import type { Options } from '@swc/core';
export function normalizeOptions(
options: SwcExecutorOptions,
@@ -68,7 +69,21 @@ export function normalizeOptions(
// default to current directory if projectRootParts is [].
// Eg: when a project is at the root level, outside of layout dir
const swcCwd = projectRootParts.join('/') || '.';
const swcrcPath = getSwcrcPath(options, contextRoot, projectRoot);
let swcrcPath = getSwcrcPath(options, contextRoot, projectRoot);
try {
const swcrcContent = readJsonFile(swcrcPath) as Options;
// if we have path mappings setup but baseUrl isn't specified, then we're proceeding with the following logic
if (
swcrcContent.jsc &&
swcrcContent.jsc.paths &&
!swcrcContent.jsc.baseUrl
) {
swcrcContent.jsc.baseUrl = `./${projectDir}`;
swcrcPath = getSwcrcPath(options, contextRoot, projectRoot, true);
writeJsonFile(swcrcPath, swcrcContent);
}
} catch (e) {}
const swcCliOptions = {
srcPath: projectDir,
@@ -190,7 +205,7 @@ export async function* swcExecutor(
}
function removeTmpSwcrc(swcrcPath: string) {
if (swcrcPath.startsWith('tmp/')) {
if (swcrcPath.includes('tmp/') && swcrcPath.includes('.generated.swcrc')) {
removeSync(dirname(swcrcPath));
}
}
+9 -4
View File
@@ -4,9 +4,14 @@ import { SwcExecutorOptions } from '../schema';
export function getSwcrcPath(
options: SwcExecutorOptions,
contextRoot: string,
projectRoot: string
projectRoot: string,
temp = false
) {
return options.swcrc
? join(contextRoot, options.swcrc)
: join(contextRoot, projectRoot, '.swcrc');
let swcrcPath = options.swcrc ?? join(projectRoot, '.swcrc');
if (temp) {
swcrcPath = join('tmp', swcrcPath.replace('.swcrc', '.generated.swcrc'));
}
return join(contextRoot, swcrcPath);
}
@@ -267,6 +267,7 @@ export async function addLintingToApplication(
unitTestRunner: options.unitTestRunner,
skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
});
return lintTask;
+3 -1
View File
@@ -47,7 +47,9 @@ export async function initGenerator(tree: Tree, schema: Schema) {
})
);
if (options.unitTestRunner === 'jest') {
tasks.push(await jestInitGenerator(tree, schema));
tasks.push(
await jestInitGenerator(tree, { ...schema, testEnvironment: 'node' })
);
}
tasks.push(updateDependencies(tree));
@@ -49,6 +49,12 @@ describe('lib', () => {
},
},
});
expect(
readJson(tree, 'package.json').devDependencies['jest-environment-jsdom']
).not.toBeDefined();
expect(
readJson(tree, 'package.json').devDependencies['jest-environment-node']
).toBeDefined();
});
it('adds srcRootForCompilationRoot', async () => {
@@ -43,7 +43,7 @@ export function runNxCommandAsync(
silenceError: false,
}
): Promise<{ stdout: string; stderr: string }> {
if (fileExists('package.json')) {
if (fileExists(tmpProjPath('package.json'))) {
const pmc = getPackageManagerCommand();
return runCommandAsync(`${pmc.exec} nx ${command}`, opts);
} else if (process.platform === 'win32') {
@@ -21,7 +21,7 @@ export function runNxCommand(
cwd: tmpProjPath(),
env: { ...process.env, ...opts.env },
};
if (fileExists('package.json')) {
if (fileExists(tmpProjPath('package.json'))) {
const pmc = getPackageManagerCommand();
return execSync(`${pmc.exec} nx ${command}`, execSyncOptions);
} else if (process.platform === 'win32') {
+4
View File
@@ -62,6 +62,10 @@ if (
// this file is already in the local workspace
if (localNx === resolveNx(null)) {
if (localNx.includes('.nx') && !process.env.NX_WRAPPER_SET) {
const nxWrapperPath = localNx.replace(/\.nx.*/, '.nx/') + 'nxw.js';
require(nxWrapperPath);
}
initLocal(workspace);
} else {
// Nx is being run from globally installed CLI - hand off to the local
+6
View File
@@ -47,6 +47,12 @@
"version": "15.0.12-beta.1",
"description": "Set project names in project.json files",
"implementation": "./src/migrations/update-15-1-0/set-project-names"
},
"15.8.2-update-nx-wrapper": {
"cli": "nx",
"version": "15.8.2-beta.0",
"description": "Updates the nx wrapper in encapsulated repos.",
"implementation": "./src/migrations/update-15-8-2/update-nxw"
}
}
}
+2 -9
View File
@@ -20,6 +20,7 @@ import { createProjectGraphAsync } from '../project-graph/project-graph';
import { filterAffected } from '../project-graph/affected/affected-project-graph';
import { readNxJson } from '../config/configuration';
import { ProjectGraph } from '../config/project-graph';
import { chunkify } from '../utils/chunkify';
const PRETTIER_PATH = require.resolve('prettier/bin-prettier');
@@ -40,7 +41,7 @@ export async function format(
);
// Chunkify the patterns array to prevent crashing the windows terminal
const chunkList: string[][] = chunkify(patterns, 50);
const chunkList: string[][] = chunkify(patterns);
switch (command) {
case 'write':
@@ -143,14 +144,6 @@ function getPatternsFromProjects(
return getProjectRoots(projects, projectGraph);
}
function chunkify(target: string[], size: number): string[][] {
return target.reduce((current: string[][], value: string, index: number) => {
if (index % size === 0) current.push([]);
current[current.length - 1].push(value);
return current;
}, []);
}
function write(patterns: string[]) {
if (patterns.length > 0) {
const [swcrcPatterns, regularPatterns] = patterns.reduce(
+4 -1
View File
@@ -3,7 +3,10 @@ import { createProjectGraphAsync } from '../project-graph/project-graph';
export async function show(args: { object: 'projects' }): Promise<void> {
if (args.object == 'projects') {
const graph = await createProjectGraphAsync();
process.stdout.write(Object.keys(graph.nodes).join('\n'));
const projects = Object.keys(graph.nodes).join('\n');
if (projects.length) {
console.log(projects);
}
} else {
throw new Error(`Unrecognized option: ${args.object}`);
}
+4 -20
View File
@@ -1,4 +1,5 @@
import { spawn } from 'child_process';
import { chunkify } from '../utils/chunkify';
import { fileExists } from '../utils/fileutils';
import { joinPathFragments } from '../utils/path';
@@ -12,26 +13,9 @@ export async function getGitHashForFiles(
);
const res: Map<string, string> = new Map<string, string>();
const promises: Promise<Map<string, string>>[] = [];
if (filesToHash.length) {
// On windows the max length is limited by the length of
// the overall comand, rather than the number of individual
// arguments. Since file paths are large and rather variable,
// we use a smaller batchSize.
const batchSize = process.platform === 'win32' ? 250 : 4000;
for (
let startIndex = 0;
startIndex < filesToHash.length;
startIndex += batchSize
) {
promises.push(
getGitHashForBatch(
filesToHash.slice(startIndex, startIndex + batchSize),
path
)
);
}
}
const promises: Promise<Map<string, string>>[] = chunkify(filesToHash).map(
(files) => getGitHashForBatch(files, path)
);
// Merge batch results into final result set
const batchResults = await Promise.all(promises);
for (const batch of batchResults) {
+6 -14
View File
@@ -459,20 +459,12 @@ class TaskHasher {
if (!this.filesetHashes[mapKey]) {
this.filesetHashes[mapKey] = new Promise(async (res) => {
const parts = [];
if (fileset.indexOf('*') > -1) {
this.projectGraph.allWorkspaceFiles
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
.forEach((f) => {
parts.push(f.hash);
});
} else {
const matchingFile = this.projectGraph.allWorkspaceFiles.find(
(t) => t.file === withoutWorkspaceRoot
);
if (matchingFile) {
parts.push(matchingFile.hash);
}
}
this.projectGraph.allWorkspaceFiles
.filter((f) => minimatch(f.file, withoutWorkspaceRoot))
.forEach((f) => {
parts.push(f.hash);
});
const value = this.hashing.hashArray(parts);
res({
value,
+5 -1
View File
@@ -141,7 +141,11 @@ function findVersion(
) {
return snapshot.resolution.slice(packageName.length + 1);
}
if (!isBerry && !satisfies(snapshot.version, versionRange)) {
if (
!isBerry &&
snapshot.resolved &&
!satisfies(snapshot.version, versionRange)
) {
return snapshot.resolved;
}
// otherwise it's a standard version
@@ -0,0 +1,13 @@
import {
getNxWrapperContents,
nxWrapperPath,
} from 'nx/src/nx-init/encapsulated/add-nx-scripts';
import { normalizePath } from '../../utils/path';
import { Tree } from '../../generators/tree';
export default async function (tree: Tree) {
const wrapperPath = normalizePath(nxWrapperPath());
if (tree.exists(wrapperPath)) {
tree.write(wrapperPath, getNxWrapperContents());
}
}
@@ -11,7 +11,7 @@ import {
} from '../../generators/tree';
import { writeJson } from '../../generators/utils/json';
const nxWrapperPath = (p: typeof import('path') = path) =>
export const nxWrapperPath = (p: typeof import('path') = path) =>
p.join('.nx', 'nxw.js');
const NODE_MISSING_ERR =
@@ -36,7 +36,7 @@ export function generateEncapsulatedNxSetup(version?: string) {
const host = new FsTree(process.cwd(), false);
writeMinimalNxJson(host, version);
updateGitIgnore(host);
host.write(nxWrapperPath(), getNodeScriptContents());
host.write(nxWrapperPath(), getNxWrapperContents());
host.write('nx.bat', BATCH_SCRIPT_CONTENTS);
host.write('nx', SHELL_SCRIPT_CONTENTS, {
mode: FsConstants.S_IXUSR | FsConstants.S_IRUSR | FsConstants.S_IWUSR,
@@ -75,7 +75,7 @@ export function updateGitIgnore(host: Tree) {
);
}
function getNodeScriptContents() {
export function getNxWrapperContents() {
// Read nxw.js, but remove any empty comments or comments that start with `//#: `
// This removes the sourceMapUrl since it is invalid, as well as any internal comments.
return readFileSync(path.join(__dirname, 'nxw.js'), 'utf-8').replace(
+2 -1
View File
@@ -99,7 +99,8 @@ function ensureUpToDateInstallation() {
}
}
if (require.main === module) {
if (require.main === module && !process.env.NX_WRAPPER_SET) {
process.env.NX_WRAPPER_SET = 'true';
ensureUpToDateInstallation();
require('./installation/node_modules/nx/bin/nx');
}
@@ -1,4 +1,5 @@
import { existsSync } from 'fs';
import { defaultHashing } from '../../../../hasher/hashing-impl';
import { join } from 'path';
import { ProjectGraphBuilder } from '../../../../project-graph/project-graph-builder';
@@ -16,13 +17,16 @@ export function buildNpmPackageNodes(builder: ProjectGraphBuilder) {
...packageJson.devDependencies,
};
Object.keys(deps).forEach((d) => {
builder.addExternalNode({
type: 'npm',
name: `npm:${d}`,
data: {
version: deps[d],
packageName: d,
},
});
if (!builder.graph.externalNodes[`npm:${d}`]) {
builder.addExternalNode({
type: 'npm',
name: `npm:${d}`,
data: {
version: deps[d],
packageName: d,
hash: defaultHashing.hashArray([d, deps[d]]),
},
});
}
});
}
@@ -116,16 +116,19 @@ describe('explicit package json dependencies', () => {
sourceProjectName: 'proj',
targetProjectName: 'proj2',
sourceProjectFile: 'libs/proj/package.json',
type: 'static',
},
{
sourceProjectFile: 'libs/proj/package.json',
sourceProjectName: 'proj',
targetProjectName: 'npm:external',
type: 'static',
},
{
sourceProjectName: 'proj',
targetProjectName: 'proj3',
sourceProjectFile: 'libs/proj/package.json',
type: 'static',
},
]);
});
@@ -1,6 +1,10 @@
import { defaultFileRead } from '../file-utils';
import { join } from 'path';
import { ProjectFileMap, ProjectGraph } from '../../config/project-graph';
import {
DependencyType,
ProjectFileMap,
ProjectGraph,
} from '../../config/project-graph';
import { parseJson } from '../../utils/json';
import { getImportPath, joinPathFragments } from '../../utils/path';
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
@@ -88,12 +92,14 @@ function processPackageJson(
sourceProjectName: sourceProject,
targetProjectName: packageNameMap[d],
sourceProjectFile: fileName,
type: DependencyType.static,
});
} else if (graph.externalNodes[`npm:${d}`]) {
collectedDeps.push({
sourceProjectName: sourceProject,
targetProjectName: `npm:${d}`,
sourceProjectFile: fileName,
type: DependencyType.static,
});
}
});
@@ -67,6 +67,16 @@ export class ProjectGraphBuilder {
* Adds a external node to the project graph
*/
addExternalNode(node: ProjectGraphExternalNode): void {
// Check if project with the same name already exists
if (this.graph.externalNodes[node.name]) {
throw new Error(
`Multiple projects are named "${node.name}". One has version "${
node.data.version
}" and the other has version "${
this.graph.externalNodes[node.name].data.version
}". Please resolve the conflicting package names.`
);
}
this.graph.externalNodes[node.name] = node;
}
+15
View File
@@ -0,0 +1,15 @@
import { chunkify } from './chunkify';
describe('chunkify', () => {
it('should wrap chunks at passed in size', () => {
const files = ['aa', 'bb', 'cc', 'dd', 'ee'];
expect(chunkify(files, 4)).toHaveLength(5);
expect(chunkify(files, 7)).toHaveLength(3);
expect(chunkify(files, 16)).toHaveLength(1);
});
it('should contain all items from target', () => {
const files = ['aa', 'bb', 'cc', 'dd', 'ee'];
expect(chunkify(files, 7).flat()).toHaveLength(5);
});
});
+41
View File
@@ -0,0 +1,41 @@
import { execSync } from 'child_process';
const TERMINAL_SIZE =
process.platform === 'win32' ? 8192 : getUnixTerminalSize();
export function chunkify(
target: string[],
maxChunkLength: number = TERMINAL_SIZE - 500
): string[][] {
const chunks = [];
let currentChunk = [];
let currentChunkLength = 0;
for (const file of target) {
if (
// Prevent empty chunk if first file path is longer than maxChunkLength
currentChunk.length &&
// +1 accounts for the space between file names
currentChunkLength + file.length + 1 >= maxChunkLength
) {
chunks.push(currentChunk);
currentChunk = [];
currentChunkLength = 0;
}
currentChunk.push(file);
currentChunkLength += file.length + 1;
}
chunks.push(currentChunk);
return chunks;
}
function getUnixTerminalSize() {
try {
const argMax = execSync('getconf ARG_MAX').toString().trim();
return Number.parseInt(argMax);
} catch {
// This number varies by system, but 100k seems like a safe
// number from some research...
// https://stackoverflow.com/questions/19354870/bash-command-line-and-input-limit
return 100000;
}
}
+2 -2
View File
@@ -222,7 +222,7 @@ export function parseFiles(options: NxArgs): { files: string[] } {
}
function getUncommittedFiles(): string[] {
return parseGitOutput(`git diff --name-only --relative HEAD .`);
return parseGitOutput(`git diff --name-only --no-renames --relative HEAD .`);
}
``;
@@ -249,7 +249,7 @@ function getFilesUsingBaseAndHead(base: string, head: string): string[] {
.trim();
}
return parseGitOutput(
`git diff --name-only --relative "${mergeBase}" "${head}"`
`git diff --name-only --no-renames --relative "${mergeBase}" "${head}"`
);
}
+1
View File
@@ -203,6 +203,7 @@ export function registerPluginTSTranspiler() {
lib: ['es2021'],
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2021,
inlineSourceMap: true,
esModuleInterop: true,
skipLibCheck: true,
experimentalDecorators: true,
@@ -44,6 +44,26 @@ describe('app', () => {
expect(projects.get('my-app-e2e').root).toEqual('apps/my-app-e2e');
});
it('should add vite types to tsconfigs', async () => {
await applicationGenerator(appTree, {
...schema,
bundler: 'vite',
unitTestRunner: 'vitest',
});
const tsconfigApp = readJson(appTree, 'apps/my-app/tsconfig.app.json');
expect(tsconfigApp.compilerOptions.types).toEqual([
'node',
'vite/client',
]);
const tsconfigSpec = readJson(appTree, 'apps/my-app/tsconfig.spec.json');
expect(tsconfigSpec.compilerOptions.types).toEqual([
'vitest/globals',
'vitest/importMeta',
'vite/client',
'node',
]);
});
it('should not overwrite default project if already set', async () => {
const nxJson = readNxJson(appTree);
nxJson.defaultProject = 'some-awesome-project';
@@ -61,6 +61,26 @@ describe('lib', () => {
});
});
it('should add vite types to tsconfigs', async () => {
await libraryGenerator(tree, {
...defaultSchema,
bundler: 'vite',
unitTestRunner: 'vitest',
});
const tsconfigApp = readJson(tree, 'libs/my-lib/tsconfig.lib.json');
expect(tsconfigApp.compilerOptions.types).toEqual([
'node',
'vite/client',
]);
const tsconfigSpec = readJson(tree, 'libs/my-lib/tsconfig.spec.json');
expect(tsconfigSpec.compilerOptions.types).toEqual([
'vitest/globals',
'vitest/importMeta',
'vite/client',
'node',
]);
});
it('should update tags', async () => {
await libraryGenerator(tree, { ...defaultSchema, tags: 'one,two' });
const project = readProjectConfiguration(tree, 'my-lib');
@@ -51,9 +51,7 @@ export async function withModuleFederation(
config.plugins.push(
new ModuleFederationPlugin({
name: options.name,
library: {
type: 'module',
},
library: options.library ?? { type: 'module' },
filename: 'remoteEntry.js',
exposes: options.exposes,
remotes: mappedRemotes,
+16 -1
View File
@@ -1,6 +1,6 @@
import { Tree } from 'nx/src/generators/tree';
import * as shared from '@nrwl/js/src/utils/typescript/create-ts-config';
import { writeJson } from 'nx/src/generators/utils/json';
import { updateJson, writeJson } from 'nx/src/generators/utils/json';
export function createTsConfig(
host: Tree,
@@ -56,6 +56,21 @@ export function createTsConfig(
}
writeJson(host, `${projectRoot}/tsconfig.json`, json);
const tsconfigProjectPath = `${projectRoot}/tsconfig.${type}.json`;
if (options.bundler === 'vite' && host.exists(tsconfigProjectPath)) {
updateJson(host, tsconfigProjectPath, (json) => {
json.compilerOptions ??= {};
const types = new Set(json.compilerOptions.types ?? []);
types.add('node');
types.add('vite/client');
json.compilerOptions.types = Array.from(types);
return json;
});
}
}
export function extractTsConfigBase(host: Tree) {
+1 -1
View File
@@ -39,7 +39,7 @@
},
"peerDependencies": {
"vite": "^4.0.1",
"vitest": "^0.25.8"
"vitest": ">=0.25.8 <1.0.0"
},
"publishConfig": {
"access": "public"
@@ -2,7 +2,7 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["vitest/globals", "node"]
"types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"]
},
"include": [
"vite.config.ts",
@@ -93,6 +93,8 @@ describe('vitest generator', () => {
"outDir": "../../dist/out-tsc",
"types": Array [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
],
},
+7 -1
View File
@@ -19,6 +19,7 @@ import { ViteDevServerExecutorOptions } from '../executors/dev-server/schema';
import { VitePreviewServerExecutorOptions } from '../executors/preview-server/schema';
import replaceFiles from '../../plugins/rollup-replace-files.plugin';
import { ViteBuildExecutorOptions } from '../executors/build/schema';
import * as path from 'path';
/**
* Returns the path to the vite config file or undefined when not found.
@@ -70,9 +71,14 @@ export function getViteSharedConfig(
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
const root = path.relative(
context.cwd,
joinPathFragments(context.root, projectRoot)
);
return {
mode: options.mode,
root: projectRoot,
root,
base: options.base,
configFile: normalizeViteConfigFilePath(projectRoot, options.configFile),
plugins: [replaceFiles(options.fileReplacements) as PluginOption],
@@ -513,6 +513,8 @@ describe('app', () => {
).toMatchInlineSnapshot(`
Array [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
]
`);