Skip to content

Commit

Permalink
feat(core): add support for plugin version in nx add, nx migrate, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
AgentEnder committed Apr 11, 2024
1 parent ff0b0c3 commit bf8527e
Show file tree
Hide file tree
Showing 13 changed files with 290 additions and 61 deletions.
17 changes: 17 additions & 0 deletions docs/shared/recipes/installation/install-non-javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,20 @@ nx graph

{% /tab %}
{% /tabs %}

## Installing Plugins

When Nx is managing its own installation, you can install plugins with `nx add {pluginName}`. This will install the plugin in the `.nx` folder and add it to the `nx.json` file. To manually install a plugin, you can add the plugin to `nx.json` as shown below:

```json {% fileName="nx.json" %}
{
"plugins": [
{
"plugin": "{pluginName}",
"version": "1.0.0"
}
]
}
```

The next time you run Nx, the plugin will be installed and available for use.
12 changes: 12 additions & 0 deletions e2e/nx-misc/src/misc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,12 @@ describe('migrate', () => {
'migrate-child-package': '1.0.0',
},
};
j.plugins = [
{
plugin: 'migrate-parent-package',
version: '1.0.0',
},
];
return j;
});
runCLI(
Expand Down Expand Up @@ -454,6 +460,12 @@ describe('migrate', () => {
expect(nxJson.installation.plugins['migrate-child-package']).toEqual(
'9.0.0'
);
const updatedPlugin = nxJson.plugins.find(
(p) => typeof p === 'object' && p.plugin === 'migrate-parent-package'
);
expect(
typeof updatedPlugin === 'object' && updatedPlugin.version === '2.0.0'
).toBeTruthy();
// should keep new line on package
const packageContent = readFile('package.json');
expect(packageContent.charCodeAt(packageContent.length - 1)).toEqual(10);
Expand Down
14 changes: 11 additions & 3 deletions packages/nx/src/command-line/add/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { getPluginCapabilities } from '../../utils/plugins';
import { nxVersion } from '../../utils/versions';
import { workspaceRoot } from '../../utils/workspace-root';
import type { AddOptions } from './command-object';
import { readDependenciesFromNxJson } from '../../utils/nx-installation';

export function addHandler(options: AddOptions): Promise<void> {
if (options.verbose) {
Expand Down Expand Up @@ -58,15 +59,22 @@ async function installPackage(pkgName: string, version: string): Promise<void> {
);
} else {
const nxJson = readNxJson();
nxJson.installation.plugins ??= {};
nxJson.installation.plugins[pkgName] = version;
const pluginDefinition = {
plugin: pkgName,
version,
};
if (readDependenciesFromNxJson(nxJson)[pkgName] === undefined) {
nxJson.plugins ??= [];
nxJson.plugins.push(pluginDefinition);
}
writeJsonFile('nx.json', nxJson);

try {
await runNxAsync('--help', { silent: true });
} catch (e) {
// revert adding the plugin to nx.json
nxJson.installation.plugins[pkgName] = undefined;
const pluginIdx = nxJson.plugins.findIndex((p) => p === pluginDefinition);
nxJson.plugins.splice(pluginIdx, 1);
writeJsonFile('nx.json', nxJson);

spinner.fail();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const variable = 3;`);

it('should remove empty comments', () => {
const stripped = sanitizeWrapperScript(`test; //`);
expect(stripped.length).toEqual(5);
expect(stripped).toMatchInlineSnapshot(`"test;"`);
});

// This test serves as a final sanity check to ensure that the contents of the
Expand All @@ -39,7 +39,6 @@ const variable = 3;`);
//
// You should not edit this file, as future updates to Nx may require changes to it.
// See: https://nx.dev/recipes/installation/install-non-javascript for more info.
const fs: typeof import('fs') = require('fs');
const path: typeof import('path') = require('path');
const cp: typeof import('child_process') = require('child_process');
Expand Down Expand Up @@ -73,7 +72,7 @@ const variable = 3;`);
) {
return false;
}
for (const [plugin, desiredVersion] of getDesiredPluginVersions(nxJson)) {
for (const [plugin, desiredVersion] of getDesiredPackageVersions(nxJson)) {
if (currentInstallation.devDependencies[plugin] !== desiredVersion) {
return false;
}
Expand Down Expand Up @@ -110,10 +109,7 @@ const variable = 3;`);
installationPath,
JSON.stringify({
name: 'nx-installation',
devDependencies: {
nx: nxJson.installation.version,
...getDesiredPluginVersions(nxJson),
},
devDependencies: Object.fromEntries(getDesiredPackageVersions(nxJson)),
})
);
Expand All @@ -130,24 +126,33 @@ const variable = 3;`);
}
}
function getDesiredPluginVersions(nxJson: NxJsonConfiguration) {
const packages: Record<string, string> = {};
let WARNED_DEPRECATED_INSTALLATIONS_PLUGIN_PROPERTY = false;
function getDesiredPackageVersions(nxJson: NxJsonConfiguration) {
const packages: Record<string, string> = {
nx: nxJson.installation.version,
};
for (const [plugin, version] of Object.entries(
nxJson?.installation?.plugins ?? {}
)) {
packages[plugin] = version;
if (!WARNED_DEPRECATED_INSTALLATIONS_PLUGIN_PROPERTY) {
console.warn(
'[Nx]: The "installation.plugins" entry in the "nx.json" file is deprecated. Use "plugins" instead. See https://nx.dev/recipes/installation/install-non-javascript'
);
WARNED_DEPRECATED_INSTALLATIONS_PLUGIN_PROPERTY = true;
}
}
for (const plugin of nxJson.plugins ?? []) {
if (typeof plugin === 'object' && plugin.version) {
packages[getPackageName(plugin.plugin)] = plugin.version;
if (typeof plugin === 'object') {
if (plugin.version) {
packages[getPackageName(plugin.plugin)] = plugin.version;
}
}
}
return Object.entries(packages);
}
function getPackageName(name: string) {
if (name.startsWith('@')) {
return name.split('/').slice(0, 2).join('/');
Expand Down Expand Up @@ -199,7 +204,6 @@ const variable = 3;`);
if (!process.env.NX_WRAPPER_SKIP_INSTALL) {
ensureUpToDateInstallation();
}
require('./installation/node_modules/nx/bin/nx');
"
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ export function getNxWrapperContents() {
// 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 = [
const removals = [
// Comments that start with //#
'\\s*\\/\\/# .*',
'\\s+\\/\\/# .*',
// Comments that are empty (often used for newlines between internal comments)
'\\s*\\/\\/\\s*',
'\\s+\\/\\/\\s*$',
// Comments that disable an eslint rule.
'\\s*\\/\\/ eslint-disable-next-line.*',
'(^|\\s?)\\/\\/ ?eslint-disable.*$',
];

const replacements = [
[linesToRemove.map((line) => `^${line}$`).join('|'), ''],
...removals.map((r) => [r, '']),
// Remove the sourceMapUrl comment
['^\\/\\/# sourceMappingURL.*$', ''],
// Keep empty comments if ! is present
Expand Down
33 changes: 24 additions & 9 deletions packages/nx/src/command-line/init/implementation/dot-nx/nxw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function matchesCurrentNxInstall(
) {
return false;
}
for (const [plugin, desiredVersion] of getDesiredPluginVersions(nxJson)) {
for (const [plugin, desiredVersion] of getDesiredPackageVersions(nxJson)) {
if (currentInstallation.devDependencies[plugin] !== desiredVersion) {
return false;
}
Expand Down Expand Up @@ -79,10 +79,7 @@ function performInstallation(
installationPath,
JSON.stringify({
name: 'nx-installation',
devDependencies: {
nx: nxJson.installation.version,
...getDesiredPluginVersions(nxJson),
},
devDependencies: Object.fromEntries(getDesiredPackageVersions(nxJson)),
})
);

Expand All @@ -99,19 +96,35 @@ function performInstallation(
}
}

function getDesiredPluginVersions(nxJson: NxJsonConfiguration) {
const packages: Record<string, string> = {};
let WARNED_DEPRECATED_INSTALLATIONS_PLUGIN_PROPERTY = false;
function getDesiredPackageVersions(nxJson: NxJsonConfiguration) {
const packages: Record<string, string> = {
nx: nxJson.installation.version,
};

//# adds support for "legacy" installation of plugins
for (const [plugin, version] of Object.entries(
nxJson?.installation?.plugins ?? {}
)) {
packages[plugin] = version;
if (!WARNED_DEPRECATED_INSTALLATIONS_PLUGIN_PROPERTY) {
console.warn(
'[Nx]: The "installation.plugins" entry in the "nx.json" file is deprecated. Use "plugins" instead. See https://nx.dev/recipes/installation/install-non-javascript'
);
WARNED_DEPRECATED_INSTALLATIONS_PLUGIN_PROPERTY = true;
}
}

for (const plugin of nxJson.plugins ?? []) {
if (typeof plugin === 'object' && plugin.version) {
packages[getPackageName(plugin.plugin)] = plugin.version;
if (typeof plugin === 'object') {
if (plugin.version) {
packages[getPackageName(plugin.plugin)] = plugin.version;
}
//# } else if (!nxJson.installation?.plugins?.[pluginName]) {
//# // Ideally we'd like to warn here, but we don't know that
//# // the plugin isn't a local plugin at this point. As such,
//# // the warning may not be relevant, and I'd like to avoid that.
//# }
}
}

Expand All @@ -123,6 +136,8 @@ function getDesiredPluginVersions(nxJson: NxJsonConfiguration) {
//# - `@nx/workspace/plugin` -> `@nx/workspace`
//# - `@nx/workspace/other` -> `@nx/workspace`
//# - `nx/plugin` -> `nx`
//# This is a copy of a function in 'nx/src/utils/nx-installation'.
//# It's tested there, but can't be imported since Nx isn't installed yet.
function getPackageName(name: string) {
if (name.startsWith('@')) {
return name.split('/').slice(0, 2).join('/');
Expand Down
35 changes: 12 additions & 23 deletions packages/nx/src/command-line/migrate/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ import {
createProjectGraphAsync,
readProjectsConfigurationFromProjectGraph,
} from '../../project-graph/project-graph';
import {
readDependenciesFromNxJson,
updateDependenciesInNxJson,
} from '../../utils/nx-installation';

export interface ResolvedMigrationConfiguration extends MigrationsJson {
packageGroup?: ArrayPackageGroup;
Expand Down Expand Up @@ -115,7 +119,7 @@ function normalizeSlashes(packageName: string): string {

export interface MigratorOptions {
packageJson?: PackageJson;
nxInstallation?: NxJsonConfiguration['installation'];
nxJson?: NxJsonConfiguration;
getInstalledPackageVersion: (
pkg: string,
overrides?: Record<string, string>
Expand All @@ -141,12 +145,12 @@ export class Migrator {
private readonly packageUpdates: Record<string, PackageUpdate> = {};
private readonly collectedVersions: Record<string, string> = {};
private readonly promptAnswers: Record<string, boolean> = {};
private readonly nxInstallation: NxJsonConfiguration['installation'] | null;
private readonly nxJson: NxJsonConfiguration | null;
private minVersionWithSkippedUpdates: string | undefined;

constructor(opts: MigratorOptions) {
this.packageJson = opts.packageJson;
this.nxInstallation = opts.nxInstallation;
this.nxJson = opts.nxJson;
this.getInstalledPackageVersion = opts.getInstalledPackageVersion;
this.fetch = opts.fetch;
this.installedPkgVersionOverrides = opts.from;
Expand Down Expand Up @@ -423,10 +427,9 @@ export class Migrator {
}

const dependencies: Record<string, string> = {
...this.packageJson?.dependencies,
...this.packageJson?.devDependencies,
...this.nxInstallation?.plugins,
...(this.nxInstallation && { nx: this.nxInstallation.version }),
...this.packageJson?.dependencies,
...readDependenciesFromNxJson(this.nxJson),
};

const filtered: Record<string, PackageUpdate> = {};
Expand Down Expand Up @@ -1152,25 +1155,11 @@ async function updateInstallationDetails(
const parseOptions: JsonReadOptions = {};
const nxJson = readJsonFile<NxJsonConfiguration>(nxJsonPath, parseOptions);

if (!nxJson.installation) {
if (!nxJson?.installation) {
return;
}

const nxVersion = updatedPackages.nx?.version;
if (nxVersion) {
nxJson.installation.version = nxVersion;
}

if (nxJson.installation.plugins) {
for (const dep in nxJson.installation.plugins) {
const update = updatedPackages[dep];
if (update) {
nxJson.installation.plugins[dep] = valid(update.version)
? update.version
: await resolvePackageVersionUsingRegistry(dep, update.version);
}
}
}
await updateDependenciesInNxJson(nxJson, updatedPackages);

writeJsonFile(nxJsonPath, nxJson, {
appendNewLine: parseOptions.endsWithNewline,
Expand Down Expand Up @@ -1237,7 +1226,7 @@ async function generateMigrationsJsonAndUpdatePackageJson(

const migrator = new Migrator({
packageJson: originalPackageJson,
nxInstallation: originalNxJson.installation,
nxJson: originalNxJson,
getInstalledPackageVersion: createInstalledPackageVersionsResolver(root),
fetch: createFetcher(),
from: opts.from,
Expand Down
7 changes: 4 additions & 3 deletions packages/nx/src/project-graph/plugins/isolation/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ type MaybePromise<T> = T | Promise<T>;
// The handler can return a message to be sent back to the process from which the message originated
type MessageHandlerReturn<T extends PluginWorkerMessage | PluginWorkerResult> =
T extends PluginWorkerResult
? MaybePromise<PluginWorkerMessage | void>
: MaybePromise<PluginWorkerResult | void>;
? MaybePromise<(PluginWorkerMessage & { callback?: () => void }) | void>
: MaybePromise<(PluginWorkerResult & { callback?: () => void }) | void>;

// Takes a message and a map of handlers and calls the appropriate handler
// type safe and requires all handlers to be handled
Expand All @@ -139,7 +139,8 @@ export async function consumeMessage<
if (handler) {
const response = await handler(message.payload);
if (response) {
process.send!(createMessage(response));
const { callback, ...message } = response;
process.send!(createMessage(message), callback);
}
} else {
throw new Error(`Unhandled message type: ${message.type}`);
Expand Down
11 changes: 8 additions & 3 deletions packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,12 @@ function createWorkerHandler(
consumeMessage<PluginWorkerResult>(parsed, {
'load-result': (result) => {
if (result.success) {
const { name, createNodesPattern } = result;
const {
name,
createNodesPattern,
hasCreateDependencies,
hasProcessProjectGraph,
} = result;
pluginName = name;
pluginNames.set(worker, pluginName);
onload({
Expand All @@ -140,7 +145,7 @@ function createWorkerHandler(
},
]
: undefined,
createDependencies: result.hasCreateDependencies
createDependencies: hasCreateDependencies
? (ctx) => {
const tx =
pluginName + ':createDependencies:' + performance.now();
Expand All @@ -154,7 +159,7 @@ function createWorkerHandler(
});
}
: undefined,
processProjectGraph: result.hasProcessProjectGraph
processProjectGraph: hasProcessProjectGraph
? (graph, ctx) => {
const tx =
pluginName + ':processProjectGraph:' + performance.now();
Expand Down

0 comments on commit bf8527e

Please sign in to comment.