chore(core): updates to nx wrapper and naming (#15552)

This commit is contained in:
Craigory Coppola 2023-03-09 08:38:41 -05:00 committed by GitHub
parent 2ab66dd410
commit 803de0d8be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 218 additions and 138 deletions

View File

@ -1147,9 +1147,9 @@
"disableCollapsible": false
},
{
"name": "Encapsulated Nx and the Nx Wrapper",
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
"id": "encapsulated-nx-and-the-wrapper",
"name": "Nx and the Nx Wrapper",
"path": "/more-concepts/nx-and-the-wrapper",
"id": "nx-and-the-wrapper",
"isExternal": false,
"children": [],
"disableCollapsible": false
@ -1310,9 +1310,9 @@
"disableCollapsible": false
},
{
"name": "Encapsulated Nx and the Nx Wrapper",
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
"id": "encapsulated-nx-and-the-wrapper",
"name": "Nx and the Nx Wrapper",
"path": "/more-concepts/nx-and-the-wrapper",
"id": "nx-and-the-wrapper",
"isExternal": false,
"children": [],
"disableCollapsible": false

View File

@ -1426,13 +1426,13 @@
"tags": ["explore-graph"]
},
{
"id": "encapsulated-nx-and-the-wrapper",
"name": "Encapsulated Nx and the Nx Wrapper",
"id": "nx-and-the-wrapper",
"name": "Nx and the Nx Wrapper",
"description": "",
"file": "shared/guides/encapsulated-nx-and-the-wrapper",
"file": "shared/guides/nx-and-the-wrapper",
"itemList": [],
"isExternal": false,
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
"path": "/more-concepts/nx-and-the-wrapper",
"tags": []
}
],
@ -1630,14 +1630,14 @@
"path": "/more-concepts/how-project-graph-is-built",
"tags": ["explore-graph"]
},
"/more-concepts/encapsulated-nx-and-the-wrapper": {
"id": "encapsulated-nx-and-the-wrapper",
"name": "Encapsulated Nx and the Nx Wrapper",
"/more-concepts/nx-and-the-wrapper": {
"id": "nx-and-the-wrapper",
"name": "Nx and the Nx Wrapper",
"description": "",
"file": "shared/guides/encapsulated-nx-and-the-wrapper",
"file": "shared/guides/nx-and-the-wrapper",
"itemList": [],
"isExternal": false,
"path": "/more-concepts/encapsulated-nx-and-the-wrapper",
"path": "/more-concepts/nx-and-the-wrapper",
"tags": []
},
"/recipes": {

View File

@ -486,9 +486,9 @@
"file": "shared/concepts/how-project-graph-is-built"
},
{
"name": "Encapsulated Nx and the Nx Wrapper",
"id": "encapsulated-nx-and-the-wrapper",
"file": "shared/guides/encapsulated-nx-and-the-wrapper"
"name": "Nx and the Nx Wrapper",
"id": "nx-and-the-wrapper",
"file": "shared/guides/nx-and-the-wrapper"
}
]
},

View File

@ -14,4 +14,4 @@
- [Grouping Libraries](/more-concepts/grouping-libraries)
- [Buildable and Publishable Libraries](/more-concepts/buildable-and-publishable-libraries)
- [How the Project Graph is Built](/more-concepts/how-project-graph-is-built)
- [Encapsulated Nx and the Nx Wrapper](/more-concepts/encapsulated-nx-and-the-wrapper)
- [Nx and the Nx Wrapper](/more-concepts/nx-and-the-wrapper)

View File

