Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf05fc3c33 | |||
| 8f6eb817aa | |||
| 731f8394f0 | |||
| 64103afab1 | |||
| 9ce268b8a7 | |||
| acd5964aec | |||
| bb05d802a6 | |||
| d77b26173b | |||
| 7865e6a91c | |||
| 26fce5b185 | |||
| 38509cce5d | |||
| 640cfbaad6 | |||
| 3569a6f158 | |||
| 4a30fcd04a | |||
| 94890f408f | |||
| 8ba237eada | |||
| 2405a2e278 | |||
| c30d39a028 | |||
| 60f4aca4a1 | |||
| f71730397d | |||
| ea42c9fe43 | |||
| 5c1cc201cc | |||
| df3cd667d4 | |||
| a109b295ae | |||
| 8fd0f5ee0f | |||
| ce0fe45de0 | |||
| f54dd1a9f6 | |||
| c3f853a21b | |||
| 2f2c7d8c17 | |||
| 77337691e2 | |||
| d294c770d0 | |||
| c54e13669c | |||
| 18a7eab104 | |||
| 1dcfec1677 | |||
| 0b0131ef6c | |||
| 87735cbe6d | |||
| 7addb44270 | |||
| 6c289f807e | |||
| 7bde32f548 | |||
| c344a1051d | |||
| e7e7ba79f0 | |||
| 81f8e8c287 | |||
| 8b305d0232 | |||
| b9ea773682 | |||
| 35082c7388 | |||
| d070fdfee8 | |||
| 65dc805957 | |||
| ac4cf513c9 |
@@ -185,6 +185,11 @@
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Relative path within the output folder."
|
||||
},
|
||||
"ignore": {
|
||||
"description": "An array of globs to ignore.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
@@ -278,6 +283,11 @@
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Relative path within the output folder."
|
||||
},
|
||||
"ignore": {
|
||||
"description": "An array of globs to ignore.",
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`."
|
||||
},
|
||||
"skipValidation": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not perform any validation on existing project."
|
||||
},
|
||||
"importPath": {
|
||||
"type": "string",
|
||||
"description": "The library name used to import it, like `@myorg/my-awesome-lib`."
|
||||
|
||||
@@ -87,6 +87,11 @@
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`."
|
||||
},
|
||||
"skipValidation": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not perform any validation on existing project."
|
||||
},
|
||||
"devServer": {
|
||||
"type": "boolean",
|
||||
"description": "Add a serve target to run a local webpack dev-server",
|
||||
|
||||
@@ -68,7 +68,7 @@ If you are only planning to use incremental builds to speed up your CI, then the
|
||||
|
||||
## Custom Serve Target
|
||||
|
||||
If you are implementing a custom serve command, you can use `WebpackNxBuildCoordinationPlugin` provided by `@nrwl/web`. It's a webpack plugin you can use to coordinate the compiling of the libs and the webpack linking.
|
||||
If you are implementing a custom serve command, you can use `WebpackNxBuildCoordinationPlugin` provided by `@nrwl/webpack`. It's a webpack plugin you can use to coordinate the compiling of the libs and the webpack linking.
|
||||
|
||||
## Using Webpack Module Federation to implement incremental builds
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ describe('convert Angular CLI workspace to an Nx workspace', () => {
|
||||
cli: {
|
||||
packageManager: packageManager,
|
||||
},
|
||||
defaultProject: project,
|
||||
implicitDependencies: {
|
||||
'.eslintrc.json': '*',
|
||||
'package.json': {
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
|
||||
describe('EsBuild Plugin', () => {
|
||||
let proj: string;
|
||||
|
||||
beforeEach(() => (proj = newProject()));
|
||||
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it('should setup and build projects using build', async () => {
|
||||
@@ -30,8 +32,10 @@ describe('EsBuild Plugin', () => {
|
||||
runCLI(`build ${myPkg}`);
|
||||
|
||||
expect(runCommand(`node dist/libs/${myPkg}/index.js`)).toMatch(/Hello/);
|
||||
|
||||
// main field should be set correctly in package.json
|
||||
checkFilesExist(`dist/libs/${myPkg}/package.json`);
|
||||
expect(runCommand(`node dist/libs/${myPkg}`)).toMatch(/Hello/);
|
||||
|
||||
expect(readFile(`dist/libs/${myPkg}/assets/a.md`)).toMatch(/file a/);
|
||||
expect(readFile(`dist/libs/${myPkg}/assets/b.md`)).toMatch(/file b/);
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
newProject,
|
||||
runCLI,
|
||||
uniq,
|
||||
} from '@nrwl/e2e/utils';
|
||||
|
||||
describe('Next.js Applications', () => {
|
||||
let proj: string;
|
||||
|
||||
beforeEach(
|
||||
() =>
|
||||
(proj = newProject({
|
||||
name: 'proj',
|
||||
packageManager: 'npm',
|
||||
}))
|
||||
);
|
||||
|
||||
afterEach(() => cleanupProject());
|
||||
|
||||
it('should run a Next.js based Storybook setup', async () => {
|
||||
const appName = uniq('app');
|
||||
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
|
||||
runCLI(
|
||||
`generate @nrwl/next:component Foo --project=${appName} --no-interactive`
|
||||
);
|
||||
|
||||
// Currently due to auto-installing peer deps in pnpm, the generator can fail while installing deps with unmet peet deps.
|
||||
runCLI(
|
||||
`generate @nrwl/react:storybook-configuration ${appName} --generateStories --no-interactive`,
|
||||
{
|
||||
silenceError: true,
|
||||
}
|
||||
);
|
||||
|
||||
runCLI(`build-storybook ${appName}`);
|
||||
checkFilesExist(`dist/storybook/${appName}/index.html`);
|
||||
}, 1_000_000);
|
||||
});
|
||||
@@ -1,14 +1,12 @@
|
||||
import {
|
||||
rmDist,
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
expectJestTestsToPass,
|
||||
isNotWindows,
|
||||
killPorts,
|
||||
newProject,
|
||||
promisifiedTreeKill,
|
||||
readFile,
|
||||
readJson,
|
||||
rmDist,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommandUntil,
|
||||
@@ -414,9 +412,6 @@ describe('Next.js Applications', () => {
|
||||
checkExport: false,
|
||||
});
|
||||
}, 300_000);
|
||||
it('should run default jest tests', async () => {
|
||||
await expectJestTestsToPass('@nrwl/next:app');
|
||||
}, 100_000);
|
||||
});
|
||||
|
||||
function getData(port: number, path = ''): Promise<any> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
isNotWindows,
|
||||
newProject,
|
||||
@@ -139,7 +140,7 @@ describe('Extra Nx Misc Tests', () => {
|
||||
it('should pass options', async () => {
|
||||
updateProjectConfig(mylib, (config) => {
|
||||
config.targets.echo = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: 'echo --var1={args.var1}',
|
||||
var1: 'a',
|
||||
@@ -156,7 +157,7 @@ describe('Extra Nx Misc Tests', () => {
|
||||
const echoTarget = uniq('echo');
|
||||
updateProjectConfig(mylib, (config) => {
|
||||
config.targets[echoTarget] = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
commands: [
|
||||
'echo "Arguments:"',
|
||||
@@ -191,7 +192,7 @@ describe('Extra Nx Misc Tests', () => {
|
||||
it('ttt should fail when a process exits non-zero', async () => {
|
||||
updateProjectConfig(mylib, (config) => {
|
||||
config.targets.error = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: `exit 1`,
|
||||
},
|
||||
@@ -221,5 +222,46 @@ describe('Extra Nx Misc Tests', () => {
|
||||
})
|
||||
).not.toThrow();
|
||||
}, 1000000);
|
||||
|
||||
it('should handle caching output directories containing trailing slashes', async () => {
|
||||
// this test relates to https://github.com/nrwl/nx/issues/10549
|
||||
// 'cp -a /path/dir/ dest/' operates differently to 'cp -a /path/dir dest/'
|
||||
// --> which means actual build works but subsequent populate from cache (using cp -a) does not
|
||||
// --> the fix is to remove trailing slashes to ensure consistent & expected behaviour
|
||||
|
||||
const mylib = uniq('lib');
|
||||
|
||||
const folder = `dist/libs/${mylib}/some-folder`;
|
||||
|
||||
runCLI(`generate @nrwl/workspace:lib ${mylib}`);
|
||||
|
||||
runCLI(
|
||||
`generate @nrwl/workspace:run-commands build --command=echo --outputs=${folder}/ --project=${mylib}`
|
||||
);
|
||||
|
||||
const commands = [
|
||||
process.platform === 'win32'
|
||||
? `mkdir ${folder}` // Windows
|
||||
: `mkdir -p ${folder}`,
|
||||
`echo dummy > ${folder}/dummy.txt`,
|
||||
];
|
||||
updateProjectConfig(mylib, (config) => {
|
||||
delete config.targets.build.options.command;
|
||||
config.targets.build.options = {
|
||||
...config.targets.build.options,
|
||||
parallel: false,
|
||||
commands: commands,
|
||||
};
|
||||
return config;
|
||||
});
|
||||
|
||||
// confirm that it builds correctly
|
||||
runCLI(`build ${mylib}`);
|
||||
checkFilesExist(`${folder}/dummy.txt`);
|
||||
|
||||
// confirm that it populates correctly from the cache
|
||||
runCLI(`build ${mylib}`);
|
||||
checkFilesExist(`${folder}/dummy.txt`);
|
||||
}, 120000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,9 +24,7 @@ describe('Workspace Tests', () => {
|
||||
proj = newProject();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cleanupProject();
|
||||
});
|
||||
afterAll(() => cleanupProject());
|
||||
|
||||
describe('@nrwl/workspace:library', () => {
|
||||
it('should create a library that can be tested and linted', async () => {
|
||||
@@ -195,6 +193,10 @@ describe('Workspace Tests', () => {
|
||||
type: 'boolean',
|
||||
description: 'skip changes to tsconfig',
|
||||
};
|
||||
json.properties['inlineprop'] = json.properties['name'];
|
||||
json.required = ['inlineprop'];
|
||||
delete json.properties['name'];
|
||||
|
||||
updateFile(
|
||||
`tools/generators/${custom}/schema.json`,
|
||||
JSON.stringify(json)
|
||||
@@ -205,10 +207,17 @@ describe('Workspace Tests', () => {
|
||||
`tools/generators/${custom}/index.ts`,
|
||||
indexFile.replace(
|
||||
'name: schema.name',
|
||||
'name: schema.name, directory: schema.directory, skipTsConfig: schema.skipTsConfig'
|
||||
'name: schema.inlineprop, directory: schema.directory, skipTsConfig: schema.skipTsConfig'
|
||||
)
|
||||
);
|
||||
|
||||
const helpOutput = runCLI(`workspace-generator ${custom} --help`);
|
||||
expect(helpOutput).toContain(
|
||||
`workspace-generator ${custom} [inlineprop] (options)`
|
||||
);
|
||||
expect(helpOutput).toContain(`--directory`);
|
||||
expect(helpOutput).toContain(`--skipTsConfig`);
|
||||
|
||||
const workspace = uniq('workspace');
|
||||
const dryRunOutput = runCLI(
|
||||
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir --skipTsConfig=true -d`
|
||||
@@ -218,11 +227,10 @@ describe('Workspace Tests', () => {
|
||||
`CREATE libs/dir/${workspace}/src/index.ts`
|
||||
);
|
||||
|
||||
const output = runCLI(
|
||||
runCLI(
|
||||
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir`
|
||||
);
|
||||
checkFilesExist(`libs/dir/${workspace}/src/index.ts`);
|
||||
expect(output).not.toContain('UPDATE nx.json');
|
||||
|
||||
const jsonFailing = readJson(`tools/generators/${failing}/schema.json`);
|
||||
jsonFailing.properties = {};
|
||||
|
||||
@@ -295,7 +295,7 @@ describe('Nx Plugin', () => {
|
||||
if (basename(f) === 'my-project-file') {
|
||||
return {
|
||||
build: {
|
||||
executor: "@nrwl/workspace:run-commands",
|
||||
executor: "nx:run-commands",
|
||||
options: {
|
||||
command: "echo 'custom registered target'"
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('cache', () => {
|
||||
// updateFile('workspace.json', (c) => {
|
||||
// const workspaceJson = JSON.parse(c);
|
||||
// workspaceJson.projects[myapp1].targets.lint = {
|
||||
// executor: '@nrwl/workspace:run-commands',
|
||||
// executor: 'nx:run-commands',
|
||||
// options: {
|
||||
// command: 'echo hi && exit 1',
|
||||
// },
|
||||
|
||||
@@ -164,7 +164,7 @@ describe('Nx Running Tests', () => {
|
||||
projectFilePatterns: ['inferred-project.nxproject'],
|
||||
registerProjectTargets: () => ({
|
||||
"echo": {
|
||||
"executor": "@nrwl/workspace:run-commands",
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "echo inferred-target"
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ describe('Webpack Plugin', () => {
|
||||
runCLI(`build ${myPkg}`);
|
||||
let output = runCommand(`node dist/libs/${myPkg}/main.js`);
|
||||
expect(output).toMatch(/Hello/);
|
||||
expect(output).not.toMatch(/Conflicting/);
|
||||
expect(output).not.toMatch(/process.env.NODE_ENV/);
|
||||
|
||||
updateProjectConfig(myPkg, (config) => {
|
||||
delete config.targets.build;
|
||||
|
||||
@@ -449,7 +449,7 @@ export class GraphService {
|
||||
this.renderGraph.$id(currentFocusedProjectName).addClass('focused');
|
||||
}
|
||||
|
||||
this.renderGraph.on('zoom', () => {
|
||||
this.renderGraph.on('zoom pan', () => {
|
||||
this.tooltipService.hideAll();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
|
||||
/** Scrollbars **/
|
||||
.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
$gray: rgb(100, 116, 139, 1);
|
||||
|
||||
#app,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"packages": ["build/packages/*"],
|
||||
"version": "14.8.0",
|
||||
"version": "14.8.5",
|
||||
"granularPathspec": false,
|
||||
"command": {
|
||||
"publish": {
|
||||
|
||||
+17
-18
@@ -53,16 +53,16 @@
|
||||
"@ngrx/effects": "~14.0.0",
|
||||
"@ngrx/router-store": "~14.0.0",
|
||||
"@ngrx/store": "~14.0.0",
|
||||
"@nrwl/cypress": "14.7.16",
|
||||
"@nrwl/devkit": "14.7.16",
|
||||
"@nrwl/eslint-plugin-nx": "14.7.16",
|
||||
"@nrwl/jest": "14.7.16",
|
||||
"@nrwl/js": "14.7.16",
|
||||
"@nrwl/linter": "14.7.16",
|
||||
"@nrwl/next": "14.7.16",
|
||||
"@nrwl/nx-cloud": "14.6.2",
|
||||
"@nrwl/react": "14.7.16",
|
||||
"@nrwl/web": "14.7.16",
|
||||
"@nrwl/cypress": "14.8.1",
|
||||
"@nrwl/devkit": "14.8.1",
|
||||
"@nrwl/eslint-plugin-nx": "14.8.1",
|
||||
"@nrwl/jest": "14.8.1",
|
||||
"@nrwl/js": "14.8.1",
|
||||
"@nrwl/linter": "14.8.1",
|
||||
"@nrwl/next": "14.8.1",
|
||||
"@nrwl/nx-cloud": "14.7.0",
|
||||
"@nrwl/react": "14.8.1",
|
||||
"@nrwl/web": "14.8.1",
|
||||
"@parcel/watcher": "2.0.4",
|
||||
"@phenomnomnominal/tsquery": "4.1.1",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
|
||||
@@ -110,10 +110,10 @@
|
||||
"@types/tmp": "^0.2.0",
|
||||
"@types/yargs": "^17.0.10",
|
||||
"@types/yarnpkg__lockfile": "^1.1.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
||||
"@typescript-eslint/parser": "^5.36.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.38.1",
|
||||
"@typescript-eslint/parser": "5.38.1",
|
||||
"@typescript-eslint/type-utils": "^5.36.1",
|
||||
"@typescript-eslint/utils": "^5.36.1",
|
||||
"@typescript-eslint/utils": "5.38.1",
|
||||
"@xstate/immer": "^0.2.0",
|
||||
"@xstate/inspect": "^0.5.1",
|
||||
"@xstate/react": "^1.6.3",
|
||||
@@ -174,7 +174,7 @@
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"jsonc-parser": "3.2.0",
|
||||
"kill-port": "^1.6.1",
|
||||
"lerna": "5.0.0-alpha.2",
|
||||
"lerna": "5.5.4",
|
||||
"less": "3.12.2",
|
||||
"less-loader": "^10.1.0",
|
||||
"license-webpack-plugin": "^4.0.2",
|
||||
@@ -183,13 +183,12 @@
|
||||
"magic-string": "~0.26.2",
|
||||
"memfs": "^3.0.1",
|
||||
"metro-resolver": "^0.72.2",
|
||||
"mime": "2.4.4",
|
||||
"mini-css-extract-plugin": "~2.4.7",
|
||||
"minimatch": "3.0.5",
|
||||
"next-sitemap": "^3.1.10",
|
||||
"ng-packagr": "~14.2.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nx": "14.7.16",
|
||||
"nx": "14.8.1",
|
||||
"open": "^8.4.0",
|
||||
"parse-markdown-links": "^1.0.4",
|
||||
"parse5": "4.0.0",
|
||||
@@ -252,7 +251,7 @@
|
||||
"xstate": "^4.25.0",
|
||||
"yargs": "^17.4.0",
|
||||
"yargs-parser": "21.0.1",
|
||||
"@nrwl/storybook": "14.7.16"
|
||||
"@nrwl/storybook": "14.8.1"
|
||||
},
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
@@ -277,7 +276,7 @@
|
||||
"@yarnpkg/lockfile": "^1.1.0",
|
||||
"@yarnpkg/parsers": "^3.0.0-rc.18",
|
||||
"@zkochan/js-yaml": "0.0.6",
|
||||
"axios": "0.21.1",
|
||||
"axios": "1.0.0",
|
||||
"classnames": "^2.3.1",
|
||||
"cliui": "^7.0.2",
|
||||
"core-js": "^3.6.5",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { joinPathFragments, logger, Tree } from '@nrwl/devkit';
|
||||
import {
|
||||
formatFiles,
|
||||
joinPathFragments,
|
||||
logger,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nrwl/devkit';
|
||||
import type { Schema } from './schema';
|
||||
|
||||
import { readProjectConfiguration, formatFiles } from '@nrwl/devkit';
|
||||
import { getMFProjects } from '../../utils/get-mf-projects';
|
||||
import {
|
||||
checkOutputNameMatchesProjectName,
|
||||
@@ -12,7 +16,7 @@ import {
|
||||
writeNewWebpackConfig,
|
||||
} from './lib';
|
||||
|
||||
export default async function convertToWithMF(tree: Tree, schema: Schema) {
|
||||
export async function convertToWithMF(tree: Tree, schema: Schema) {
|
||||
const projects = new Set(getMFProjects(tree, { legacy: true }));
|
||||
|
||||
if (!projects.has(schema.project)) {
|
||||
@@ -56,3 +60,5 @@ export default async function convertToWithMF(tree: Tree, schema: Schema) {
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
export default convertToWithMF;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { addStandaloneRoute } from '../../utils/nx-devkit/standalone-utils';
|
||||
import { setupMf } from '../setup-mf/setup-mf';
|
||||
import { E2eTestRunner } from '../../utils/test-runners';
|
||||
|
||||
export default async function host(tree: Tree, options: Schema) {
|
||||
export async function host(tree: Tree, options: Schema) {
|
||||
const projects = getProjects(tree);
|
||||
|
||||
const remotesToGenerate: string[] = [];
|
||||
@@ -158,3 +158,5 @@ ${remoteRoutes}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default host;
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ Object {
|
||||
"affected": Object {
|
||||
"defaultBase": "main",
|
||||
},
|
||||
"defaultProject": "app1",
|
||||
"implicitDependencies": Object {
|
||||
".eslintrc.json": "*",
|
||||
"package.json": Object {
|
||||
|
||||
@@ -241,6 +241,11 @@ describe('workspace', () => {
|
||||
).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should set the default project correctly', async () => {
|
||||
await migrateFromAngularCli(tree, {});
|
||||
expect(readJson(tree, 'nx.json').defaultProject).toBe('myApp');
|
||||
});
|
||||
|
||||
it('should create nx.json', async () => {
|
||||
await migrateFromAngularCli(tree, { defaultBase: 'main' });
|
||||
expect(readJson(tree, 'nx.json')).toMatchSnapshot();
|
||||
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
formatFiles,
|
||||
installPackagesTask,
|
||||
readJson,
|
||||
readWorkspaceConfiguration,
|
||||
Tree,
|
||||
updateJson,
|
||||
updateWorkspaceConfiguration,
|
||||
} from '@nrwl/devkit';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import type { GeneratorOptions } from './schema';
|
||||
@@ -36,6 +38,8 @@ export async function migrateFromAngularCli(
|
||||
const projects = getAllProjects(tree);
|
||||
const options = normalizeOptions(tree, rawOptions, projects);
|
||||
|
||||
const defaultProject = projects.apps.find((app) => app.config.root === '');
|
||||
|
||||
if (options.preserveAngularCliLayout) {
|
||||
addDependenciesToPackageJson(
|
||||
tree,
|
||||
@@ -103,6 +107,14 @@ export async function migrateFromAngularCli(
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
if (defaultProject) {
|
||||
const workspaceConfig = readWorkspaceConfiguration(tree);
|
||||
updateWorkspaceConfiguration(tree, {
|
||||
...workspaceConfig,
|
||||
defaultProject: defaultProject.name,
|
||||
});
|
||||
}
|
||||
|
||||
if (!options.skipInstall) {
|
||||
return () => {
|
||||
installPackagesTask(tree);
|
||||
|
||||
@@ -28,7 +28,7 @@ function findNextAvailablePort(tree: Tree) {
|
||||
return nextAvailablePort;
|
||||
}
|
||||
|
||||
export default async function remote(tree: Tree, options: Schema) {
|
||||
export async function remote(tree: Tree, options: Schema) {
|
||||
const projects = getProjects(tree);
|
||||
if (options.host && !projects.has(options.host)) {
|
||||
throw new Error(
|
||||
@@ -152,3 +152,5 @@ export class AppModule {}`
|
||||
tree.write(pathToIndexHtml, newIndexContents);
|
||||
}
|
||||
}
|
||||
|
||||
export default remote;
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
readCachedProjectGraph,
|
||||
} from '@nrwl/devkit';
|
||||
import { readCachedProjectConfiguration } from 'nx/src/project-graph/project-graph';
|
||||
import { extname, join } from 'path';
|
||||
import { extname } from 'path';
|
||||
import {
|
||||
getNpmPackageSharedConfig,
|
||||
SharedLibraryConfig,
|
||||
@@ -59,7 +59,11 @@ function mapRemotes(remotes: MFRemotes) {
|
||||
const remoteLocationExt = extname(remoteLocation);
|
||||
mappedRemotes[remoteName] = ['.js', '.mjs'].includes(remoteLocationExt)
|
||||
? remoteLocation
|
||||
: join(remoteLocation, 'remoteEntry.mjs');
|
||||
: `${
|
||||
remoteLocation.endsWith('/')
|
||||
? remoteLocation.slice(0, -1)
|
||||
: remoteLocation
|
||||
}/remoteEntry.mjs`;
|
||||
} else if (typeof remote === 'string') {
|
||||
mappedRemotes[remote] = determineRemoteUrl(remote);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export function setupE2eProject(appName: string) {
|
||||
const data = fs.readFileSync(`apps/${appName}-e2e/project.json`);
|
||||
const json = JSON.parse(data.toString());
|
||||
json.targets.e2e = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
commands: [`nx e2e-serve ${appName}-e2e`, `nx e2e-run ${appName}-e2e`],
|
||||
},
|
||||
@@ -19,7 +19,7 @@ export function setupE2eProject(appName: string) {
|
||||
},
|
||||
};
|
||||
json.targets['e2e-serve'] = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
commands: [`nx serve ${appName}`],
|
||||
readyWhen: 'can now view',
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import axios from 'axios';
|
||||
import { isCI } from './output';
|
||||
|
||||
export class PromptMessages {
|
||||
private messages = {
|
||||
nxCloudCreation: [
|
||||
{
|
||||
code: 'set-up-distributed-caching-ci',
|
||||
message: `Enable distributed caching to make your CI faster`,
|
||||
},
|
||||
],
|
||||
nxCloudMigration: [
|
||||
{
|
||||
code: 'we-noticed',
|
||||
message: `We noticed you are migrating to a new major version, but are not taking advantage of Nx Cloud. Nx Cloud can make your CI up to 10 times faster. Learn more about it here: nx.app. Would you like to add it?`,
|
||||
},
|
||||
{
|
||||
code: 'not-leveraging-caching',
|
||||
message: `You're not leveraging distributed caching yet. Do you want to enable it and speed up your CI?`,
|
||||
},
|
||||
{
|
||||
code: 'make-ci-faster',
|
||||
message: `Enable distributed caching to make your CI faster?`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
private selectedMessages = {};
|
||||
|
||||
getPromptMessage(key: string): string {
|
||||
if (this.selectedMessages[key] === undefined) {
|
||||
if (process.env.NX_GENERATE_DOCS_PROCESS === 'true') {
|
||||
this.selectedMessages[key] = 0;
|
||||
} else {
|
||||
this.selectedMessages[key] = Math.floor(
|
||||
Math.random() * this.messages[key].length
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.messages[key][this.selectedMessages[key]].message;
|
||||
}
|
||||
|
||||
codeOfSelectedPromptMessage(key: string): string {
|
||||
if (this.selectedMessages[key] === undefined) return null;
|
||||
return this.messages[key][this.selectedMessages[key]].code;
|
||||
}
|
||||
}
|
||||
|
||||
export const messages = new PromptMessages();
|
||||
|
||||
/**
|
||||
* We are incrementing a counter to track how often create-nx-workspace is used in CI
|
||||
* vs dev environments. No personal information is collected.
|
||||
*/
|
||||
export async function recordStat(opts: {
|
||||
command: string;
|
||||
nxVersion: string;
|
||||
useCloud: boolean;
|
||||
meta: string;
|
||||
}) {
|
||||
try {
|
||||
const major = Number(opts.nxVersion.split('.')[0]);
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.log(`Record stat. Major: ${major}`);
|
||||
}
|
||||
if (major < 10 || major > 14) return; // test version, skip it
|
||||
await axios
|
||||
.create({
|
||||
baseURL: 'https://cloud.nx.app',
|
||||
timeout: 400,
|
||||
})
|
||||
.post('/nx-cloud/stats', {
|
||||
command: opts.command,
|
||||
isCI: isCI(),
|
||||
useCloud: opts.useCloud,
|
||||
meta: opts.meta,
|
||||
});
|
||||
} catch (e) {
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import * as path from 'path';
|
||||
import { dirSync } from 'tmp';
|
||||
import * as yargs from 'yargs';
|
||||
import { showNxWarning, unparse } from './shared';
|
||||
import { isCI, output } from './output';
|
||||
import { output } from './output';
|
||||
import * as ora from 'ora';
|
||||
import {
|
||||
detectInvokedPackageManager,
|
||||
@@ -23,7 +23,7 @@ import chalk = require('chalk');
|
||||
import { ciList } from './ci';
|
||||
import { join } from 'path';
|
||||
import { initializeGitRepo } from './git';
|
||||
import axios from 'axios';
|
||||
import { messages, recordStat } from './ab-testing';
|
||||
|
||||
type Arguments = {
|
||||
name: string;
|
||||
@@ -62,37 +62,6 @@ enum Preset {
|
||||
Express = 'express',
|
||||
}
|
||||
|
||||
class PromptMessages {
|
||||
private messages = {
|
||||
nxCloud: [
|
||||
{
|
||||
code: 'set-up-distributed-caching-ci',
|
||||
message: `Enable distributed caching to make your CI faster`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
private selectedMessages = {};
|
||||
|
||||
getPromptMessage(key: string): string {
|
||||
if (this.selectedMessages[key] === undefined) {
|
||||
if (process.env.NX_GENERATE_DOCS_PROCESS === 'true') {
|
||||
this.selectedMessages[key] = 0;
|
||||
} else {
|
||||
this.selectedMessages[key] = Math.floor(
|
||||
Math.random() * this.messages[key].length
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.messages[key][this.selectedMessages[key]].message;
|
||||
}
|
||||
|
||||
codeOfSelectedPromptMessage(key: string): string {
|
||||
if (this.selectedMessages[key] === undefined) return null;
|
||||
return this.messages[key][this.selectedMessages[key]].code;
|
||||
}
|
||||
}
|
||||
|
||||
const presetOptions: { name: Preset; message: string }[] = [
|
||||
{
|
||||
name: Preset.Apps,
|
||||
@@ -162,8 +131,6 @@ const nxVersion = require('../package.json').version;
|
||||
const tsVersion = 'TYPESCRIPT_VERSION'; // This gets replaced with the typescript version in the root package.json during build
|
||||
const prettierVersion = 'PRETTIER_VERSION'; // This gets replaced with the prettier version in the root package.json during build
|
||||
|
||||
const messages = new PromptMessages();
|
||||
|
||||
export const commandsObject: yargs.Argv<Arguments> = yargs
|
||||
.wrap(yargs.terminalWidth())
|
||||
.parserConfiguration({
|
||||
@@ -208,7 +175,7 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
|
||||
type: 'string',
|
||||
})
|
||||
.option('nxCloud', {
|
||||
describe: chalk.dim(messages.getPromptMessage('nxCloud')),
|
||||
describe: chalk.dim(messages.getPromptMessage('nxCloudCreation')),
|
||||
type: 'boolean',
|
||||
})
|
||||
.option('ci', {
|
||||
@@ -346,7 +313,12 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
|
||||
printNxCloudSuccessMessage(nxCloudInstallRes.stdout);
|
||||
}
|
||||
|
||||
await recordWorkspaceCreationStats(nxCloud);
|
||||
await recordStat({
|
||||
nxVersion,
|
||||
command: 'create-nx-workspace',
|
||||
useCloud: nxCloud,
|
||||
meta: messages.codeOfSelectedPromptMessage('nxCloudCreation'),
|
||||
});
|
||||
}
|
||||
|
||||
async function getConfiguration(
|
||||
@@ -718,7 +690,7 @@ async function determineNxCloud(
|
||||
.prompt([
|
||||
{
|
||||
name: 'NxCloud',
|
||||
message: messages.getPromptMessage('nxCloud'),
|
||||
message: messages.getPromptMessage('nxCloudCreation'),
|
||||
type: 'autocomplete',
|
||||
choices: [
|
||||
{
|
||||
@@ -1019,10 +991,7 @@ function pointToTutorialAndCourse(preset: Preset) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [
|
||||
`https://nx.dev/react-tutorial/01-create-application`,
|
||||
...pointToFreeCourseOnEgghead(),
|
||||
],
|
||||
bodyLines: [`https://nx.dev/react-tutorial/01-create-application`],
|
||||
});
|
||||
break;
|
||||
case Preset.Angular:
|
||||
@@ -1030,66 +999,15 @@ function pointToTutorialAndCourse(preset: Preset) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [
|
||||
`https://nx.dev/angular-tutorial/01-create-application`,
|
||||
...pointToFreeCourseOnYoutube(),
|
||||
],
|
||||
bodyLines: [`https://nx.dev/angular-tutorial/01-create-application`],
|
||||
});
|
||||
break;
|
||||
case Preset.Nest:
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [
|
||||
`https://nx.dev/node-tutorial/01-create-application`,
|
||||
...pointToFreeCourseOnYoutube(),
|
||||
],
|
||||
bodyLines: [`https://nx.dev/node-tutorial/01-create-application`],
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function pointToFreeCourseOnYoutube(): string[] {
|
||||
return [
|
||||
``,
|
||||
`Prefer watching videos? Check out this free Nx course on YouTube.`,
|
||||
`https://www.youtube.com/watch?v=2mYLe9Kp9VM&list=PLakNactNC1dH38AfqmwabvOszDmKriGco`,
|
||||
];
|
||||
}
|
||||
|
||||
function pointToFreeCourseOnEgghead(): string[] {
|
||||
return [
|
||||
``,
|
||||
`Prefer watching videos? Check out this free Nx course on Egghead.io.`,
|
||||
`https://egghead.io/playlists/scale-react-development-with-nx-4038`,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* We are incrementing a counter to track how often create-nx-workspace is used in CI
|
||||
* vs dev environments. No personal information is collected.
|
||||
*/
|
||||
async function recordWorkspaceCreationStats(useCloud: boolean) {
|
||||
try {
|
||||
const major = Number(nxVersion.split('.')[0]);
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.log(`Record stat. Major: ${major}`);
|
||||
}
|
||||
if (major < 10 || major > 14) return; // test version, skip it
|
||||
await axios
|
||||
.create({
|
||||
baseURL: 'https://cloud.nx.app',
|
||||
timeout: 400,
|
||||
})
|
||||
.post('/nx-cloud/stats', {
|
||||
command: 'create-nx-workspace',
|
||||
isCI: isCI(),
|
||||
useCloud,
|
||||
meta: messages.codeOfSelectedPromptMessage('nxCloud'),
|
||||
});
|
||||
} catch (e) {
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"tmp": "~0.2.1",
|
||||
"tslib": "^2.3.0",
|
||||
"yargs": "^17.4.0",
|
||||
"axios": "0.21.1"
|
||||
"axios": "^1.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dotenv/config';
|
||||
import * as chalk from 'chalk';
|
||||
import type { ExecutorContext } from '@nrwl/devkit';
|
||||
import { cacheDir, joinPathFragments, logger } from '@nrwl/devkit';
|
||||
import { parse } from 'path';
|
||||
import {
|
||||
copyAssets,
|
||||
copyPackageJson,
|
||||
@@ -15,10 +14,9 @@ import { normalizeOptions } from './lib/normalize';
|
||||
|
||||
import { EsBuildExecutorOptions } from './schema';
|
||||
import { removeSync, writeJsonSync } from 'fs-extra';
|
||||
import { getClientEnvironment } from '../../utils/environment-variables';
|
||||
import { createAsyncIterable } from '@nrwl/js/src/utils/create-async-iterable/create-async-iteratable';
|
||||
import { buildEsbuildOptions } from './lib/build-esbuild-options';
|
||||
|
||||
const ESM_FILE_EXTENSION = '.js';
|
||||
const CJS_FILE_EXTENSION = '.cjs';
|
||||
|
||||
const BUILD_WATCH_FAILED = `[ ${chalk.red(
|
||||
@@ -40,70 +38,64 @@ export async function* esbuildExecutor(
|
||||
const packageJsonResult = await copyPackageJson(
|
||||
{
|
||||
...options,
|
||||
skipTypings: options.skipTypeCheck,
|
||||
// TODO(jack): make types generate with esbuild
|
||||
skipTypings: true,
|
||||
outputFileExtensionForCjs: CJS_FILE_EXTENSION,
|
||||
},
|
||||
context
|
||||
);
|
||||
|
||||
const esbuildOptions: esbuild.BuildOptions = {
|
||||
...options.esbuildOptions,
|
||||
entryPoints: [options.main, ...options.additionalEntryPoints],
|
||||
entryNames:
|
||||
options.outputHashing === 'all' ? '[dir]/[name].[hash]' : '[dir]/[name]',
|
||||
outdir: options.outputPath,
|
||||
bundle: true, // TODO(jack): support non-bundled builds
|
||||
define: getClientEnvironment(),
|
||||
external: options.external,
|
||||
minify: options.minify,
|
||||
platform: options.platform,
|
||||
target: options.target,
|
||||
metafile: options.metafile,
|
||||
tsconfig: options.tsConfig,
|
||||
};
|
||||
|
||||
if (options.watch) {
|
||||
return yield* createAsyncIterable<{ success: boolean; outfile: string }>(
|
||||
return yield* createAsyncIterable<{ success: boolean; outfile?: string }>(
|
||||
async ({ next, done }) => {
|
||||
let hasTypeErrors = false;
|
||||
|
||||
const results = await Promise.all(
|
||||
options.format.map((format, idx) => {
|
||||
const outfile = getOutfile(format, options, context);
|
||||
return esbuild.build({
|
||||
...esbuildOptions,
|
||||
watch:
|
||||
// Only emit info on one of the watch processes.
|
||||
idx === 0
|
||||
? {
|
||||
onRebuild: async (
|
||||
error: esbuild.BuildFailure,
|
||||
result: esbuild.BuildResult
|
||||
) => {
|
||||
if (!options.skipTypeCheck) {
|
||||
const { errors } = await runTypeCheck(
|
||||
options,
|
||||
context
|
||||
);
|
||||
hasTypeErrors = errors.length > 0;
|
||||
}
|
||||
const success = !error && !hasTypeErrors;
|
||||
|
||||
if (!success) {
|
||||
logger.info(BUILD_WATCH_FAILED);
|
||||
} else {
|
||||
logger.info(BUILD_WATCH_SUCCEEDED);
|
||||
}
|
||||
|
||||
next({ success: !!error && !hasTypeErrors, outfile });
|
||||
},
|
||||
}
|
||||
: true,
|
||||
options.format.map(async (format, idx) => {
|
||||
const esbuildOptions = buildEsbuildOptions(
|
||||
format,
|
||||
outExtension: {
|
||||
'.js': getOutExtension(format),
|
||||
},
|
||||
});
|
||||
options,
|
||||
context
|
||||
);
|
||||
const watch =
|
||||
// Only emit info on one of the watch processes.
|
||||
idx === 0
|
||||
? {
|
||||
onRebuild: async (
|
||||
error: esbuild.BuildFailure,
|
||||
result: esbuild.BuildResult
|
||||
) => {
|
||||
if (!options.skipTypeCheck) {
|
||||
const { errors } = await runTypeCheck(options, context);
|
||||
hasTypeErrors = errors.length > 0;
|
||||
}
|
||||
const success = !error && !hasTypeErrors;
|
||||
|
||||
if (!success) {
|
||||
logger.info(BUILD_WATCH_FAILED);
|
||||
} else {
|
||||
logger.info(BUILD_WATCH_SUCCEEDED);
|
||||
}
|
||||
|
||||
next({
|
||||
success: !!error && !hasTypeErrors,
|
||||
outfile: esbuildOptions.outfile,
|
||||
});
|
||||
},
|
||||
}
|
||||
: true;
|
||||
try {
|
||||
const result = await esbuild.build({ ...esbuildOptions, watch });
|
||||
|
||||
next({
|
||||
success: true,
|
||||
outfile: esbuildOptions.outfile,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch {
|
||||
next({ success: false });
|
||||
}
|
||||
})
|
||||
);
|
||||
const processOnExit = () => {
|
||||
@@ -133,23 +125,12 @@ export async function* esbuildExecutor(
|
||||
} else {
|
||||
logger.info(BUILD_WATCH_SUCCEEDED);
|
||||
}
|
||||
|
||||
next({
|
||||
success,
|
||||
outfile: getOutfile(options.format[0], options, context),
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const buildResults = await Promise.all(
|
||||
options.format.map((format) =>
|
||||
esbuild.build({
|
||||
...esbuildOptions,
|
||||
format,
|
||||
outExtension: {
|
||||
'.js': getOutExtension(format),
|
||||
},
|
||||
})
|
||||
esbuild.build(buildEsbuildOptions(format, options, context))
|
||||
)
|
||||
);
|
||||
const buildSuccess = buildResults.every((r) => r.errors?.length === 0);
|
||||
@@ -200,24 +181,6 @@ function getTypeCheckOptions(
|
||||
return typeCheckOptions;
|
||||
}
|
||||
|
||||
function getOutExtension(format: 'cjs' | 'esm') {
|
||||
return format === 'esm' ? ESM_FILE_EXTENSION : CJS_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
function getOutfile(
|
||||
format: 'cjs' | 'esm',
|
||||
options: EsBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const candidate = joinPathFragments(
|
||||
context.target.options.outputPath,
|
||||
options.outputFileName
|
||||
);
|
||||
const ext = getOutExtension(format);
|
||||
const { dir, name } = parse(candidate);
|
||||
return `${dir}/${name}${ext}`;
|
||||
}
|
||||
|
||||
async function runTypeCheck(
|
||||
options: EsBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import { buildEsbuildOptions } from './build-esbuild-options';
|
||||
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
|
||||
|
||||
describe('buildEsbuildOptions', () => {
|
||||
const context: ExecutorContext = {
|
||||
workspace: {
|
||||
version: 2,
|
||||
projects: {
|
||||
myapp: {
|
||||
root: 'apps/myapp',
|
||||
},
|
||||
},
|
||||
},
|
||||
isVerbose: false,
|
||||
root: '/',
|
||||
cwd: '/',
|
||||
target: {
|
||||
executor: '@nrwl/esbuild:esbuild',
|
||||
options: {
|
||||
outputPath: 'dist/apps/myapp',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('should include environment variables for platform === browser', () => {
|
||||
expect(
|
||||
buildEsbuildOptions(
|
||||
'esm',
|
||||
{
|
||||
platform: 'browser',
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'index.js',
|
||||
singleEntry: true,
|
||||
},
|
||||
context
|
||||
)
|
||||
).toEqual({
|
||||
bundle: true,
|
||||
define: expect.objectContaining({
|
||||
'process.env.NODE_ENV': '"test"',
|
||||
}),
|
||||
entryNames: '[dir]/[name]',
|
||||
entryPoints: ['apps/myapp/src/index.ts'],
|
||||
format: 'esm',
|
||||
platform: 'browser',
|
||||
outfile: 'dist/apps/myapp/index.js',
|
||||
tsconfig: 'apps/myapp/tsconfig.app.json',
|
||||
outExtension: {
|
||||
'.js': '.js',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should support multiple entry points', () => {
|
||||
expect(
|
||||
buildEsbuildOptions(
|
||||
'esm',
|
||||
{
|
||||
platform: 'browser',
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
additionalEntryPoints: ['apps/myapp/src/extra-entry.ts'],
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'index.js',
|
||||
singleEntry: false,
|
||||
},
|
||||
context
|
||||
)
|
||||
).toEqual({
|
||||
bundle: true,
|
||||
define: expect.objectContaining({
|
||||
'process.env.NODE_ENV': '"test"',
|
||||
}),
|
||||
entryNames: '[dir]/[name]',
|
||||
entryPoints: ['apps/myapp/src/index.ts', 'apps/myapp/src/extra-entry.ts'],
|
||||
format: 'esm',
|
||||
platform: 'browser',
|
||||
outdir: 'dist/apps/myapp',
|
||||
tsconfig: 'apps/myapp/tsconfig.app.json',
|
||||
outExtension: {
|
||||
'.js': '.js',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should support cjs format', () => {
|
||||
expect(
|
||||
buildEsbuildOptions(
|
||||
'cjs',
|
||||
{
|
||||
platform: 'browser',
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'index.js',
|
||||
singleEntry: true,
|
||||
},
|
||||
context
|
||||
)
|
||||
).toEqual({
|
||||
bundle: true,
|
||||
define: expect.objectContaining({
|
||||
'process.env.NODE_ENV': '"test"',
|
||||
}),
|
||||
entryNames: '[dir]/[name]',
|
||||
entryPoints: ['apps/myapp/src/index.ts'],
|
||||
format: 'cjs',
|
||||
platform: 'browser',
|
||||
outfile: 'dist/apps/myapp/index.cjs',
|
||||
tsconfig: 'apps/myapp/tsconfig.app.json',
|
||||
outExtension: {
|
||||
'.js': '.cjs',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not define environment variables for node', () => {
|
||||
expect(
|
||||
buildEsbuildOptions(
|
||||
'cjs',
|
||||
{
|
||||
platform: 'node',
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'index.js',
|
||||
singleEntry: true,
|
||||
},
|
||||
context
|
||||
)
|
||||
).toEqual({
|
||||
bundle: true,
|
||||
entryNames: '[dir]/[name]',
|
||||
entryPoints: ['apps/myapp/src/index.ts'],
|
||||
format: 'cjs',
|
||||
platform: 'node',
|
||||
outfile: 'dist/apps/myapp/index.cjs',
|
||||
tsconfig: 'apps/myapp/tsconfig.app.json',
|
||||
outExtension: {
|
||||
'.js': '.cjs',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
import { getClientEnvironment } from '../../../utils/environment-variables';
|
||||
import {
|
||||
EsBuildExecutorOptions,
|
||||
NormalizedEsBuildExecutorOptions,
|
||||
} from '../schema';
|
||||
import { ExecutorContext } from 'nx/src/config/misc-interfaces';
|
||||
import { joinPathFragments } from 'nx/src/utils/path';
|
||||
import { parse } from 'path';
|
||||
|
||||
const ESM_FILE_EXTENSION = '.js';
|
||||
const CJS_FILE_EXTENSION = '.cjs';
|
||||
|
||||
export function buildEsbuildOptions(
|
||||
format: 'cjs' | 'esm',
|
||||
options: NormalizedEsBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
): esbuild.BuildOptions {
|
||||
const esbuildOptions: esbuild.BuildOptions = {
|
||||
...options.esbuildOptions,
|
||||
entryPoints: options.additionalEntryPoints
|
||||
? [options.main, ...options.additionalEntryPoints]
|
||||
: [options.main],
|
||||
entryNames:
|
||||
options.outputHashing === 'all' ? '[dir]/[name].[hash]' : '[dir]/[name]',
|
||||
bundle: true, // TODO(jack): support non-bundled builds
|
||||
external: options.external,
|
||||
minify: options.minify,
|
||||
platform: options.platform,
|
||||
target: options.target,
|
||||
metafile: options.metafile,
|
||||
tsconfig: options.tsConfig,
|
||||
format,
|
||||
outExtension: { '.js': getOutExtension(format) },
|
||||
};
|
||||
|
||||
if (options.platform === 'browser') {
|
||||
esbuildOptions.define = getClientEnvironment();
|
||||
}
|
||||
|
||||
if (options.singleEntry) {
|
||||
esbuildOptions.outfile = getOutfile(format, options, context);
|
||||
} else {
|
||||
esbuildOptions.outdir = options.outputPath;
|
||||
}
|
||||
|
||||
return esbuildOptions;
|
||||
}
|
||||
|
||||
function getOutExtension(format: 'cjs' | 'esm') {
|
||||
return format === 'esm' ? ESM_FILE_EXTENSION : CJS_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
function getOutfile(
|
||||
format: 'cjs' | 'esm',
|
||||
options: EsBuildExecutorOptions,
|
||||
context: ExecutorContext
|
||||
) {
|
||||
const candidate = joinPathFragments(
|
||||
context.target.options.outputPath,
|
||||
options.outputFileName
|
||||
);
|
||||
const ext = getOutExtension(format);
|
||||
const { dir, name } = parse(candidate);
|
||||
return `${dir}/${name}${ext}`;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { normalizeOptions } from './normalize';
|
||||
|
||||
describe('normalizeOptions', () => {
|
||||
it('should handle single entry point options', () => {
|
||||
expect(
|
||||
normalizeOptions({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
})
|
||||
).toEqual({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'index.js',
|
||||
singleEntry: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple entry point options', () => {
|
||||
expect(
|
||||
normalizeOptions({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
additionalEntryPoints: ['apps/myapp/src/extra-entry.ts'],
|
||||
})
|
||||
).toEqual({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
additionalEntryPoints: ['apps/myapp/src/extra-entry.ts'],
|
||||
singleEntry: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should support custom output file name', () => {
|
||||
expect(
|
||||
normalizeOptions({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'test.js',
|
||||
})
|
||||
).toEqual({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
outputFileName: 'test.js',
|
||||
singleEntry: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate against multiple entry points + outputFileName', () => {
|
||||
expect(() =>
|
||||
normalizeOptions({
|
||||
main: 'apps/myapp/src/index.ts',
|
||||
outputPath: 'dist/apps/myapp',
|
||||
tsConfig: 'apps/myapp/tsconfig.app.json',
|
||||
project: 'apps/myapp/package.json',
|
||||
assets: [],
|
||||
additionalEntryPoints: ['apps/myapp/src/extra-entry.ts'],
|
||||
outputFileName: 'test.js',
|
||||
})
|
||||
).toThrow(/Cannot use/);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,29 @@
|
||||
import { EsBuildExecutorOptions } from '../schema';
|
||||
import { parse } from 'path';
|
||||
import {
|
||||
EsBuildExecutorOptions,
|
||||
NormalizedEsBuildExecutorOptions,
|
||||
} from '../schema';
|
||||
|
||||
export function normalizeOptions(
|
||||
options: EsBuildExecutorOptions
|
||||
): EsBuildExecutorOptions {
|
||||
return {
|
||||
...options,
|
||||
outputFileName: options.outputFileName ?? 'main.js',
|
||||
};
|
||||
): NormalizedEsBuildExecutorOptions {
|
||||
if (options.additionalEntryPoints?.length > 0) {
|
||||
const { outputFileName, ...rest } = options;
|
||||
if (outputFileName) {
|
||||
throw new Error(
|
||||
`Cannot use outputFileName and additionalEntry points together. Please remove outputFileName and try again.`
|
||||
);
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
singleEntry: false,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...options,
|
||||
singleEntry: true,
|
||||
outputFileName:
|
||||
options.outputFileName ?? `${parse(options.main).name}.js`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +24,8 @@ export interface EsBuildExecutorOptions {
|
||||
updateBuildableProjectDepsInPackageJson?: boolean;
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
export interface NormalizedEsBuildExecutorOptions
|
||||
extends EsBuildExecutorOptions {
|
||||
singleEntry: boolean;
|
||||
}
|
||||
|
||||
@@ -151,6 +151,13 @@
|
||||
"output": {
|
||||
"type": "string",
|
||||
"description": "Relative path within the output folder."
|
||||
},
|
||||
"ignore": {
|
||||
"description": "An array of globs to ignore.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/expo/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
@@ -76,16 +77,6 @@
|
||||
"options": {
|
||||
"command": "node ./scripts/copy-readme.js expo"
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
"executor": "@nrwl/workspace:run-commands",
|
||||
"options": {
|
||||
"parallel": false,
|
||||
"commands": [
|
||||
"nx build expo",
|
||||
"node tools/scripts/publish.mjs expo {args.ver} {args.tag}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
||||
@@ -52,7 +52,7 @@ function getTargets(options: NormalizedSchema) {
|
||||
};
|
||||
|
||||
architect.serve = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: `nx start ${options.name}`,
|
||||
},
|
||||
|
||||
@@ -842,7 +842,7 @@ describe('lib', () => {
|
||||
|
||||
const config = readProjectConfiguration(tree, 'my-lib');
|
||||
expect(config.targets.publish).toEqual({
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command:
|
||||
'node tools/scripts/publish.mjs my-lib {args.ver} {args.tag}',
|
||||
|
||||
@@ -132,7 +132,7 @@ function addProject(
|
||||
const publishScriptPath = addMinimalPublishScript(tree);
|
||||
|
||||
projectConfiguration.targets.publish = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: `node ${publishScriptPath} ${options.name} {args.ver} {args.tag}`,
|
||||
},
|
||||
|
||||
@@ -36,7 +36,9 @@ export const defaultFileEventHandler = (events: FileEvent[]) => {
|
||||
dirs.forEach((d) => fse.ensureDirSync(d));
|
||||
events.forEach((event) => {
|
||||
if (event.type === 'create' || event.type === 'update') {
|
||||
fse.copyFileSync(event.src, event.dest);
|
||||
if (fse.lstatSync(event.src).isFile()) {
|
||||
fse.copyFileSync(event.src, event.dest);
|
||||
}
|
||||
} else if (event.type === 'delete') {
|
||||
fse.removeSync(event.dest);
|
||||
} else {
|
||||
|
||||
@@ -145,4 +145,25 @@ describe('getUpdatedPackageJsonContent', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set types when { skipTypings: true }', () => {
|
||||
const json = getUpdatedPackageJsonContent(
|
||||
{
|
||||
name: 'test',
|
||||
version: '0.0.1',
|
||||
},
|
||||
{
|
||||
main: 'proj/src/index.ts',
|
||||
outputPath: 'dist/proj',
|
||||
projectRoot: 'proj',
|
||||
skipTypings: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(json).toEqual({
|
||||
name: 'test',
|
||||
main: './src/index.js',
|
||||
version: '0.0.1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { daemonClient } from '../src/daemon/client/client';
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
if (fileExists(join(workspaceRoot, 'nx.json'))) {
|
||||
if (isMainNxPackage() && fileExists(join(workspaceRoot, 'nx.json'))) {
|
||||
try {
|
||||
await daemonClient.stop();
|
||||
} catch (e) {}
|
||||
@@ -27,3 +27,11 @@ import { daemonClient } from '../src/daemon/client/client';
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
function isMainNxPackage() {
|
||||
const mainNxPath = require.resolve('nx', {
|
||||
paths: [workspaceRoot],
|
||||
});
|
||||
const thisNxPath = require.resolve('nx');
|
||||
return mainNxPath === thisNxPath;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,6 @@ function shouldDelegateToAngularCLI() {
|
||||
const commands = [
|
||||
'add',
|
||||
'analytics',
|
||||
'deploy',
|
||||
'config',
|
||||
'doc',
|
||||
'update',
|
||||
|
||||
@@ -63,7 +63,8 @@
|
||||
"v8-compile-cache": "2.3.0",
|
||||
"yargs": "^17.4.0",
|
||||
"yargs-parser": "21.0.1",
|
||||
"js-yaml": "4.1.0"
|
||||
"js-yaml": "4.1.0",
|
||||
"axios": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc-node/register": "^1.4.2",
|
||||
@@ -80,31 +81,34 @@
|
||||
"nx-migrations": {
|
||||
"migrations": "./migrations.json",
|
||||
"packageGroup": [
|
||||
"@nrwl/workspace",
|
||||
"@nrwl/angular",
|
||||
"@nrwl/cli",
|
||||
"@nrwl/cypress",
|
||||
"@nrwl/detox",
|
||||
"@nrwl/devkit",
|
||||
"@nrwl/esbuild",
|
||||
"@nrwl/eslint-plugin-nx",
|
||||
"@nrwl/expo",
|
||||
"@nrwl/express",
|
||||
"@nrwl/jest",
|
||||
"@nrwl/js",
|
||||
"@nrwl/linter",
|
||||
"@nrwl/nest",
|
||||
"@nrwl/next",
|
||||
"@nrwl/node",
|
||||
"@nrwl/nx-plugin",
|
||||
"@nrwl/react",
|
||||
"@nrwl/react-native",
|
||||
"@nrwl/rollup",
|
||||
"@nrwl/storybook",
|
||||
"@nrwl/web",
|
||||
"@nrwl/js",
|
||||
"@nrwl/cli",
|
||||
"@nrwl/tao",
|
||||
"@nrwl/web",
|
||||
"@nrwl/webpack",
|
||||
"@nrwl/workspace",
|
||||
{
|
||||
"package": "@nrwl/nx-cloud",
|
||||
"version": "latest"
|
||||
},
|
||||
"@nrwl/react-native",
|
||||
"@nrwl/detox",
|
||||
"@nrwl/expo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"executors": "./executors.json",
|
||||
|
||||
@@ -130,6 +130,7 @@ export async function affected(
|
||||
break;
|
||||
}
|
||||
}
|
||||
await output.drain();
|
||||
} catch (e) {
|
||||
printError(e, args.verbose);
|
||||
process.exit(1);
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function connectToNxCloudIfExplicitlyAsked(opts: {
|
||||
|
||||
export async function connectToNxCloudCommand(
|
||||
promptOverride?: string
|
||||
): Promise<void> {
|
||||
): Promise<boolean> {
|
||||
const nxJson = readNxJson();
|
||||
const nxCloudUsed = Object.values(nxJson.tasksRunnerOptions).find(
|
||||
(r) => r.runner == '@nrwl/nx-cloud'
|
||||
@@ -38,16 +38,17 @@ export async function connectToNxCloudCommand(
|
||||
output.log({
|
||||
title: 'This workspace is already connected to Nx Cloud.',
|
||||
});
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const res = await connectToNxCloudPrompt(promptOverride);
|
||||
if (!res) return;
|
||||
if (!res) return false;
|
||||
const pmc = getPackageManagerCommand();
|
||||
execSync(`${pmc.addDev} @nrwl/nx-cloud@latest`);
|
||||
execSync(`${pmc.exec} nx g @nrwl/nx-cloud:init`, {
|
||||
stdio: [0, 1, 2],
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async function connectToNxCloudPrompt(prompt?: string) {
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
import { handleErrors } from '../utils/params';
|
||||
import { connectToNxCloudCommand } from './connect-to-nx-cloud';
|
||||
import { output } from '../utils/output';
|
||||
import { messages, recordStat } from 'nx/src/utils/ab-testing';
|
||||
import { nxVersion } from '../utils/versions';
|
||||
|
||||
export interface ResolvedMigrationConfiguration extends MigrationsJson {
|
||||
packageGroup?: NxMigrationsConfiguration['packageGroup'];
|
||||
@@ -815,9 +817,15 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
||||
opts.targetVersion
|
||||
))
|
||||
) {
|
||||
await connectToNxCloudCommand(
|
||||
'We noticed you are migrating to a new major version, but are not taking advantage of Nx Cloud. Nx Cloud can make your CI up to 10 times faster. Learn more about it here: nx.app. Would you like to add it?'
|
||||
const useCloud = await connectToNxCloudCommand(
|
||||
messages.getPromptMessage('nxCloudMigration')
|
||||
);
|
||||
await recordStat({
|
||||
command: 'migrate',
|
||||
nxVersion,
|
||||
useCloud,
|
||||
meta: messages.codeOfSelectedPromptMessage('nxCloudMigration'),
|
||||
});
|
||||
originalPackageJson = readJsonFile<PackageJson>(
|
||||
join(root, 'package.json')
|
||||
);
|
||||
|
||||
@@ -256,15 +256,10 @@ export const commandsObject = yargs
|
||||
aliases: ['workspace-schematic [name]'],
|
||||
builder: async (yargs) =>
|
||||
linkToNxDevAndExamples(
|
||||
await withWorkspaceGeneratorOptions(yargs),
|
||||
await withWorkspaceGeneratorOptions(yargs, process.argv.slice(3)),
|
||||
'workspace-generator'
|
||||
),
|
||||
handler: async () => {
|
||||
await (
|
||||
await import('./workspace-generators')
|
||||
).workspaceGenerators(process.argv.slice(3));
|
||||
process.exit(0);
|
||||
},
|
||||
handler: workspaceGeneratorHandler,
|
||||
})
|
||||
.command({
|
||||
command: 'migrate [packageAndVersion]',
|
||||
@@ -763,27 +758,130 @@ function withRunOneOptions(yargs: yargs.Argv) {
|
||||
}
|
||||
}
|
||||
|
||||
async function withWorkspaceGeneratorOptions(yargs: yargs.Argv) {
|
||||
yargs
|
||||
.option('list-generators', {
|
||||
describe: 'List the available workspace-generators',
|
||||
type: 'boolean',
|
||||
})
|
||||
.positional('name', {
|
||||
type: 'string',
|
||||
describe: 'The name of your generator',
|
||||
});
|
||||
type WorkspaceGeneratorProperties = {
|
||||
[name: string]:
|
||||
| {
|
||||
type: yargs.Options['type'];
|
||||
description?: string;
|
||||
default?: any;
|
||||
enum?: yargs.Options['type'][];
|
||||
}
|
||||
| {
|
||||
type: yargs.PositionalOptionsType;
|
||||
description?: string;
|
||||
default?: any;
|
||||
enum?: yargs.PositionalOptionsType[];
|
||||
$default: {
|
||||
$source: 'argv';
|
||||
index: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Don't require `name` if only listing available
|
||||
* schematics
|
||||
*/
|
||||
if ((await yargs.argv).listGenerators !== true) {
|
||||
yargs.demandOption('name');
|
||||
function isPositionalProperty(
|
||||
property: WorkspaceGeneratorProperties[keyof WorkspaceGeneratorProperties]
|
||||
): property is { type: yargs.PositionalOptionsType } {
|
||||
return property['$default']?.['$source'] === 'argv';
|
||||
}
|
||||
|
||||
async function withWorkspaceGeneratorOptions(
|
||||
yargs: yargs.Argv,
|
||||
args: string[]
|
||||
) {
|
||||
// filter out only positional arguments
|
||||
args = args.filter((a) => !a.startsWith('-'));
|
||||
if (args.length) {
|
||||
// this is an actual workspace generator
|
||||
return withCustomGeneratorOptions(yargs, args[0]);
|
||||
} else {
|
||||
yargs
|
||||
.option('list-generators', {
|
||||
describe: 'List the available workspace-generators',
|
||||
type: 'boolean',
|
||||
})
|
||||
.positional('name', {
|
||||
type: 'string',
|
||||
describe: 'The name of your generator',
|
||||
});
|
||||
/**
|
||||
* Don't require `name` if only listing available
|
||||
* schematics
|
||||
*/
|
||||
if ((await yargs.argv).listGenerators !== true) {
|
||||
yargs.demandOption('name');
|
||||
}
|
||||
return yargs;
|
||||
}
|
||||
}
|
||||
|
||||
async function withCustomGeneratorOptions(
|
||||
yargs: yargs.Argv,
|
||||
generatorName: string
|
||||
) {
|
||||
const schema = (
|
||||
await import('./workspace-generators')
|
||||
).workspaceGeneratorSchema(generatorName);
|
||||
const options = [];
|
||||
const positionals = [];
|
||||
|
||||
Object.entries(schema.properties as WorkspaceGeneratorProperties).forEach(
|
||||
([name, prop]) => {
|
||||
options.push({
|
||||
name,
|
||||
definition: {
|
||||
describe: prop.description,
|
||||
type: prop.type,
|
||||
default: prop.default,
|
||||
choices: prop.enum,
|
||||
},
|
||||
});
|
||||
if (isPositionalProperty(prop)) {
|
||||
positionals.push({
|
||||
name,
|
||||
definition: {
|
||||
describe: prop.description,
|
||||
type: prop.type,
|
||||
choices: prop.enum,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let command = generatorName;
|
||||
positionals.forEach(({ name }) => {
|
||||
command += ` [${name}]`;
|
||||
});
|
||||
if (options.length) {
|
||||
command += ' (options)';
|
||||
}
|
||||
|
||||
yargs.command({
|
||||
// this is the default and only command
|
||||
command,
|
||||
describe: schema.description || '',
|
||||
builder: (y) => {
|
||||
options.forEach(({ name, definition }) => {
|
||||
y.option(name, definition);
|
||||
});
|
||||
positionals.forEach(({ name, definition }) => {
|
||||
y.positional(name, definition);
|
||||
});
|
||||
return linkToNxDevAndExamples(y, 'workspace-generator');
|
||||
},
|
||||
handler: workspaceGeneratorHandler,
|
||||
});
|
||||
|
||||
return yargs;
|
||||
}
|
||||
|
||||
async function workspaceGeneratorHandler() {
|
||||
await (
|
||||
await import('./workspace-generators')
|
||||
).workspaceGenerators(process.argv.slice(3));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function withMigrationOptions(yargs: yargs.Argv) {
|
||||
const defaultCommitPrefix = 'chore: [nx migration] ';
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ export const packagesWeCareAbout = [
|
||||
'@nrwl/cypress',
|
||||
'@nrwl/detox',
|
||||
'@nrwl/devkit',
|
||||
'@nrwl/esbuild',
|
||||
'@nrwl/eslint-plugin-nx',
|
||||
'@nrwl/expo',
|
||||
'@nrwl/express',
|
||||
@@ -33,9 +34,11 @@ export const packagesWeCareAbout = [
|
||||
'@nrwl/nx-plugin',
|
||||
'@nrwl/react',
|
||||
'@nrwl/react-native',
|
||||
'@nrwl/rollup',
|
||||
'@nrwl/schematics',
|
||||
'@nrwl/storybook',
|
||||
'@nrwl/web',
|
||||
'@nrwl/webpack',
|
||||
'@nrwl/workspace',
|
||||
'typescript',
|
||||
];
|
||||
|
||||
@@ -40,6 +40,17 @@ export async function workspaceGenerators(args: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
export function workspaceGeneratorSchema(name: string) {
|
||||
const schemaFile = path.join(generatorsDir, name, 'schema.json');
|
||||
|
||||
if (fileExists(schemaFile)) {
|
||||
return readJsonFile(schemaFile);
|
||||
} else {
|
||||
logger.error(`Cannot find schema for ${name}. Does the generator exist?`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// compile tools
|
||||
function compileTools() {
|
||||
const toolsOutDir = getToolsOutDir();
|
||||
|
||||
@@ -725,22 +725,23 @@ function buildProjectConfigurationFromPackageJson(
|
||||
): ProjectConfiguration & { name: string } {
|
||||
const directory = dirname(path).split('\\').join('/');
|
||||
let name = packageJson.name ?? toProjectName(directory, nxJson);
|
||||
if (nxJson.npmScope) {
|
||||
if (nxJson?.npmScope) {
|
||||
const npmPrefix = `@${nxJson.npmScope}/`;
|
||||
if (name.startsWith(npmPrefix)) {
|
||||
name = name.replace(npmPrefix, '');
|
||||
}
|
||||
}
|
||||
const projectType =
|
||||
nxJson?.workspaceLayout?.appsDir != nxJson?.workspaceLayout?.libsDir &&
|
||||
nxJson?.workspaceLayout?.appsDir &&
|
||||
directory.startsWith(nxJson.workspaceLayout.appsDir)
|
||||
? 'application'
|
||||
: 'library';
|
||||
return {
|
||||
root: directory,
|
||||
sourceRoot: directory,
|
||||
name,
|
||||
projectType:
|
||||
nxJson.workspaceLayout?.appsDir != nxJson.workspaceLayout?.libsDir &&
|
||||
nxJson.workspaceLayout?.appsDir &&
|
||||
directory.startsWith(nxJson.workspaceLayout.appsDir)
|
||||
? 'application'
|
||||
: 'library',
|
||||
projectType,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -161,11 +161,19 @@ export class DaemonClient {
|
||||
});
|
||||
|
||||
this.socket.on('close', () => {
|
||||
output.error({
|
||||
title: 'Daemon process terminated and closed the connection',
|
||||
bodyLines: ['Please rerun the command, which will restart the daemon.'],
|
||||
});
|
||||
process.exit(1);
|
||||
// it's ok for the daemon to terminate if the client doesn't wait on
|
||||
// any messages from the daemon
|
||||
if (this.queue.isEmpty()) {
|
||||
this._connected = false;
|
||||
} else {
|
||||
output.error({
|
||||
title: 'Daemon process terminated and closed the connection',
|
||||
bodyLines: [
|
||||
'Please rerun the command, which will restart the daemon.',
|
||||
],
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('error', (err) => {
|
||||
|
||||
@@ -51,8 +51,13 @@ export type HandlerResult = {
|
||||
response?: string;
|
||||
};
|
||||
|
||||
let numberOfOpenConnections = 0;
|
||||
|
||||
const server = createServer(async (socket) => {
|
||||
serverLogger.log('Established a connection');
|
||||
numberOfOpenConnections += 1;
|
||||
serverLogger.log(
|
||||
`Established a connection. Number of open connections: ${numberOfOpenConnections}`
|
||||
);
|
||||
resetInactivityTimeout(handleInactivityTimeout);
|
||||
if (!performanceObserver) {
|
||||
performanceObserver = new PerformanceObserver((list) => {
|
||||
@@ -75,7 +80,10 @@ const server = createServer(async (socket) => {
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
serverLogger.log('Closed a connection');
|
||||
numberOfOpenConnections -= 1;
|
||||
serverLogger.log(
|
||||
`Closed a connection. Number of open connections: ${numberOfOpenConnections}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,10 +148,17 @@ async function handleResult(socket: Socket, hr: HandlerResult) {
|
||||
}
|
||||
|
||||
function handleInactivityTimeout() {
|
||||
handleServerProcessTermination({
|
||||
server,
|
||||
reason: `${SERVER_INACTIVITY_TIMEOUT_MS}ms of inactivity`,
|
||||
});
|
||||
if (numberOfOpenConnections > 0) {
|
||||
serverLogger.log(
|
||||
`There are ${numberOfOpenConnections} open connections. Reset inactivity timer.`
|
||||
);
|
||||
resetInactivityTimeout(handleInactivityTimeout);
|
||||
} else {
|
||||
handleServerProcessTermination({
|
||||
server,
|
||||
reason: `${SERVER_INACTIVITY_TIMEOUT_MS}ms of inactivity`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
process
|
||||
|
||||
@@ -2,6 +2,7 @@ import Ajv from 'ajv';
|
||||
import { Tree } from '../tree';
|
||||
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||
|
||||
import { createTree } from '../testing-utils/create-tree';
|
||||
import {
|
||||
createTreeWithEmptyWorkspace,
|
||||
createTreeWithEmptyV1Workspace,
|
||||
@@ -432,6 +433,23 @@ describe('project configuration', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProjects', () => {
|
||||
it('should get a map of projects', () => {
|
||||
addProjectConfiguration(tree, 'proj', {
|
||||
root: 'proj',
|
||||
});
|
||||
|
||||
const projects = getProjects(tree);
|
||||
|
||||
expect(projects.size).toEqual(1);
|
||||
expect(projects.get('proj')).toEqual({
|
||||
$schema: '../node_modules/nx/schemas/project-schema.json',
|
||||
name: 'proj',
|
||||
root: 'proj',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without nx.json', () => {
|
||||
beforeEach(() => tree.delete('nx.json'));
|
||||
|
||||
@@ -568,6 +586,64 @@ describe('project configuration', () => {
|
||||
)
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('getProjects', () => {
|
||||
it('should get a map of projects', () => {
|
||||
addProjectConfiguration(tree, 'proj', {
|
||||
root: 'proj',
|
||||
});
|
||||
|
||||
const projects = getProjects(tree);
|
||||
|
||||
expect(projects.size).toEqual(1);
|
||||
expect(projects.get('proj')).toEqual({
|
||||
$schema: '../node_modules/nx/schemas/project-schema.json',
|
||||
name: 'proj',
|
||||
root: 'proj',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('for npm workspaces', () => {
|
||||
beforeEach(() => {
|
||||
tree = createTree();
|
||||
});
|
||||
|
||||
describe('readProjectConfiguration', () => {
|
||||
it('should read project configuration from package.json files', () => {
|
||||
writeJson(tree, 'proj/package.json', {
|
||||
name: 'proj',
|
||||
});
|
||||
|
||||
const proj = readProjectConfiguration(tree, 'proj');
|
||||
|
||||
expect(proj).toEqual({
|
||||
root: 'proj',
|
||||
sourceRoot: 'proj',
|
||||
projectType: 'library',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProjects', () => {
|
||||
beforeEach(() => {
|
||||
writeJson(tree, 'proj/package.json', {
|
||||
name: 'proj',
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a map of projects', () => {
|
||||
const projects = getProjects(tree);
|
||||
|
||||
expect(projects.size).toEqual(1);
|
||||
expect(projects.get('proj')).toEqual({
|
||||
root: 'proj',
|
||||
sourceRoot: 'proj',
|
||||
projectType: 'library',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -465,6 +465,7 @@ function findDeletedProjects(tree: Tree) {
|
||||
}
|
||||
|
||||
let staticFSWorkspace: RawProjectsConfigurations;
|
||||
let cachedTree: Tree;
|
||||
function readRawWorkspaceJson(tree: Tree): RawProjectsConfigurations {
|
||||
const path = getWorkspacePath(tree);
|
||||
if (path && tree.exists(path)) {
|
||||
@@ -477,13 +478,14 @@ function readRawWorkspaceJson(tree: Tree): RawProjectsConfigurations {
|
||||
findCreatedProjects(tree),
|
||||
(file) => readJson(tree, file)
|
||||
).projects;
|
||||
// We already have built a cache
|
||||
if (!staticFSWorkspace) {
|
||||
// We already have built a cache but need to confirm it's the same tree
|
||||
if (!staticFSWorkspace || tree !== cachedTree) {
|
||||
staticFSWorkspace = buildWorkspaceConfigurationFromGlobs(
|
||||
nxJson,
|
||||
[...globForProjectFiles(tree.root, nxJson)],
|
||||
(file) => readJson(tree, file)
|
||||
);
|
||||
cachedTree = tree;
|
||||
}
|
||||
const projects = { ...staticFSWorkspace.projects, ...createdProjects };
|
||||
findDeletedProjects(tree).forEach((file) => {
|
||||
@@ -498,10 +500,11 @@ function readRawWorkspaceJson(tree: Tree): RawProjectsConfigurations {
|
||||
delete projects[matchingStaticProject[0]];
|
||||
}
|
||||
});
|
||||
return {
|
||||
staticFSWorkspace = {
|
||||
...staticFSWorkspace,
|
||||
projects,
|
||||
};
|
||||
return staticFSWorkspace;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { workspaceRoot } from '../utils/workspace-root';
|
||||
import { NodeBasedFileHasher } from './node-based-file-hasher';
|
||||
import { FileHasherBase } from './file-hasher-base';
|
||||
import { execSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
function createFileHasher(): FileHasherBase {
|
||||
// special case for unit tests
|
||||
@@ -11,7 +13,12 @@ function createFileHasher(): FileHasherBase {
|
||||
}
|
||||
try {
|
||||
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
|
||||
return new GitBasedFileHasher();
|
||||
// we don't use git based hasher when the repo uses git submodules
|
||||
if (!existsSync(join(workspaceRoot, '.git', 'modules'))) {
|
||||
return new GitBasedFileHasher();
|
||||
} else {
|
||||
return new NodeBasedFileHasher();
|
||||
}
|
||||
} catch {
|
||||
return new NodeBasedFileHasher();
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export class NodeBasedFileHasher extends FileHasherBase {
|
||||
}
|
||||
try {
|
||||
const s = statSync(absoluteChild);
|
||||
if (!s.isDirectory()) {
|
||||
if (s.isFile()) {
|
||||
this.fileHashes.set(
|
||||
normalizePath(relChild),
|
||||
this.hashFile(relChild)
|
||||
|
||||
@@ -89,9 +89,9 @@ export function calculateFileChanges(
|
||||
}
|
||||
switch (ext) {
|
||||
case '.json':
|
||||
const atBase = readFileAtRevision(f, nxArgs.base);
|
||||
const atHead = readFileAtRevision(f, nxArgs.head);
|
||||
try {
|
||||
const atBase = readFileAtRevision(f, nxArgs.base);
|
||||
const atHead = readFileAtRevision(f, nxArgs.head);
|
||||
return jsonDiff(JSON.parse(atBase), JSON.parse(atHead));
|
||||
} catch (e) {
|
||||
return [new WholeFileChange()];
|
||||
@@ -122,6 +122,7 @@ function defaultReadFileAtRevision(
|
||||
? readFileSync(file, 'utf-8')
|
||||
: execSync(`git show ${revision}:${filePathInGitRepository}`, {
|
||||
maxBuffer: TEN_MEGABYTES,
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
})
|
||||
.toString()
|
||||
.trim();
|
||||
|
||||
@@ -181,6 +181,11 @@ export class Cache {
|
||||
}
|
||||
|
||||
private async copy(src: string, destination: string): Promise<void> {
|
||||
// 'cp -a /path/dir/ dest/' operates differently to 'cp -a /path/dir dest/'
|
||||
// --> which means actual build works but subsequent populate from cache (using cp -a) does not
|
||||
// --> the fix is to remove trailing slashes to ensure consistent & expected behaviour
|
||||
src = src.replace(/[\/\\]$/, '');
|
||||
|
||||
if (this.useFsExtraToCopyAndRemove) {
|
||||
return copy(src, destination);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import axios from 'axios';
|
||||
import { isCI } from './is-ci';
|
||||
|
||||
export class PromptMessages {
|
||||
private messages = {
|
||||
nxCloudCreation: [
|
||||
{
|
||||
code: 'set-up-distributed-caching-ci',
|
||||
message: `Enable distributed caching to make your CI faster`,
|
||||
},
|
||||
],
|
||||
nxCloudMigration: [
|
||||
{
|
||||
code: 'we-noticed',
|
||||
message: `We noticed you are migrating to a new major version, but are not taking advantage of Nx Cloud. Nx Cloud can make your CI up to 10 times faster. Learn more about it here: nx.app. Would you like to add it?`,
|
||||
},
|
||||
{
|
||||
code: 'not-leveraging-caching',
|
||||
message: `You're not leveraging distributed caching yet. Do you want to enable it and speed up your CI?`,
|
||||
},
|
||||
{
|
||||
code: 'make-ci-faster',
|
||||
message: `Enable distributed caching to make your CI faster?`,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
private selectedMessages = {};
|
||||
|
||||
getPromptMessage(key: string): string {
|
||||
if (this.selectedMessages[key] === undefined) {
|
||||
if (process.env.NX_GENERATE_DOCS_PROCESS === 'true') {
|
||||
this.selectedMessages[key] = 0;
|
||||
} else {
|
||||
this.selectedMessages[key] = Math.floor(
|
||||
Math.random() * this.messages[key].length
|
||||
);
|
||||
}
|
||||
}
|
||||
return this.messages[key][this.selectedMessages[key]].message;
|
||||
}
|
||||
|
||||
codeOfSelectedPromptMessage(key: string): string {
|
||||
if (this.selectedMessages[key] === undefined) return null;
|
||||
return this.messages[key][this.selectedMessages[key]].code;
|
||||
}
|
||||
}
|
||||
|
||||
export const messages = new PromptMessages();
|
||||
|
||||
/**
|
||||
* We are incrementing a counter to track how often create-nx-workspace is used in CI
|
||||
* vs dev environments. No personal information is collected.
|
||||
*/
|
||||
export async function recordStat(opts: {
|
||||
command: string;
|
||||
nxVersion: string;
|
||||
useCloud: boolean;
|
||||
meta: string;
|
||||
}) {
|
||||
try {
|
||||
const major = Number(opts.nxVersion.split('.')[0]);
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.log(`Record stat. Major: ${major}`);
|
||||
}
|
||||
if (major < 10 || major > 14) return; // test version, skip it
|
||||
await axios
|
||||
.create({
|
||||
baseURL: 'https://cloud.nx.app',
|
||||
timeout: 400,
|
||||
})
|
||||
.post('/nx-cloud/stats', {
|
||||
command: opts.command,
|
||||
isCI: isCI(),
|
||||
useCloud: opts.useCloud,
|
||||
meta: opts.meta,
|
||||
});
|
||||
} catch (e) {
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { logger } from './logger';
|
||||
|
||||
describe('Logger', () => {
|
||||
it('should color the NX prefix', () => {
|
||||
let logObject;
|
||||
jest.spyOn(console, 'info').mockImplementation((message) => {
|
||||
logObject = message;
|
||||
});
|
||||
|
||||
logger.info('NX some Nx message!');
|
||||
|
||||
if (process.env.CI === undefined) {
|
||||
expect(logObject).toMatchInlineSnapshot(`
|
||||
"
|
||||
[36m>[39m [7m[1m[36m NX [39m[22m[27m [1msome Nx message![22m
|
||||
"
|
||||
`);
|
||||
} else {
|
||||
expect(logObject).toMatchInlineSnapshot(`
|
||||
"
|
||||
> NX some Nx message!
|
||||
"
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should log the full stack trace when an object is being passed', () => {
|
||||
let logObject;
|
||||
jest.spyOn(console, 'error').mockImplementation((message) => {
|
||||
logObject = message;
|
||||
});
|
||||
|
||||
const err = new Error(
|
||||
'TypeError: Cannot read property target of undefined'
|
||||
);
|
||||
err.stack = `TypeError: Cannot read property 'target' of undefined
|
||||
at /someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:145:49
|
||||
at step (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:69:23)
|
||||
at Object.next (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:50:53)
|
||||
at fulfilled (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:41:58)`;
|
||||
|
||||
logger.error(err);
|
||||
|
||||
if (process.env.CI === undefined) {
|
||||
expect(logObject).toMatchInlineSnapshot(`
|
||||
"[1m[31mTypeError: Cannot read property 'target' of undefined[39m[22m
|
||||
[1m[31m at /someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:145:49[39m[22m
|
||||
[1m[31m at step (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:69:23)[39m[22m
|
||||
[1m[31m at Object.next (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:50:53)[39m[22m
|
||||
[1m[31m at fulfilled (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:41:58)[39m[22m"
|
||||
`);
|
||||
} else {
|
||||
expect(logObject).toMatchInlineSnapshot(`
|
||||
"TypeError: Cannot read property 'target' of undefined
|
||||
at /someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:145:49
|
||||
at step (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:69:23)
|
||||
at Object.next (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:50:53)
|
||||
at fulfilled (/someuser/node_modules/@storybook/angular/dist/ts3.9/server/angular-devkit-build-webpack.js:41:58)"
|
||||
`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -267,6 +267,16 @@ class CLIOutput {
|
||||
|
||||
this.addNewline();
|
||||
}
|
||||
|
||||
drain(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (process.stdout.writableNeedDrain) {
|
||||
process.stdout.once('drain', resolve);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const output = new CLIOutput();
|
||||
|
||||
@@ -2,31 +2,47 @@ import * as chalk from 'chalk';
|
||||
import { output } from '../output';
|
||||
import type { CorePlugin, PluginCapabilities } from './models';
|
||||
|
||||
export function fetchCorePlugins() {
|
||||
const corePlugins: CorePlugin[] = [
|
||||
export function fetchCorePlugins(): CorePlugin[] {
|
||||
return [
|
||||
{
|
||||
name: '@nrwl/angular',
|
||||
capabilities: 'generators',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/cypress',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/express',
|
||||
name: '@nrwl/detox',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/esbuild',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/expo',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/express',
|
||||
capabilities: 'generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/jest',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/js',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/linter',
|
||||
capabilities: 'executors',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/nest',
|
||||
capabilities: 'executors,generators',
|
||||
capabilities: 'generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/next',
|
||||
@@ -36,6 +52,10 @@ export function fetchCorePlugins() {
|
||||
name: '@nrwl/node',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: 'nx',
|
||||
capabilities: 'executors',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/nx-plugin',
|
||||
capabilities: 'executors,generators',
|
||||
@@ -45,7 +65,11 @@ export function fetchCorePlugins() {
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/js',
|
||||
name: '@nrwl/react-native',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/rollup',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
@@ -56,12 +80,15 @@ export function fetchCorePlugins() {
|
||||
name: '@nrwl/web',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/webpack',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
{
|
||||
name: '@nrwl/workspace',
|
||||
capabilities: 'executors,generators',
|
||||
},
|
||||
];
|
||||
return corePlugins;
|
||||
}
|
||||
|
||||
export function listCorePlugins(
|
||||
|
||||
@@ -165,7 +165,7 @@ describe('project graph utils', () => {
|
||||
it('should prefer project.json targets', () => {
|
||||
const projectJsonTargets = {
|
||||
build: {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: 'echo 2',
|
||||
},
|
||||
@@ -182,7 +182,7 @@ describe('project graph utils', () => {
|
||||
it('should provide targets from project.json and package.json', () => {
|
||||
const projectJsonTargets = {
|
||||
clean: {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: 'echo 2',
|
||||
},
|
||||
@@ -243,14 +243,14 @@ describe('project graph utils', () => {
|
||||
|
||||
const result = mergeNpmScriptsWithTargets('', {
|
||||
build: {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: { command: 'echo hi' },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
build: {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: { command: 'echo hi' },
|
||||
},
|
||||
test: {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export class PromisedBasedQueue {
|
||||
private counter = 0;
|
||||
private promise = Promise.resolve(null);
|
||||
|
||||
sendToQueue(fn: () => Promise<any>): Promise<any> {
|
||||
this.counter++;
|
||||
let res, rej;
|
||||
const r = new Promise((_res, _rej) => {
|
||||
res = _res;
|
||||
@@ -12,17 +14,25 @@ export class PromisedBasedQueue {
|
||||
.then(async () => {
|
||||
try {
|
||||
res(await fn());
|
||||
this.counter--;
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
this.counter--;
|
||||
}
|
||||
})
|
||||
.catch(async () => {
|
||||
try {
|
||||
res(await fn());
|
||||
this.counter--;
|
||||
} catch (e) {
|
||||
rej(e);
|
||||
this.counter--;
|
||||
}
|
||||
});
|
||||
return r;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return this.counter === 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ function getTargets(options: NormalizedSchema) {
|
||||
};
|
||||
|
||||
architect.serve = {
|
||||
executor: '@nrwl/workspace:run-commands',
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command: `nx start ${options.name}`,
|
||||
},
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"minimatch": "3.0.5",
|
||||
"react-refresh": "^0.10.0",
|
||||
"semver": "7.3.4",
|
||||
"style-loader": "^3.3.0",
|
||||
"stylus": "^0.55.0",
|
||||
"stylus-loader": "^6.2.0",
|
||||
"url-loader": "^4.1.1",
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
SharedLibraryConfig,
|
||||
} from './models';
|
||||
import { readRootPackageJson } from './package-json';
|
||||
import { extname, join } from 'path';
|
||||
import { extname } from 'path';
|
||||
import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
|
||||
|
||||
function collectDependencies(
|
||||
@@ -133,7 +133,11 @@ function mapRemotes(remotes: Remotes, projectGraph: ProjectGraph) {
|
||||
const remoteLocationExt = extname(remoteLocation);
|
||||
mappedRemotes[remoteName] = ['.js', '.mjs'].includes(remoteLocationExt)
|
||||
? remoteLocation
|
||||
: join(remoteLocation, 'remoteEntry.js');
|
||||
: `${
|
||||
remoteLocation.endsWith('/')
|
||||
? remoteLocation.slice(0, -1)
|
||||
: remoteLocation
|
||||
}/remoteEntry.js`;
|
||||
} else if (typeof remote === 'string') {
|
||||
mappedRemotes[remote] = determineRemoteUrl(remote, projectGraph);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,12 @@ export async function rollupProjectGenerator(
|
||||
}
|
||||
|
||||
function checkForTargetConflicts(tree: Tree, options: RollupProjectSchema) {
|
||||
if (options.skipValidation) return;
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
if (project.targets.build) {
|
||||
throw new Error(`Project "${project.name}" already has a build target.`);
|
||||
}
|
||||
|
||||
if (options.devServer && project.targets.serve) {
|
||||
throw new Error(`Project "${project.name}" already has a serve target.`);
|
||||
if (project.targets?.build) {
|
||||
throw new Error(
|
||||
`Project "${options.project}" already has a build target. Pass --skipValidation to ignore this error.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ export interface RollupProjectSchema {
|
||||
main?: string;
|
||||
tsConfig?: string;
|
||||
compiler?: 'babel' | 'swc' | 'tsc';
|
||||
devServer?: boolean;
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
skipValidation?: boolean;
|
||||
importPath?: string;
|
||||
external?: string[];
|
||||
rollupConfig?: string;
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`."
|
||||
},
|
||||
"skipValidation": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not perform any validation on existing project."
|
||||
},
|
||||
"importPath": {
|
||||
"type": "string",
|
||||
"description": "The library name used to import it, like `@myorg/my-awesome-lib`."
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import * as ts from 'typescript';
|
||||
import { stripIndents } from '@nrwl/devkit';
|
||||
import { findBuilderInMainJsTs } from './utils';
|
||||
import { logger } from '@nrwl/devkit';
|
||||
import { builderIsWebpackButNotWebpack5 } from './utils';
|
||||
|
||||
describe('testing utilities', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(logger, 'warn');
|
||||
});
|
||||
|
||||
it('should not log the webpack5 warning if builder is webpack5', () => {
|
||||
const sourceCode = stripIndents`
|
||||
describe('builderIsWebpackButNotWebpack5', () => {
|
||||
it('should return false if builder is webpack5', () => {
|
||||
const sourceCode = stripIndents`
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
@@ -18,19 +14,18 @@ describe('testing utilities', () => {
|
||||
};
|
||||
`;
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
findBuilderInMainJsTs(source);
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(builderIsWebpackButNotWebpack5(source)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not log the webpack5 warning if builder is @storybook/webpack5', () => {
|
||||
const sourceCode = stripIndents`
|
||||
it('should return false if builder is @storybook/webpack5', () => {
|
||||
const sourceCode = stripIndents`
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
@@ -39,19 +34,18 @@ describe('testing utilities', () => {
|
||||
};
|
||||
`;
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
findBuilderInMainJsTs(source);
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(builderIsWebpackButNotWebpack5(source)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not log the webpack5 warning if builder exists but does not contain webpack', () => {
|
||||
const sourceCode = stripIndents`
|
||||
it('should return false if builder exists but does not contain webpack', () => {
|
||||
const sourceCode = stripIndents`
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
@@ -60,19 +54,18 @@ describe('testing utilities', () => {
|
||||
};
|
||||
`;
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
findBuilderInMainJsTs(source);
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(builderIsWebpackButNotWebpack5(source)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should log the webpack5 warning if builder is webpack4', () => {
|
||||
const sourceCode = stripIndents`
|
||||
it('should return true if builder is webpack4', () => {
|
||||
const sourceCode = stripIndents`
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
@@ -81,19 +74,18 @@ describe('testing utilities', () => {
|
||||
};
|
||||
`;
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
findBuilderInMainJsTs(source);
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
});
|
||||
expect(builderIsWebpackButNotWebpack5(source)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should log the webpack5 warning if builder does not exist', () => {
|
||||
const sourceCode = stripIndents`
|
||||
it('should return true if builder does not exist because default is webpack', () => {
|
||||
const sourceCode = stripIndents`
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
@@ -101,14 +93,14 @@ describe('testing utilities', () => {
|
||||
};
|
||||
`;
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
const source = ts.createSourceFile(
|
||||
'.storybook/main.js',
|
||||
sourceCode,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
findBuilderInMainJsTs(source);
|
||||
expect(logger.warn).toHaveBeenCalled();
|
||||
expect(builderIsWebpackButNotWebpack5(source)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -47,39 +47,49 @@ export function runStorybookSetupCheck(options: CommonNxStorybookConfig) {
|
||||
|
||||
function reactWebpack5Check(options: CommonNxStorybookConfig) {
|
||||
if (options.uiFramework === '@storybook/react') {
|
||||
let storybookConfigFilePath = joinPathFragments(
|
||||
options.config.configFolder,
|
||||
'main.js'
|
||||
);
|
||||
|
||||
if (!existsSync(storybookConfigFilePath)) {
|
||||
storybookConfigFilePath = joinPathFragments(
|
||||
options.config.configFolder,
|
||||
'main.ts'
|
||||
);
|
||||
}
|
||||
|
||||
if (!existsSync(storybookConfigFilePath)) {
|
||||
// looks like there's no main config file, so skip
|
||||
return;
|
||||
}
|
||||
|
||||
const source = mainJsTsFileContent(options.config.configFolder);
|
||||
const rootSource = mainJsTsFileContent('.storybook');
|
||||
// check whether the current Storybook configuration has the webpack 5 builder enabled
|
||||
const storybookConfig = readFileSync(storybookConfigFilePath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
const source = ts.createSourceFile(
|
||||
storybookConfigFilePath,
|
||||
storybookConfig,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
|
||||
findBuilderInMainJsTs(source);
|
||||
if (
|
||||
builderIsWebpackButNotWebpack5(source) &&
|
||||
builderIsWebpackButNotWebpack5(rootSource)
|
||||
) {
|
||||
logger.warn(`
|
||||
It looks like you use Webpack 5 but your Storybook setup is not configured to leverage that
|
||||
and thus falls back to Webpack 4.
|
||||
Make sure you upgrade your Storybook config to use Webpack 5.
|
||||
|
||||
- https://gist.github.com/shilman/8856ea1786dcd247139b47b270912324#upgrade
|
||||
|
||||
`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mainJsTsFileContent(configFolder: string): ts.SourceFile {
|
||||
let storybookConfigFilePath = joinPathFragments(configFolder, 'main.js');
|
||||
|
||||
if (!existsSync(storybookConfigFilePath)) {
|
||||
storybookConfigFilePath = joinPathFragments(configFolder, 'main.ts');
|
||||
}
|
||||
|
||||
if (!existsSync(storybookConfigFilePath)) {
|
||||
// looks like there's no main config file, so skip
|
||||
return;
|
||||
}
|
||||
|
||||
const storybookConfig = readFileSync(storybookConfigFilePath, {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
return ts.createSourceFile(
|
||||
storybookConfigFilePath,
|
||||
storybookConfig,
|
||||
ts.ScriptTarget.Latest,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
function webpackFinalPropertyCheck(options: CommonNxStorybookConfig) {
|
||||
let placesToCheck = [
|
||||
{
|
||||
@@ -135,33 +145,25 @@ export function resolveCommonStorybookOptionMapper(
|
||||
return storybookOptions;
|
||||
}
|
||||
|
||||
export function findBuilderInMainJsTs(storybookConfig: ts.SourceFile) {
|
||||
export function builderIsWebpackButNotWebpack5(
|
||||
storybookConfig: ts.SourceFile
|
||||
): boolean {
|
||||
const importArray = findNodes(storybookConfig, [
|
||||
ts.SyntaxKind.PropertyAssignment,
|
||||
]);
|
||||
let builderIsSpecified = false;
|
||||
let builderIsWebpackNot5 = false;
|
||||
importArray.forEach((parent) => {
|
||||
const identifier = findNodes(parent, ts.SyntaxKind.Identifier);
|
||||
const sbBuilder = findNodes(parent, ts.SyntaxKind.StringLiteral);
|
||||
const builderText = sbBuilder?.[0]?.getText() ?? '';
|
||||
if (identifier[0].getText() === 'builder') {
|
||||
builderIsSpecified = true;
|
||||
if (
|
||||
builderText.includes('webpack') &&
|
||||
!builderText.includes('webpack5')
|
||||
) {
|
||||
builderIsSpecified = false;
|
||||
}
|
||||
if (
|
||||
identifier?.[0]?.getText() === 'builder' &&
|
||||
builderText.includes('webpack') &&
|
||||
!builderText.includes('webpack5')
|
||||
) {
|
||||
builderIsWebpackNot5 = true;
|
||||
}
|
||||
});
|
||||
if (!builderIsSpecified) {
|
||||
logger.warn(`
|
||||
It looks like you use Webpack 5 but your Storybook setup is not configured to leverage that
|
||||
and thus falls back to Webpack 4.
|
||||
Make sure you upgrade your Storybook config to use Webpack 5.
|
||||
|
||||
- https://gist.github.com/shilman/8856ea1786dcd247139b47b270912324#upgrade
|
||||
|
||||
`);
|
||||
}
|
||||
|
||||
return builderIsWebpackNot5;
|
||||
}
|
||||
|
||||
@@ -1,84 +1 @@
|
||||
import { watch } from 'chokidar';
|
||||
import { execSync } from 'child_process';
|
||||
import { workspaceLayout } from '@nrwl/devkit';
|
||||
import { joinPathFragments } from '@nrwl/devkit';
|
||||
import ignore from 'ignore';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export class WebpackNxBuildCoordinationPlugin {
|
||||
private currentlyRunning: 'none' | 'nx-build' | 'webpack-build' = 'none';
|
||||
|
||||
constructor(private readonly buildCmd: string) {
|
||||
this.buildChangedProjects();
|
||||
this.startWatchingBuildableLibs();
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
compiler.hooks.beforeCompile.tapPromise(
|
||||
'IncrementalDevServerPlugin',
|
||||
async () => {
|
||||
while (this.currentlyRunning === 'nx-build') {
|
||||
await sleep(50);
|
||||
}
|
||||
this.currentlyRunning = 'webpack-build';
|
||||
}
|
||||
);
|
||||
compiler.hooks.done.tapPromise('IncrementalDevServerPlugin', async () => {
|
||||
this.currentlyRunning = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
startWatchingBuildableLibs() {
|
||||
createFileWatcher(process.cwd(), () => {
|
||||
this.buildChangedProjects();
|
||||
});
|
||||
}
|
||||
|
||||
async buildChangedProjects() {
|
||||
while (this.currentlyRunning === 'webpack-build') {
|
||||
await sleep(50);
|
||||
}
|
||||
this.currentlyRunning = 'nx-build';
|
||||
try {
|
||||
execSync(this.buildCmd, { stdio: [0, 1, 2] });
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
this.currentlyRunning = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(time: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, time));
|
||||
}
|
||||
|
||||
function getIgnoredGlobs(root: string) {
|
||||
const ig = ignore();
|
||||
try {
|
||||
ig.add(readFileSync(`${root}/.gitignore`, 'utf-8'));
|
||||
} catch {}
|
||||
try {
|
||||
ig.add(readFileSync(`${root}/.nxignore`, 'utf-8'));
|
||||
} catch {}
|
||||
return ig;
|
||||
}
|
||||
|
||||
function createFileWatcher(root: string, changeHandler: () => void) {
|
||||
const ignoredGlobs = getIgnoredGlobs(root);
|
||||
const layout = workspaceLayout();
|
||||
|
||||
const watcher = watch(
|
||||
[
|
||||
joinPathFragments(layout.appsDir, '**'),
|
||||
joinPathFragments(layout.libsDir, '**'),
|
||||
],
|
||||
{
|
||||
cwd: root,
|
||||
ignoreInitial: true,
|
||||
}
|
||||
);
|
||||
watcher.on('all', (_event: string, path: string) => {
|
||||
if (ignoredGlobs.ignores(path)) return;
|
||||
changeHandler();
|
||||
});
|
||||
return { close: () => watcher.close() };
|
||||
}
|
||||
export { WebpackNxBuildCoordinationPlugin } from '@nrwl/webpack/src/plugins/webpack-nx-build-coordination-plugin';
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface WebpackProjectGeneratorSchema {
|
||||
devServer?: boolean;
|
||||
skipFormat?: boolean;
|
||||
skipPackageJson?: boolean;
|
||||
skipValidation?: boolean;
|
||||
target?: 'node' | 'web';
|
||||
webpackConfig?: string;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@
|
||||
"default": false,
|
||||
"description": "Do not add dependencies to `package.json`."
|
||||
},
|
||||
"skipValidation": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Do not perform any validation on existing project."
|
||||
},
|
||||
"devServer": {
|
||||
"type": "boolean",
|
||||
"description": "Add a serve target to run a local webpack dev-server",
|
||||
|
||||
@@ -29,13 +29,20 @@ function checkForTargetConflicts(
|
||||
tree: Tree,
|
||||
options: WebpackProjectGeneratorSchema
|
||||
) {
|
||||
if (options.skipValidation) return;
|
||||
|
||||
const project = readProjectConfiguration(tree, options.project);
|
||||
if (project.targets.build) {
|
||||
throw new Error(`Project "${project.name}" already has a build target.`);
|
||||
|
||||
if (project.targets?.build) {
|
||||
throw new Error(
|
||||
`Project "${project.name}" already has a build target. Pass --skipValidation to ignore this error.`
|
||||
);
|
||||
}
|
||||
|
||||
if (options.devServer && project.targets.serve) {
|
||||
throw new Error(`Project "${project.name}" already has a serve target.`);
|
||||
if (options.devServer && project.targets?.serve) {
|
||||
throw new Error(
|
||||
`Project "${project.name}" already has a serve target. Pass --skipValidation to ignore this error.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { workspaceLayout } from '@nrwl/devkit';
|
||||
import { joinPathFragments } from '@nrwl/devkit';
|
||||
import ignore from 'ignore';
|
||||
import { readFileSync } from 'fs';
|
||||
import type { Compiler } from 'webpack';
|
||||
|
||||
export class WebpackNxBuildCoordinationPlugin {
|
||||
private currentlyRunning: 'none' | 'nx-build' | 'webpack-build' = 'none';
|
||||
@@ -13,7 +14,7 @@ export class WebpackNxBuildCoordinationPlugin {
|
||||
this.startWatchingBuildableLibs();
|
||||
}
|
||||
|
||||
apply(compiler) {
|
||||
apply(compiler: Compiler) {
|
||||
compiler.hooks.beforeCompile.tapPromise(
|
||||
'IncrementalDevServerPlugin',
|
||||
async () => {
|
||||
|
||||
@@ -37,6 +37,9 @@ export function getBaseWebpackPartial(
|
||||
internalOptions: InternalBuildOptions,
|
||||
context?: ExecutorContext
|
||||
): Configuration {
|
||||
// If the function is called directly and not through `@nrwl/webpack:webpack` then this target may not be set.
|
||||
options.target ??= 'web';
|
||||
|
||||
const mainFields = [
|
||||
...(internalOptions.esm ? ['es2015'] : []),
|
||||
'module',
|
||||
@@ -68,7 +71,7 @@ export function getBaseWebpackPartial(
|
||||
) ?? {};
|
||||
|
||||
const webpackConfig: Configuration = {
|
||||
target: options.target ?? 'web', // webpack defaults to 'browserslist' which breaks Fast Refresh
|
||||
target: options.target,
|
||||
entry: {
|
||||
[mainEntry]: [options.main],
|
||||
...additionalEntryPoints,
|
||||
@@ -189,6 +192,8 @@ export function getBaseWebpackPartial(
|
||||
runtimeChunk: true,
|
||||
};
|
||||
}
|
||||
webpackConfig.optimization ??= {};
|
||||
webpackConfig.optimization.nodeEnv = process.env.NODE_ENV ?? mode;
|
||||
}
|
||||
|
||||
const extraPlugins: WebpackPluginInstance[] = [];
|
||||
@@ -390,7 +395,7 @@ export function createLoaderFromCompiler(
|
||||
}),
|
||||
},
|
||||
};
|
||||
default:
|
||||
case 'babel':
|
||||
return {
|
||||
test: /\.([jt])sx?$/,
|
||||
loader: join(__dirname, 'web-babel-loader'),
|
||||
@@ -408,5 +413,7 @@ export function createLoaderFromCompiler(
|
||||
cacheCompression: false,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +193,6 @@ export function getCommonConfig(
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
mode:
|
||||
scriptsOptimization || stylesOptimization ? 'production' : 'development',
|
||||
profile: buildOptions.statsJson,
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.mjs', '.js'],
|
||||
|
||||
@@ -34,26 +34,29 @@
|
||||
"migrations": "./migrations.json",
|
||||
"packageGroup": {
|
||||
"@nrwl/angular": "*",
|
||||
"@nrwl/cli": "*",
|
||||
"@nrwl/cypress": "*",
|
||||
"@nrwl/detox": "*",
|
||||
"@nrwl/devkit": "*",
|
||||
"@nrwl/esbuild": "*",
|
||||
"@nrwl/eslint-plugin-nx": "*",
|
||||
"@nrwl/expo": "*",
|
||||
"@nrwl/express": "*",
|
||||
"@nrwl/jest": "*",
|
||||
"@nrwl/js": "*",
|
||||
"@nrwl/linter": "*",
|
||||
"@nrwl/nest": "*",
|
||||
"@nrwl/next": "*",
|
||||
"@nrwl/node": "*",
|
||||
"@nrwl/nx-cloud": "latest",
|
||||
"@nrwl/nx-plugin": "*",
|
||||
"@nrwl/react": "*",
|
||||
"@nrwl/storybook": "*",
|
||||
"@nrwl/web": "*",
|
||||
"@nrwl/js": "*",
|
||||
"@nrwl/cli": "*",
|
||||
"@nrwl/tao": "*",
|
||||
"@nrwl/nx-cloud": "latest",
|
||||
"@nrwl/react-native": "*",
|
||||
"@nrwl/expo": "*",
|
||||
"@nrwl/detox": "*",
|
||||
"@nrwl/rollup": "*",
|
||||
"@nrwl/storybook": "*",
|
||||
"@nrwl/tao": "*",
|
||||
"@nrwl/web": "*",
|
||||
"@nrwl/webpack": "*",
|
||||
"nx": "*"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('missingDependencies', () => {
|
||||
example: [
|
||||
{
|
||||
source: 'example',
|
||||
target: 'npm:formik',
|
||||
target: 'missing',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -128,6 +128,14 @@ function collectDependencies(
|
||||
): { name: string; isTopLevel: boolean }[] {
|
||||
(projGraph.dependencies[project] || []).forEach((dependency) => {
|
||||
if (!acc.some((dep) => dep.name === dependency.target)) {
|
||||
// Temporary skip this. Currently the set of external nodes is built from package.json, not lock file.
|
||||
// As a result, some nodes might be missing. This should not cause any issues, we can just skip them.
|
||||
if (
|
||||
dependency.target.startsWith('npm:') &&
|
||||
!projGraph.externalNodes[dependency.target]
|
||||
)
|
||||
return;
|
||||
|
||||
acc.push({ name: dependency.target, isTopLevel: areTopLevelDeps });
|
||||
if (!shallow) {
|
||||
collectDependencies(dependency.target, projGraph, acc, shallow, false);
|
||||
|
||||
Reference in New Issue
Block a user