Compare commits

...

31 Commits

Author SHA1 Message Date
Jason Jean c30d39a028 chore(misc): publish 14.8.3 2022-10-04 11:01:36 -04:00
Victor Savkin 60f4aca4a1 fix(core): use node based hasher in git repos with submodules
(cherry picked from commit 678f786769)
2022-10-04 10:49:34 -04:00
Victor Savkin f71730397d Revert "fix(nx): git-hasher should fetch files from git submodules (#11334)"
This reverts commit 65e5e148db.

(cherry picked from commit f016c81d94)
2022-10-04 10:49:32 -04:00
Jack Hsu ea42c9fe43 feat(bundling): add skipValidation option when generating rollup/webpack project (#12392)
(cherry picked from commit f5e35e186b)
2022-10-04 10:49:26 -04:00
Jack Hsu 5c1cc201cc cleanup(nextjs): re-enable storybook e2e test for next.js (#12384)
(cherry picked from commit 17cef57656)
2022-10-04 10:49:21 -04:00
Jack Hsu df3cd667d4 fix(nextjs): return correct webpack config for next.js storybook app (#12371)
(cherry picked from commit 5ae5f8858d)
2022-10-04 10:49:17 -04:00
Benjamin Cabanes a109b295ae chore(repo): update nx to 14.8.1 (#12383)
(cherry picked from commit 63601d4cf2)
2022-10-04 10:49:15 -04:00
Victor Savkin 8fd0f5ee0f chore(repo): update nx to latest
(cherry picked from commit 342b1e605b)
2022-10-04 10:49:12 -04:00
Victor Savkin ce0fe45de0 feat(misc): a/b different messages during migration to next major
(cherry picked from commit 77b57b78e8)
2022-10-03 19:11:23 -04:00
Miroslav Jonaš f54dd1a9f6 fix(core): disable broken git hasher recourse submodules (#12337)
(cherry picked from commit 2f0d61f2dd)
2022-10-03 19:10:50 -04:00
Daniel Harvey c3f853a21b fix(core): remove trailing slash in outputs that prevents correct caching (#10708)
Fix by removing trailing slash when performing `cp -a` into cache.

Add test to confirm fix.

Resolves nrwl/nx#10549

(cherry picked from commit 2889372dde)
2022-10-03 19:10:47 -04:00
Jack Hsu 2f2c7d8c17 fix(bundling): set NODE_ENV correctly to avoid warnings (#12331)
(cherry picked from commit c3e060247c)
2022-10-03 19:10:45 -04:00
Jack Hsu 77337691e2 fix(bundling): correct main field in package.json when using esbuild (#12328)
(cherry picked from commit a14646a864)
2022-10-03 19:10:39 -04:00
Jason Jean d294c770d0 chore(misc): publish 14.8.2 2022-09-29 18:34:52 -04:00
Rami Hanano c54e13669c fix(nx): git-hasher should fetch files from git submodules (#11334)
* fix: git-hasher should fetch files from git submodules

* chore(repo): format

* fix(git-hasher): get untracked files by calling git submodule foreach

* fix(git-hasher): get untracked files in both parent repo and submodules

* chore(git-hasher): format focument

* fix(git-hasher): fix formatting

* fix(core): make git-hasher read all files from parent repo and nested submodules

* fix(core): call git submodule foreach for unstaged files

Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
(cherry picked from commit 65e5e148db)
2022-09-29 17:24:39 -04:00
Victor Savkin 18a7eab104 cleanup(misc): do not show links to video courses after create-nx-workspace
(cherry picked from commit fbe38865c1)
2022-09-29 17:11:42 -04:00
Victor Savkin 1dcfec1677 fix(core): daemon should not shut down due to inactivity when there are open connections
(cherry picked from commit 759f7ced86)
2022-09-29 17:11:29 -04:00
Wenchen Li 0b0131ef6c fix(webpack): regression from #10432 to allow // prefix in remote (#12302)
(cherry picked from commit ba657da7c5)
2022-09-29 17:11:24 -04:00
Phillip Barta 87735cbe6d cleanup(web): reexport WebpackNxBuildCoordinationPlugin from @nrwl/webpack (#12315)
(cherry picked from commit a592af1eb3)
2022-09-29 17:11:11 -04:00
Phillip Barta 7addb44270 fix(core): add missing core plugins to list command (#12305)
(cherry picked from commit 776b5a02f3)
2022-09-29 17:11:05 -04:00
Phillip Barta 6c289f807e cleanup(repo): remove unused dependency mime (#12254)
(cherry picked from commit 418cbc9bd0)
2022-09-29 17:10:58 -04:00
Phillip Barta 7bde32f548 fix(core): add missing packages to report command (#12306)
(cherry picked from commit 6a7a7c14a4)
2022-09-29 17:10:55 -04:00
Katerina Skroumpelou c344a1051d fix(storybook): check root mainjs for builder (#12308)
(cherry picked from commit 27037cbaed)
2022-09-29 17:10:49 -04:00
Jason Jean e7e7ba79f0 chore(repo): update lerna to 5.5.4 (#12300)
(cherry picked from commit 3c6bff2a0f)
2022-09-29 17:10:39 -04:00
Jason Jean 81f8e8c287 chore(repo): update nx to 14.8.0 (#12297)
(cherry picked from commit 5372b4b3ad)
2022-09-29 17:10:37 -04:00
Jason Jean 8b305d0232 fix(misc): remove remaining usages of @nrwl/workspace:run-commands executor (#12299) 2022-09-28 19:30:46 -04:00
Jason Jean b9ea773682 chore(misc): publish 14.8.1 2022-09-28 18:53:14 -04:00
Jason Jean 35082c7388 fix(devkit): ensure that getProjects works properly without a nx.json (#12295) 2022-09-28 17:54:35 -04:00
Victor Savkin d070fdfee8 fix(misc): buildable libs should not throw when some npm packages are not defined 2022-09-28 17:54:35 -04:00
Jack Hsu 65dc805957 fix(bundling): add missing assets ignore option to @nrwl/esbuild:esbuild (#12288) 2022-09-28 17:54:35 -04:00
Jack Hsu ac4cf513c9 fix(core): add missing packages (esbuild, webpack, rollup) to nx-migration field (#12285) 2022-09-28 11:09:35 -04:00
62 changed files with 1919 additions and 1641 deletions
+10
View File
@@ -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,
+5
View File
@@ -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`."
+5
View File
@@ -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",
+1 -1
View File
@@ -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
+5 -1
View File
@@ -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/);
+40
View File
@@ -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 -6
View File
@@ -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> {
+45 -3
View File
@@ -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);
});
});
+1 -1
View File
@@ -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'"
}
+1 -1
View File
@@ -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',
// },
+1 -1
View File
@@ -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"
}
+2
View File
@@ -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;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"packages": ["build/packages/*"],
"version": "14.8.0",
"version": "14.8.3",
"granularPathspec": false,
"command": {
"publish": {
+16 -17
View File
@@ -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",
@@ -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);
}
}
}
@@ -15,8 +15,8 @@ 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';
@@ -46,31 +46,19 @@ export async function* esbuildExecutor(
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({
options.format.map(async (format, idx) => {
const esbuildOptions = buildEsbuildOptions(
format,
options,
context
);
const result = await esbuild.build({
...esbuildOptions,
watch:
// Only emit info on one of the watch processes.
@@ -95,15 +83,21 @@ export async function* esbuildExecutor(
logger.info(BUILD_WATCH_SUCCEEDED);
}
next({ success: !!error && !hasTypeErrors, outfile });
next({
success: !!error && !hasTypeErrors,
outfile: esbuildOptions.outfile,
});
},
}
: true,
format,
outExtension: {
'.js': getOutExtension(format),
},
});
next({
success,
outfile: esbuildOptions.outfile,
});
return result;
})
);
const processOnExit = () => {
@@ -133,23 +127,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 +183,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`,
};
}
}
+5
View File
@@ -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 -10
View File
@@ -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}`,
},
+13 -9
View File
@@ -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": "0.21.1"
},
"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",
@@ -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) {
+10 -2
View File
@@ -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')
);
+3
View File
@@ -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',
];
+8 -7
View File
@@ -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,
};
}
+21 -6
View File
@@ -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',
});
});
});
});
});
+8 -1
View File
@@ -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)
+5
View File
@@ -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);
}
+83
View File
@@ -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);
}
}
}
-62
View File
@@ -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(`
"
>  NX  some Nx message!
"
`);
} 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(`
"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)"
`);
} 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)"
`);
}
});
});
+35 -8
View File
@@ -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: {
@@ -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}`,
},
+1
View File
@@ -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.`
);
}
}
+1 -1
View File
@@ -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`."
+52 -60
View File
@@ -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();
});
});
});
+51 -49
View File
@@ -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 () => {
+9 -2
View File
@@ -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'],
+11 -8
View File
@@ -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);
+877 -1111
View File
File diff suppressed because it is too large Load Diff