@ -1,53 +0,0 @@
# Encapsulated Nx
{% callout type="note" title="Available since Nx v15.8.0" %}
Support for `--encapsulated` was added in Nx v15.8.0. To ensure that it is available, specify the version of nx when running your command so that `npx` doesn't accept an older version that is in the cache. (e.g. `npx nx@latest init --encapsulated` or `npx nx@15.8.0 init --encapsulated`)
{% /callout %}
`nx init` accepts a flag called `--encapsulated`. When this flag is passed, Nx manages its own installation within a `.nx` directory. This allows Nx to be used without a `node_modules` folder or root `package.json`.
To manage the local Nx installation, and invoke Nx, a total of 4 files are created.
- [.nx/nxw.js](#nx-wrapper)
- [nx](#nx-and-nxbat)
- [nx.bat](#nx-and-nxbat)
- [nx.json](#nx-json)
## Nx Wrapper
After running `nx init --encapsulated`, a file is created called `.nx/nxw.js`. This javascript file is responsible for ensuring that your local installation of Nx is up to date with what the repository expects, and then invoking Nx. We refer to this as the "Nx Wrapper".
## Nx and nx bat
`nx` and `nx.bat` perform the same function, with `nx` working under the bash shell and `nx.bat` working for windows users. Each script simply checks that `node` and `npm` are available, and then invokes the [nx wrapper](#nx-wrapper). They should each be committed to your repository, so that developers on any operating system are able to use Nx.
To invoke an Nx command, you would use:
{% tabs %}
{% tab label="MacOS / Linux" %}
```shell
> ./nx build my-project
> ./nx generate application
> ./nx graph
```
{% /tab %}
{% tab label="Windows" %}
```shell
> ./nx.bat build my-project
> ./nx.bat generate application
> ./nx.bat graph
```
{% /tab %}
{% /tabs %}
## Nx Json
This is the configuration file for Nx. A full description of `nx.json`, and the possible configuration options, is located in [the nx.json reference documentation](/reference/nx-json).
When Nx is encapsulated, an `installation` key is added to `nx.json`. This property tracks the currently installed version of Nx and plugins.

View File

@ -0,0 +1,47 @@
# `.nx` and the Nx Wrapper
Nx is able to manage its own installation via the `.nx` directory. This installation is described within [nx.json](/reference/nx-json#installation).
By allowing Nx to manage its installation, a given repository is no longer required to contain a package.json or node_modules in its root. This is useful for repositories which may not contain any javascript or typescript. Additionally, since the Nx installation is managed inside `.nx`, it is easier to separate out from your other dependencies.
## Usage
You can install Nx in the `.nx/installation` directory by running `nx init` in a directory without package.json, and picking to install Nx in the current directory.
When Nx is installed in `.nx`, you can run Nx via a global Nx installation or the nx and nx.bat scripts that were created. In either case, the wrapper (.nx/nxw.js) will be invoked and ensure that the current workspace is up to date prior to invoking Nx.
{% tabs %}
{% tab label="Global Install" %}
```shell
> nx build my-project
> nx generate application
> nx graph
```
{% /tab %}
{% tab label="nx shell script" %}
```shell
> ./nx build my-project
> ./nx generate application
> ./nx graph
```
{% /tab %}
{% tab label="nx.bat" %}
```shell
> ./nx.bat build my-project
> ./nx.bat generate application
> ./nx.bat graph
```
{% /tab %}
{% /tabs %}
{% callout type="note" title="Available since Nx v15.8.7" %}
Support for `--use-dot-nx-installation` was added in Nx v15.8.7. To ensure that it is available, specify the version of nx when running your command so that `npx` doesn't accept an older version that is in the cache. (e.g. `npx nx@latest init`)
{% /callout %}

View File

@ -1,6 +1,6 @@
import type { NxJsonConfiguration } from '@nrwl/devkit';
import {
newEncapsulatedNxWorkspace,
newWrappedNxWorkspace,
updateFile,
updateJson,
checkFilesDoNotExist,
@ -13,11 +13,11 @@ import {
} from '@nrwl/e2e/utils';
import { bold } from 'chalk';
describe('encapsulated nx', () => {
let runEncapsulatedNx: ReturnType<typeof newEncapsulatedNxWorkspace>;
describe('nx wrapper / .nx installation', () => {
let runNxWrapper: ReturnType<typeof newWrappedNxWorkspace>;
beforeAll(() => {
runEncapsulatedNx = newEncapsulatedNxWorkspace();
runNxWrapper = newWrappedNxWorkspace();
});
afterAll(() => {
@ -26,7 +26,7 @@ describe('encapsulated nx', () => {
});
});
it('should support running targets in a encapsulated repo', () => {
it('should support running targets in a repo with .nx', () => {
updateFile(
'projects/a/project.json',
JSON.stringify({
@ -47,9 +47,9 @@ describe('encapsulated nx', () => {
return json;
});
expect(runEncapsulatedNx('echo a')).toContain('Hello from A');
expect(runNxWrapper('echo a')).toContain('Hello from A');
expect(runEncapsulatedNx('echo a')).toContain(
expect(runNxWrapper('echo a')).toContain(
'Nx read the output from the cache instead of running the command for 1 out of 1 tasks'
);
@ -64,7 +64,7 @@ describe('encapsulated nx', () => {
});
it('should work with nx report', () => {
const output = runEncapsulatedNx('report');
const output = runNxWrapper('report');
expect(output).toMatch(new RegExp(`nx.*:.*${getPublishedVersion()}`));
expect(output).toMatch(
new RegExp(`@nrwl/nest.*:.*${getPublishedVersion()}`)
@ -73,7 +73,7 @@ describe('encapsulated nx', () => {
});
it('should work with nx list', () => {
let output = runEncapsulatedNx('list');
let output = runNxWrapper('list');
const lines = output.split('\n');
const installedPluginStart = lines.findIndex((l) =>
l.includes('Installed plugins')
@ -91,7 +91,7 @@ describe('encapsulated nx', () => {
installedPluginLines.some((x) => x.includes(`${bold('@nrwl/nest')}`))
);
output = runEncapsulatedNx('list @nrwl/nest');
output = runNxWrapper('list @nrwl/nest');
expect(output).toContain('Capabilities in @nrwl/nest');
});
@ -101,9 +101,7 @@ describe('encapsulated nx', () => {
j.installation.plugins['@nrwl/workspace'] = getPublishedVersion();
return j;
});
expect(() =>
runEncapsulatedNx(`g npm-package ${uniq('pkg')}`)
).not.toThrow();
expect(() => runNxWrapper(`g npm-package ${uniq('pkg')}`)).not.toThrow();
expect(() => checkFilesExist());
});
@ -215,7 +213,7 @@ describe('encapsulated nx', () => {
};
return j;
});
runEncapsulatedNx(
runNxWrapper(
'migrate migrate-parent-package@2.0.0 --from="migrate-parent-package@1.0.0"',
{
env: {
@ -250,7 +248,7 @@ describe('encapsulated nx', () => {
});
// runs migrations
runEncapsulatedNx('migrate --run-migrations=migrations.json', {
runNxWrapper('migrate --run-migrations=migrations.json', {
env: {
...process.env,
NX_MIGRATE_SKIP_INSTALL: 'true',

View File

@ -365,13 +365,15 @@ export function newLernaWorkspace({
}
}
export function newEncapsulatedNxWorkspace({
name = uniq('encapsulated'),
export function newWrappedNxWorkspace({
name = uniq('wrapped'),
pmc = getPackageManagerCommand(),
} = {}): (command: string, opts?: Partial<ExecSyncOptions>) => string {
projName = name;
ensureDirSync(tmpProjPath());
runCommand(`${pmc.runUninstalledPackage} nx@latest init --encapsulated`);
runCommand(
`${pmc.runUninstalledPackage} nx@latest init --use-dot-nx-installation`
);
return (command: string, opts: Partial<ExecSyncOptions> | undefined) => {
if (process.platform === 'win32') {
return runCommand(`./nx.bat ${command}`, { ...opts, failOnError: true });

View File

@ -26,6 +26,30 @@
}
]
}
},
{
"files": ["nxw.ts"],
"rules": {
"@typescript-eslint/no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["*", "!fs"],
"message": "The Nx wrapper is ran before packages are installed. It should only import node builtins.",
"allowTypeImports": true
}
]
}
],
"no-restricted-modules": [
"error",
{
"patterns": ["*", "!fs", "!path", "!child_process", "!node:*"]
}
],
"no-restricted-imports": "off"
}
}
]
}

View File

@ -62,14 +62,15 @@ if (
// this file is already in the local workspace
if (localNx === resolveNx(null)) {
if (localNx.includes('.nx') && !process.env.NX_WRAPPER_SET) {
const nxWrapperPath = localNx.replace(/\.nx.*/, '.nx/') + 'nxw.js';
require(nxWrapperPath);
}
initLocal(workspace);
} else {
// Nx is being run from globally installed CLI - hand off to the local
require(localNx);
if (localNx.includes('.nx')) {
const nxWrapperPath = localNx.replace(/\.nx.*/, '.nx/') + 'nxw.js';
require(nxWrapperPath);
} else {
require(localNx);
}
}
}

View File

@ -51,7 +51,7 @@
"15.8.2-update-nx-wrapper": {
"cli": "nx",
"version": "15.8.2-beta.0",
"description": "Updates the nx wrapper in encapsulated repos.",
"description": "Updates the nx wrapper.",
"implementation": "./src/migrations/update-15-8-2/update-nxw"
}
}

View File

@ -2,46 +2,36 @@ import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { prerelease } from 'semver';
import * as parser from 'yargs-parser';
import { prompt } from 'enquirer';
import { addNxToMonorepo } from '../nx-init/add-nx-to-monorepo';
import { addNxToNest } from '../nx-init/add-nx-to-nest';
import { addNxToNpmRepo } from '../nx-init/add-nx-to-npm-repo';
import { addNxToAngularCliRepo } from '../nx-init/angular';
import { generateEncapsulatedNxSetup } from '../nx-init/encapsulated/add-nx-scripts';
import { generateDotNxSetup } from '../nx-init/dot-nx/add-nx-scripts';
import { directoryExists, readJsonFile } from '../utils/fileutils';
import { PackageJson } from '../utils/package-json';
import { runNxSync } from '../utils/child-process';
export async function initHandler() {
const args = process.argv.slice(2).join(' ');
const flags = parser(args, {
boolean: ['encapsulated'],
default: {
encapsulated: false,
boolean: ['useDotNxInstallation'],
alias: {
useDotNxInstallation: ['encapsulated'],
},
}) as any as { encapsulated: boolean };
default: {
useDotNxInstallation: false,
},
}) as any as { useDotNxInstallation: boolean };
const version =
process.env.NX_VERSION ?? prerelease(require('../../package.json').version)
? 'next'
: 'latest';
process.env.NX_VERSION ??
(prerelease(require('../../package.json').version) ? 'next' : 'latest');
if (process.env.NX_VERSION) {
console.log(`Using version ${process.env.NX_VERSION}`);
}
if (flags.encapsulated === true) {
if (process.platform !== 'win32') {
console.log(
'Setting Nx up installation in `.nx`. You can run nx commands like: `./nx --help`'
);
} else {
console.log(
'Setting Nx up installation in `.nx`. You can run nx commands like: `./nx.bat --help`'
);
}
generateEncapsulatedNxSetup(version);
if (process.platform === 'win32') {
execSync('./nx.bat', { stdio: 'inherit' });
} else {
execSync('./nx', { stdio: 'inherit' });
}
if (flags.useDotNxInstallation === true) {
setupDotNxInstallation(version);
} else if (existsSync('package.json')) {
const packageJson: PackageJson = readJsonFile('package.json');
if (existsSync('angular.json')) {
@ -59,9 +49,30 @@ export async function initHandler() {
await addNxToNpmRepo();
}
} else {
execSync(`npx --yes create-nx-workspace@${version} ${args}`, {
stdio: [0, 1, 2],
});
const useDotNxFolder = await prompt<{ useDotNxFolder: string }>([
{
name: 'useDotNxFolder',
type: 'autocomplete',
message: 'Where should your workspace be created?',
choices: [
{
name: 'In a new folder under this directory',
value: 'false',
},
{
name: 'In this directory',
value: 'true',
},
],
},
]).then((r) => r.useDotNxFolder === 'true');
if (useDotNxFolder) {
setupDotNxInstallation(version);
} else {
execSync(`npx --yes create-nx-workspace@${version} ${args}`, {
stdio: [0, 1, 2],
});
}
}
}
@ -105,3 +116,18 @@ function isMonorepo(packageJson: PackageJson) {
return false;
}
function setupDotNxInstallation(version: string) {
if (process.platform !== 'win32') {
console.log(
'Setting Nx up installation in `.nx`. You can run nx commands like: `./nx --help`'
);
} else {
console.log(
'Setting Nx up installation in `.nx`. You can run nx commands like: `./nx.bat --help`'
);
}
generateDotNxSetup(version);
// invokes the wrapper, thus invoking the initial installation process
runNxSync('');
}

View File

@ -1,7 +1,7 @@
import {
getNxWrapperContents,
nxWrapperPath,
} from '../../nx-init/encapsulated/add-nx-scripts';
} from '../../nx-init/dot-nx/add-nx-scripts';
import { normalizePath } from '../../utils/path';
import { Tree } from '../../generators/tree';

View File

@ -0,0 +1,24 @@
import { sanitizeWrapperScript } from './add-nx-scripts';
describe('sanitizeWrapperScript', () => {
it('should remove any comments starting with //#', () => {
const stripped = sanitizeWrapperScript(`// should not be removed
//# internal should be removed
const variable = 3;`);
expect(stripped).not.toContain('internal');
expect(stripped).toContain('variable = 3;');
});
it('should remove eslint-disable comments', () => {
const stripped = sanitizeWrapperScript(`// should not be removed
// eslint-disable-next-line no-restricted-modules
const variable = 3;`);
expect(stripped).not.toContain('no-restricted-modules');
expect(stripped).toContain('variable = 3;');
});
it('should remove empty comments', () => {
const stripped = sanitizeWrapperScript(`test; //`);
expect(stripped.length).toEqual(5);
});
});

View File

@ -32,7 +32,7 @@ command -v npm >/dev/null 2>&1 || { echo >&2 "${NPM_MISSING_ERR}"; exit 1; }
path_to_root=$(dirname $BASH_SOURCE)
node ${path.posix.join('$path_to_root', nxWrapperPath(path.posix))} $@`;
export function generateEncapsulatedNxSetup(version?: string) {
export function generateDotNxSetup(version?: string) {
const host = new FsTree(process.cwd(), false);
writeMinimalNxJson(host, version);
updateGitIgnore(host);
@ -75,11 +75,24 @@ export function updateGitIgnore(host: Tree) {
);
}
// Gets the sanitized contents for nxw.js
export function getNxWrapperContents() {
// Read nxw.js, but remove any empty comments or comments that start with `//#: `
// This removes the sourceMapUrl since it is invalid, as well as any internal comments.
return readFileSync(path.join(__dirname, 'nxw.js'), 'utf-8').replace(
/(\/\/# .*)|(\/\/\w*)$/gm,
''
return sanitizeWrapperScript(
readFileSync(path.join(__dirname, 'nxw.js'), 'utf-8')
);
}
// Remove any empty comments or comments that start with `//#: ` or eslint-disable comments.
// This removes the sourceMapUrl since it is invalid, as well as any internal comments.
export function sanitizeWrapperScript(input: string) {
const linesToRemove = [
// Comments that start with //#
'\\/\\/# .*',
// Comments that are empty (often used for newlines between internal comments)
'\\s*\\/\\/\\s*',
// Comments that disable an eslint rule.
'\\/\\/ eslint-disable-next-line.*',
];
const regex = `(${linesToRemove.join('|')})$`;
return input.replace(new RegExp(regex, 'gm'), '');
}

View File

@ -1,6 +1,6 @@
// This file should be committed to your repository! It wraps Nx and ensures
// that your local installation matches nx.json.
// See: https://nx.dev/more-concepts/encapsulated-nx-and-the-wrapper for more info.
// See: https://nx.dev/more-concepts/nx-and-the-wrapper for more info.
//
//# The contents of this file are executed before packages are installed.
//# As such, we should not import anything from nx, other @nrwl packages,
@ -60,7 +60,7 @@ function ensureUpToDateInstallation() {
nxJson = require(nxJsonPath);
} catch {
console.error(
'[NX]: nx.json is required when running in encapsulated mode. Run `npx nx init --encapsulated` to restore it.'
'[NX]: nx.json is required when running the nx wrapper. See https://nx.dev/more-concepts/nx-and-the-wrapper'
);
process.exit(1);
}
@ -99,10 +99,8 @@ function ensureUpToDateInstallation() {
}
}
if (require.main === module || !process.env.NX_WRAPPER_SET) {
if (!process.env.NX_WRAPPER_SKIP_INSTALL) {
ensureUpToDateInstallation();
}
process.env.NX_WRAPPER_SET = 'true';
require('./installation/node_modules/nx/bin/nx');
if (!process.env.NX_WRAPPER_SKIP_INSTALL) {
ensureUpToDateInstallation();
}
// eslint-disable-next-line no-restricted-modules
require('./installation/node_modules/nx/bin/nx');