feat(core): add integration with nx powerpack (#27972)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> There is no Nx Powerpack product. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> Integration with Nx Powerpack is added. Nx Powerpack is optional, stay tuned for more information which will be released soon. ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes # --------- Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com> Co-authored-by: JamesHenry <james@henry.sc>
This commit is contained in:
parent
fb91ed5e7f
commit
b06f515059
25
docs/generated/cli/activate-powerpack.md
Normal file
25
docs/generated/cli/activate-powerpack.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: 'activate-powerpack - CLI command'
|
||||||
|
description: 'Activate a Nx Powerpack license.'
|
||||||
|
---
|
||||||
|
|
||||||
|
# activate-powerpack
|
||||||
|
|
||||||
|
Activate a Nx Powerpack license.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nx activate-powerpack <license>
|
||||||
|
```
|
||||||
|
|
||||||
|
Install `nx` globally to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpm nx`.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
| ----------- | ------- | ---------------------------------------------------------------------- |
|
||||||
|
| `--help` | boolean | Show help. |
|
||||||
|
| `--license` | string | This is a License Key for Nx Powerpack. |
|
||||||
|
| `--verbose` | boolean | Prints additional information about the commands (e.g., stack traces). |
|
||||||
|
| `--version` | boolean | Show version number. |
|
||||||
@ -8753,6 +8753,14 @@
|
|||||||
"children": [],
|
"children": [],
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "activate-powerpack",
|
||||||
|
"path": "/nx-api/nx/documents/activate-powerpack",
|
||||||
|
"id": "activate-powerpack",
|
||||||
|
"isExternal": false,
|
||||||
|
"children": [],
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reset",
|
"name": "reset",
|
||||||
"path": "/nx-api/nx/documents/reset",
|
"path": "/nx-api/nx/documents/reset",
|
||||||
|
|||||||
@ -1880,6 +1880,17 @@
|
|||||||
"tags": ["cache-task-results", "distribute-task-execution"],
|
"tags": ["cache-task-results", "distribute-task-execution"],
|
||||||
"originalFilePath": "generated/cli/connect"
|
"originalFilePath": "generated/cli/connect"
|
||||||
},
|
},
|
||||||
|
"/nx-api/nx/documents/activate-powerpack": {
|
||||||
|
"id": "activate-powerpack",
|
||||||
|
"name": "activate-powerpack",
|
||||||
|
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
|
||||||
|
"file": "generated/packages/nx/documents/activate-powerpack",
|
||||||
|
"itemList": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"path": "/nx-api/nx/documents/activate-powerpack",
|
||||||
|
"tags": ["cache-task-results"],
|
||||||
|
"originalFilePath": "generated/cli/activate-powerpack"
|
||||||
|
},
|
||||||
"/nx-api/nx/documents/reset": {
|
"/nx-api/nx/documents/reset": {
|
||||||
"id": "reset",
|
"id": "reset",
|
||||||
"name": "reset",
|
"name": "reset",
|
||||||
|
|||||||
@ -235,6 +235,13 @@
|
|||||||
"name": "connect-to-nx-cloud",
|
"name": "connect-to-nx-cloud",
|
||||||
"path": "/nx-api/nx/documents/connect-to-nx-cloud"
|
"path": "/nx-api/nx/documents/connect-to-nx-cloud"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
|
||||||
|
"file": "generated/packages/nx/documents/activate-powerpack",
|
||||||
|
"id": "activate-powerpack",
|
||||||
|
"name": "activate-powerpack",
|
||||||
|
"path": "/nx-api/nx/documents/activate-powerpack"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
|
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
|
||||||
"file": "generated/packages/nx/documents/reset",
|
"file": "generated/packages/nx/documents/reset",
|
||||||
|
|||||||
@ -1859,6 +1859,17 @@
|
|||||||
"tags": ["cache-task-results", "distribute-task-execution"],
|
"tags": ["cache-task-results", "distribute-task-execution"],
|
||||||
"originalFilePath": "generated/cli/connect"
|
"originalFilePath": "generated/cli/connect"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "activate-powerpack",
|
||||||
|
"name": "activate-powerpack",
|
||||||
|
"description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
|
||||||
|
"file": "generated/packages/nx/documents/activate-powerpack",
|
||||||
|
"itemList": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"path": "nx/documents/activate-powerpack",
|
||||||
|
"tags": ["cache-task-results"],
|
||||||
|
"originalFilePath": "generated/cli/activate-powerpack"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "reset",
|
"id": "reset",
|
||||||
"name": "reset",
|
"name": "reset",
|
||||||
|
|||||||
25
docs/generated/packages/nx/documents/activate-powerpack.md
Normal file
25
docs/generated/packages/nx/documents/activate-powerpack.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: 'activate-powerpack - CLI command'
|
||||||
|
description: 'Activate a Nx Powerpack license.'
|
||||||
|
---
|
||||||
|
|
||||||
|
# activate-powerpack
|
||||||
|
|
||||||
|
Activate a Nx Powerpack license.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
nx activate-powerpack <license>
|
||||||
|
```
|
||||||
|
|
||||||
|
Install `nx` globally to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpm nx`.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
| ----------- | ------- | ---------------------------------------------------------------------- |
|
||||||
|
| `--help` | boolean | Show help. |
|
||||||
|
| `--license` | string | This is a License Key for Nx Powerpack. |
|
||||||
|
| `--verbose` | boolean | Prints additional information about the commands (e.g., stack traces). |
|
||||||
|
| `--version` | boolean | Show version number. |
|
||||||
@ -2095,6 +2095,12 @@
|
|||||||
"tags": ["cache-task-results", "distribute-task-execution"],
|
"tags": ["cache-task-results", "distribute-task-execution"],
|
||||||
"file": "generated/cli/connect"
|
"file": "generated/cli/connect"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "activate-powerpack",
|
||||||
|
"tags": ["cache-task-results"],
|
||||||
|
"id": "activate-powerpack",
|
||||||
|
"file": "generated/cli/activate-powerpack"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "reset",
|
"name": "reset",
|
||||||
"id": "reset",
|
"id": "reset",
|
||||||
|
|||||||
@ -556,6 +556,7 @@
|
|||||||
- [report](/nx-api/nx/documents/report)
|
- [report](/nx-api/nx/documents/report)
|
||||||
- [list](/nx-api/nx/documents/list)
|
- [list](/nx-api/nx/documents/list)
|
||||||
- [connect-to-nx-cloud](/nx-api/nx/documents/connect-to-nx-cloud)
|
- [connect-to-nx-cloud](/nx-api/nx/documents/connect-to-nx-cloud)
|
||||||
|
- [activate-powerpack](/nx-api/nx/documents/activate-powerpack)
|
||||||
- [reset](/nx-api/nx/documents/reset)
|
- [reset](/nx-api/nx/documents/reset)
|
||||||
- [repair](/nx-api/nx/documents/repair)
|
- [repair](/nx-api/nx/documents/repair)
|
||||||
- [sync](/nx-api/nx/documents/sync)
|
- [sync](/nx-api/nx/documents/sync)
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { ActivatePowerpackOptions } from './command-object';
|
||||||
|
import { prompt } from 'enquirer';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { getPackageManagerCommand } from '../../utils/package-manager';
|
||||||
|
|
||||||
|
export async function handleActivatePowerpack(
|
||||||
|
options: ActivatePowerpackOptions
|
||||||
|
) {
|
||||||
|
const license =
|
||||||
|
options.license ??
|
||||||
|
(await prompt({
|
||||||
|
type: 'input',
|
||||||
|
name: 'license',
|
||||||
|
message: 'Enter your License Key',
|
||||||
|
}));
|
||||||
|
const { activatePowerpack } = await requirePowerpack();
|
||||||
|
activatePowerpack(workspaceRoot, license);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requirePowerpack(): Promise<any> {
|
||||||
|
// @ts-ignore
|
||||||
|
return import('@nx/powerpack-license').catch(async (e) => {
|
||||||
|
if ('code' in e && e.code === 'MODULE_NOT_FOUND') {
|
||||||
|
try {
|
||||||
|
execSync(
|
||||||
|
`${getPackageManagerCommand().addDev} @nx/powerpack-license@latest`
|
||||||
|
);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return await import('@nx/powerpack-license');
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
'Failed to install @nx/powerpack-license. Please install @nx/powerpack-license and try again.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { CommandModule } from 'yargs';
|
||||||
|
import { withVerbose } from '../yargs-utils/shared-options';
|
||||||
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
|
|
||||||
|
export interface ActivatePowerpackOptions {
|
||||||
|
license: string;
|
||||||
|
verbose: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const yargsActivatePowerpackCommand: CommandModule<
|
||||||
|
{},
|
||||||
|
ActivatePowerpackOptions
|
||||||
|
> = {
|
||||||
|
command: 'activate-powerpack <license>',
|
||||||
|
describe: 'Activate a Nx Powerpack license.',
|
||||||
|
builder: (yargs) =>
|
||||||
|
withVerbose(yargs)
|
||||||
|
.parserConfiguration({
|
||||||
|
'strip-dashed': true,
|
||||||
|
'unknown-options-as-args': true,
|
||||||
|
})
|
||||||
|
.positional('license', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'This is a License Key for Nx Powerpack.',
|
||||||
|
})
|
||||||
|
.example(
|
||||||
|
'$0 activate-powerpack <license key>',
|
||||||
|
'Activate a Nx Powerpack license'
|
||||||
|
),
|
||||||
|
handler: async (args) => {
|
||||||
|
const exitCode = await handleErrors(args.verbose as boolean, async () => {
|
||||||
|
return (await import('./activate-powerpack')).handleActivatePowerpack(
|
||||||
|
args
|
||||||
|
);
|
||||||
|
});
|
||||||
|
process.exit(exitCode);
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -8,10 +8,7 @@ export interface AddOptions {
|
|||||||
__overrides_unparsed__: string[];
|
__overrides_unparsed__: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const yargsAddCommand: CommandModule<
|
export const yargsAddCommand: CommandModule<{}, AddOptions> = {
|
||||||
Record<string, unknown>,
|
|
||||||
AddOptions
|
|
||||||
> = {
|
|
||||||
command: 'add <packageSpecifier>',
|
command: 'add <packageSpecifier>',
|
||||||
describe: 'Install a plugin and initialize it.',
|
describe: 'Install a plugin and initialize it.',
|
||||||
builder: (yargs) =>
|
builder: (yargs) =>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
listPlugins,
|
listPlugins,
|
||||||
} from '../../utils/plugins';
|
} from '../../utils/plugins';
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
|
import { listPowerpackPlugins } from '../../utils/plugins/output';
|
||||||
|
|
||||||
export interface ListArgs {
|
export interface ListArgs {
|
||||||
/** The name of an installed plugin to query */
|
/** The name of an installed plugin to query */
|
||||||
@ -46,6 +47,7 @@ export async function listHandler(args: ListArgs): Promise<void> {
|
|||||||
}
|
}
|
||||||
listPlugins(installedPlugins, 'Installed plugins:');
|
listPlugins(installedPlugins, 'Installed plugins:');
|
||||||
listAlsoAvailableCorePlugins(installedPlugins);
|
listAlsoAvailableCorePlugins(installedPlugins);
|
||||||
|
listPowerpackPlugins();
|
||||||
|
|
||||||
output.note({
|
output.note({
|
||||||
title: 'Community Plugins',
|
title: 'Community Plugins',
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
|
|
||||||
|
import { yargsActivatePowerpackCommand } from './activate-powerpack/command-object';
|
||||||
import {
|
import {
|
||||||
yargsAffectedBuildCommand,
|
yargsAffectedBuildCommand,
|
||||||
yargsAffectedCommand,
|
yargsAffectedCommand,
|
||||||
@ -63,6 +64,7 @@ export const commandsObject = yargs
|
|||||||
.parserConfiguration(parserConfiguration)
|
.parserConfiguration(parserConfiguration)
|
||||||
.usage(chalk.bold('Smart Monorepos · Fast CI'))
|
.usage(chalk.bold('Smart Monorepos · Fast CI'))
|
||||||
.demandCommand(1, '')
|
.demandCommand(1, '')
|
||||||
|
.command(yargsActivatePowerpackCommand)
|
||||||
.command(yargsAddCommand)
|
.command(yargsAddCommand)
|
||||||
.command(yargsAffectedBuildCommand)
|
.command(yargsAffectedBuildCommand)
|
||||||
.command(yargsAffectedCommand)
|
.command(yargsAffectedCommand)
|
||||||
@ -98,9 +100,27 @@ export const commandsObject = yargs
|
|||||||
.command(yargsNxInfixCommand)
|
.command(yargsNxInfixCommand)
|
||||||
.command(yargsLoginCommand)
|
.command(yargsLoginCommand)
|
||||||
.command(yargsLogoutCommand)
|
.command(yargsLogoutCommand)
|
||||||
|
.command(resolveConformanceCommandObject())
|
||||||
.scriptName('nx')
|
.scriptName('nx')
|
||||||
.help()
|
.help()
|
||||||
// NOTE: we handle --version in nx.ts, this just tells yargs that the option exists
|
// NOTE: we handle --version in nx.ts, this just tells yargs that the option exists
|
||||||
// so that it shows up in help. The default yargs implementation of --version is not
|
// so that it shows up in help. The default yargs implementation of --version is not
|
||||||
// hit, as the implementation in nx.ts is hit first and calls process.exit(0).
|
// hit, as the implementation in nx.ts is hit first and calls process.exit(0).
|
||||||
.version();
|
.version();
|
||||||
|
|
||||||
|
function resolveConformanceCommandObject() {
|
||||||
|
try {
|
||||||
|
const { yargsConformanceCommand } = require('@nx/powerpack-conformance');
|
||||||
|
return yargsConformanceCommand;
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
command: 'conformance',
|
||||||
|
// Hide from --help output in the common case of not having the plugin installed
|
||||||
|
describe: false,
|
||||||
|
handler: () => {
|
||||||
|
// TODO: Add messaging to help with learning more about powerpack and conformance
|
||||||
|
process.exit(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { getNxRequirePaths } from '../../utils/installation-directory';
|
|||||||
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
|
import { NxJsonConfiguration, readNxJson } from '../../config/nx-json';
|
||||||
import { ProjectGraph } from '../../config/project-graph';
|
import { ProjectGraph } from '../../config/project-graph';
|
||||||
import { ProjectGraphError } from '../../project-graph/error-types';
|
import { ProjectGraphError } from '../../project-graph/error-types';
|
||||||
|
import { getPowerpackLicenseInformation } from '../../utils/powerpack';
|
||||||
|
|
||||||
const nxPackageJson = readJsonFile<typeof import('../../../package.json')>(
|
const nxPackageJson = readJsonFile<typeof import('../../../package.json')>(
|
||||||
join(__dirname, '../../../package.json')
|
join(__dirname, '../../../package.json')
|
||||||
@ -39,6 +40,7 @@ export const packagesWeCareAbout = [
|
|||||||
|
|
||||||
export const patternsWeIgnoreInCommunityReport: Array<string | RegExp> = [
|
export const patternsWeIgnoreInCommunityReport: Array<string | RegExp> = [
|
||||||
...packagesWeCareAbout,
|
...packagesWeCareAbout,
|
||||||
|
new RegExp('@nx/powerpack*'),
|
||||||
'@schematics/angular',
|
'@schematics/angular',
|
||||||
new RegExp('@angular/*'),
|
new RegExp('@angular/*'),
|
||||||
'@nestjs/schematics',
|
'@nestjs/schematics',
|
||||||
@ -58,7 +60,9 @@ export async function reportHandler() {
|
|||||||
const {
|
const {
|
||||||
pm,
|
pm,
|
||||||
pmVersion,
|
pmVersion,
|
||||||
|
powerpackLicense,
|
||||||
localPlugins,
|
localPlugins,
|
||||||
|
powerpackPlugins,
|
||||||
communityPlugins,
|
communityPlugins,
|
||||||
registeredPlugins,
|
registeredPlugins,
|
||||||
packageVersionsWeCareAbout,
|
packageVersionsWeCareAbout,
|
||||||
@ -88,6 +92,36 @@ export async function reportHandler() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (powerpackLicense) {
|
||||||
|
bodyLines.push(LINE_SEPARATOR);
|
||||||
|
bodyLines.push(chalk.green('Nx Powerpack'));
|
||||||
|
|
||||||
|
bodyLines.push(
|
||||||
|
`Licensed to ${powerpackLicense.organizationName} for ${
|
||||||
|
powerpackLicense.seatCount
|
||||||
|
} user${powerpackLicense.seatCount > 1 ? 's' : ''} in ${
|
||||||
|
powerpackLicense.workspaceCount
|
||||||
|
} workspace${
|
||||||
|
powerpackLicense.workspaceCount > 1 ? 's' : ''
|
||||||
|
} until ${new Date(powerpackLicense.expiresAt).toLocaleDateString()}`
|
||||||
|
);
|
||||||
|
bodyLines.push('');
|
||||||
|
|
||||||
|
padding =
|
||||||
|
Math.max(
|
||||||
|
...powerpackPlugins.map(
|
||||||
|
(powerpackPlugin) => powerpackPlugin.name.length
|
||||||
|
)
|
||||||
|
) + 1;
|
||||||
|
for (const powerpackPlugin of powerpackPlugins) {
|
||||||
|
bodyLines.push(
|
||||||
|
`${chalk.green(powerpackPlugin.name.padEnd(padding))} : ${chalk.bold(
|
||||||
|
powerpackPlugin.version
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (registeredPlugins.length) {
|
if (registeredPlugins.length) {
|
||||||
bodyLines.push(LINE_SEPARATOR);
|
bodyLines.push(LINE_SEPARATOR);
|
||||||
bodyLines.push('Registered Plugins:');
|
bodyLines.push('Registered Plugins:');
|
||||||
@ -147,6 +181,9 @@ export async function reportHandler() {
|
|||||||
export interface ReportData {
|
export interface ReportData {
|
||||||
pm: PackageManager;
|
pm: PackageManager;
|
||||||
pmVersion: string;
|
pmVersion: string;
|
||||||
|
// TODO(@FrozenPandaz): Provide the right type here.
|
||||||
|
powerpackLicense: any | null;
|
||||||
|
powerpackPlugins: PackageJson[];
|
||||||
localPlugins: string[];
|
localPlugins: string[];
|
||||||
communityPlugins: PackageJson[];
|
communityPlugins: PackageJson[];
|
||||||
registeredPlugins: string[];
|
registeredPlugins: string[];
|
||||||
@ -174,6 +211,7 @@ export async function getReportData(): Promise<ReportData> {
|
|||||||
|
|
||||||
const nxJson = readNxJson();
|
const nxJson = readNxJson();
|
||||||
const localPlugins = await findLocalPlugins(graph, nxJson);
|
const localPlugins = await findLocalPlugins(graph, nxJson);
|
||||||
|
const powerpackPlugins = findInstalledPowerpackPlugins();
|
||||||
const communityPlugins = findInstalledCommunityPlugins();
|
const communityPlugins = findInstalledCommunityPlugins();
|
||||||
const registeredPlugins = findRegisteredPluginsBeingUsed(nxJson);
|
const registeredPlugins = findRegisteredPluginsBeingUsed(nxJson);
|
||||||
|
|
||||||
@ -193,8 +231,15 @@ export async function getReportData(): Promise<ReportData> {
|
|||||||
|
|
||||||
const native = isNativeAvailable();
|
const native = isNativeAvailable();
|
||||||
|
|
||||||
|
let powerpackLicense = null;
|
||||||
|
try {
|
||||||
|
powerpackLicense = await getPowerpackLicenseInformation();
|
||||||
|
} catch {}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pm,
|
pm,
|
||||||
|
powerpackLicense,
|
||||||
|
powerpackPlugins,
|
||||||
pmVersion,
|
pmVersion,
|
||||||
localPlugins,
|
localPlugins,
|
||||||
communityPlugins,
|
communityPlugins,
|
||||||
@ -294,6 +339,13 @@ export function findMisalignedPackagesForPackage(
|
|||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findInstalledPowerpackPlugins(): PackageJson[] {
|
||||||
|
const installedPlugins = findInstalledPlugins();
|
||||||
|
return installedPlugins.filter((dep) =>
|
||||||
|
new RegExp('@nx/powerpack*').test(dep.name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function findInstalledCommunityPlugins(): PackageJson[] {
|
export function findInstalledCommunityPlugins(): PackageJson[] {
|
||||||
const installedPlugins = findInstalledPlugins();
|
const installedPlugins = findInstalledPlugins();
|
||||||
return installedPlugins.filter(
|
return installedPlugins.filter(
|
||||||
|
|||||||
67
packages/nx/src/native/cache/cache.rs
vendored
67
packages/nx/src/native/cache/cache.rs
vendored
@ -4,12 +4,12 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use fs_extra::remove_items;
|
use fs_extra::remove_items;
|
||||||
use napi::bindgen_prelude::*;
|
use napi::bindgen_prelude::*;
|
||||||
|
use regex::Regex;
|
||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use crate::native::cache::expand_outputs::_expand_outputs;
|
use crate::native::cache::expand_outputs::_expand_outputs;
|
||||||
use crate::native::cache::file_ops::_copy;
|
use crate::native::cache::file_ops::_copy;
|
||||||
use crate::native::machine_id::get_machine_id;
|
|
||||||
use crate::native::utils::Normalize;
|
use crate::native::utils::Normalize;
|
||||||
|
|
||||||
#[napi(object)]
|
#[napi(object)]
|
||||||
@ -36,8 +36,7 @@ impl NxCache {
|
|||||||
cache_path: String,
|
cache_path: String,
|
||||||
db_connection: External<Connection>,
|
db_connection: External<Connection>,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let machine_id = get_machine_id();
|
let cache_path = PathBuf::from(&cache_path);
|
||||||
let cache_path = PathBuf::from(&cache_path).join(machine_id);
|
|
||||||
|
|
||||||
create_dir_all(&cache_path)?;
|
create_dir_all(&cache_path)?;
|
||||||
create_dir_all(cache_path.join("terminalOutputs"))?;
|
create_dir_all(cache_path.join("terminalOutputs"))?;
|
||||||
@ -143,7 +142,11 @@ impl NxCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn apply_remote_cache_results(&self, hash: String, result: CachedResult) -> anyhow::Result<()> {
|
pub fn apply_remote_cache_results(
|
||||||
|
&self,
|
||||||
|
hash: String,
|
||||||
|
result: CachedResult,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let terminal_output = result.terminal_output;
|
let terminal_output = result.terminal_output;
|
||||||
write(self.get_task_outputs_path(hash.clone()), terminal_output)?;
|
write(self.get_task_outputs_path(hash.clone()), terminal_output)?;
|
||||||
|
|
||||||
@ -153,14 +156,13 @@ impl NxCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_task_outputs_path_internal(&self, hash: &str) -> PathBuf {
|
fn get_task_outputs_path_internal(&self, hash: &str) -> PathBuf {
|
||||||
self.cache_path
|
self.cache_path.join("terminalOutputs").join(hash)
|
||||||
.join("terminalOutputs")
|
|
||||||
.join(hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn get_task_outputs_path(&self, hash: String) -> String {
|
pub fn get_task_outputs_path(&self, hash: String) -> String {
|
||||||
self.get_task_outputs_path_internal(&hash).to_normalized_string()
|
self.get_task_outputs_path_internal(&hash)
|
||||||
|
.to_normalized_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_to_cache(&self, hash: String, code: i16) -> anyhow::Result<()> {
|
fn record_to_cache(&self, hash: String, code: i16) -> anyhow::Result<()> {
|
||||||
@ -192,11 +194,12 @@ impl NxCache {
|
|||||||
.as_slice(),
|
.as_slice(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
trace!("Copying Files from Cache {:?} -> {:?}", &outputs_path, &self.workspace_root);
|
trace!(
|
||||||
_copy(
|
"Copying Files from Cache {:?} -> {:?}",
|
||||||
outputs_path,
|
&outputs_path,
|
||||||
&self.workspace_root,
|
&self.workspace_root
|
||||||
)?;
|
);
|
||||||
|
_copy(outputs_path, &self.workspace_root)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -224,4 +227,42 @@ impl NxCache {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub fn check_cache_fs_in_sync(&self) -> anyhow::Result<bool> {
|
||||||
|
// Checks that the number of cache records in the database
|
||||||
|
// matches the number of cache directories on the filesystem.
|
||||||
|
// If they don't match, it means that the cache is out of sync.
|
||||||
|
let cache_records_exist = self.db.query_row(
|
||||||
|
"SELECT EXISTS (SELECT 1 FROM cache_outputs)",
|
||||||
|
[],
|
||||||
|
|row| {
|
||||||
|
let exists: bool = row.get(0)?;
|
||||||
|
Ok(exists)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if !cache_records_exist {
|
||||||
|
let hash_regex = Regex::new(r"^\d+$").expect("Hash regex is invalid");
|
||||||
|
let fs_entries = std::fs::read_dir(&self.cache_path)
|
||||||
|
.map_err(anyhow::Error::from)?;
|
||||||
|
|
||||||
|
for entry in fs_entries {
|
||||||
|
let entry = entry?;
|
||||||
|
let is_dir = entry.file_type()?.is_dir();
|
||||||
|
|
||||||
|
if (is_dir) {
|
||||||
|
if let Some(file_name) = entry.file_name().to_str() {
|
||||||
|
if hash_regex.is_match(file_name) {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,13 @@ use crate::native::machine_id::get_machine_id;
|
|||||||
pub fn connect_to_nx_db(
|
pub fn connect_to_nx_db(
|
||||||
cache_dir: String,
|
cache_dir: String,
|
||||||
nx_version: String,
|
nx_version: String,
|
||||||
|
db_name: Option<String>,
|
||||||
) -> anyhow::Result<External<Connection>> {
|
) -> anyhow::Result<External<Connection>> {
|
||||||
let machine_id = get_machine_id();
|
|
||||||
let cache_dir_buf = PathBuf::from(cache_dir);
|
let cache_dir_buf = PathBuf::from(cache_dir);
|
||||||
let db_path = cache_dir_buf.join(format!("{}.db", machine_id));
|
let db_path = cache_dir_buf.join(format!(
|
||||||
|
"{}.db",
|
||||||
|
db_name.unwrap_or_else(get_machine_id)
|
||||||
|
));
|
||||||
create_dir_all(cache_dir_buf)?;
|
create_dir_all(cache_dir_buf)?;
|
||||||
|
|
||||||
let c = create_connection(&db_path)?;
|
let c = create_connection(&db_path)?;
|
||||||
|
|||||||
3
packages/nx/src/native/index.d.ts
vendored
3
packages/nx/src/native/index.d.ts
vendored
@ -35,6 +35,7 @@ export declare class NxCache {
|
|||||||
getTaskOutputsPath(hash: string): string
|
getTaskOutputsPath(hash: string): string
|
||||||
copyFilesFromCache(cachedResult: CachedResult, outputs: Array<string>): void
|
copyFilesFromCache(cachedResult: CachedResult, outputs: Array<string>): void
|
||||||
removeOldCacheRecords(): void
|
removeOldCacheRecords(): void
|
||||||
|
checkCacheFsInSync(): boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare class NxTaskHistory {
|
export declare class NxTaskHistory {
|
||||||
@ -96,7 +97,7 @@ export interface CachedResult {
|
|||||||
outputsPath: string
|
outputsPath: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export declare export function connectToNxDb(cacheDir: string, nxVersion: string): ExternalObject<Connection>
|
export declare export function connectToNxDb(cacheDir: string, nxVersion: string, dbName?: string | undefined | null): ExternalObject<Connection>
|
||||||
|
|
||||||
export declare export function copy(src: string, dest: string): void
|
export declare export function copy(src: string, dest: string): void
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,9 @@ describe('Cache', () => {
|
|||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dbConnection = getDbConnection(join(__dirname, 'temp-db'));
|
const dbConnection = getDbConnection({
|
||||||
|
directory: join(__dirname, 'temp-db'),
|
||||||
|
});
|
||||||
|
|
||||||
taskDetails = new TaskDetails(dbConnection);
|
taskDetails = new TaskDetails(dbConnection);
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,9 @@ describe('NxTaskHistory', () => {
|
|||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dbConnection = getDbConnection(join(__dirname, 'temp-db'));
|
const dbConnection = getDbConnection({
|
||||||
|
directory: join(__dirname, 'temp-db'),
|
||||||
|
});
|
||||||
taskHistory = new NxTaskHistory(dbConnection);
|
taskHistory = new NxTaskHistory(dbConnection);
|
||||||
taskDetails = new TaskDetails(dbConnection);
|
taskDetails = new TaskDetails(dbConnection);
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { isNxCloudUsed } from '../utils/nx-cloud-utils';
|
|||||||
import { readNxJson } from '../config/nx-json';
|
import { readNxJson } from '../config/nx-json';
|
||||||
import { verifyOrUpdateNxCloudClient } from '../nx-cloud/update-manager';
|
import { verifyOrUpdateNxCloudClient } from '../nx-cloud/update-manager';
|
||||||
import { getCloudOptions } from '../nx-cloud/utilities/get-cloud-options';
|
import { getCloudOptions } from '../nx-cloud/utilities/get-cloud-options';
|
||||||
|
import { isCI } from '../utils/is-ci';
|
||||||
|
|
||||||
export type CachedResult = {
|
export type CachedResult = {
|
||||||
terminalOutput: string;
|
terminalOutput: string;
|
||||||
@ -40,15 +41,20 @@ export function getCache(options: DefaultTasksRunnerOptions) {
|
|||||||
|
|
||||||
export class DbCache {
|
export class DbCache {
|
||||||
private cache = new NxCache(workspaceRoot, cacheDir, getDbConnection());
|
private cache = new NxCache(workspaceRoot, cacheDir, getDbConnection());
|
||||||
|
|
||||||
private remoteCache: RemoteCacheV2 | null;
|
private remoteCache: RemoteCacheV2 | null;
|
||||||
private remoteCachePromise: Promise<RemoteCacheV2>;
|
private remoteCachePromise: Promise<RemoteCacheV2>;
|
||||||
|
|
||||||
async setup() {
|
|
||||||
this.remoteCache = await this.getRemoteCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(private readonly options: { nxCloudRemoteCache: RemoteCache }) {}
|
constructor(private readonly options: { nxCloudRemoteCache: RemoteCache }) {}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
// This should be cheap because we've already loaded
|
||||||
|
this.remoteCache = await this.getRemoteCache();
|
||||||
|
if (!this.remoteCache) {
|
||||||
|
this.assertCacheIsValid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async get(task: Task): Promise<CachedResult | null> {
|
async get(task: Task): Promise<CachedResult | null> {
|
||||||
const res = this.cache.get(task.hash);
|
const res = this.cache.get(task.hash);
|
||||||
|
|
||||||
@ -58,7 +64,6 @@ export class DbCache {
|
|||||||
remote: false,
|
remote: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await this.setup();
|
|
||||||
if (this.remoteCache) {
|
if (this.remoteCache) {
|
||||||
// didn't find it locally but we have a remote cache
|
// didn't find it locally but we have a remote cache
|
||||||
// attempt remote cache
|
// attempt remote cache
|
||||||
@ -92,10 +97,10 @@ export class DbCache {
|
|||||||
outputs: string[],
|
outputs: string[],
|
||||||
code: number
|
code: number
|
||||||
) {
|
) {
|
||||||
|
await this.assertCacheIsValid();
|
||||||
return tryAndRetry(async () => {
|
return tryAndRetry(async () => {
|
||||||
this.cache.put(task.hash, terminalOutput, outputs, code);
|
this.cache.put(task.hash, terminalOutput, outputs, code);
|
||||||
|
|
||||||
await this.setup();
|
|
||||||
if (this.remoteCache) {
|
if (this.remoteCache) {
|
||||||
await this.remoteCache.store(
|
await this.remoteCache.store(
|
||||||
task.hash,
|
task.hash,
|
||||||
@ -142,9 +147,59 @@ export class DbCache {
|
|||||||
return await RemoteCacheV2.fromCacheV1(this.options.nxCloudRemoteCache);
|
return await RemoteCacheV2.fromCacheV1(this.options.nxCloudRemoteCache);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
return (
|
||||||
|
(await this.getPowerpackS3Cache()) ??
|
||||||
|
(await this.getPowerpackSharedCache()) ??
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPowerpackS3Cache(): Promise<RemoteCacheV2 | null> {
|
||||||
|
try {
|
||||||
|
const { getRemoteCache } = await import(
|
||||||
|
this.resolvePackage('@nx/powerpack-s3-cache')
|
||||||
|
);
|
||||||
|
return getRemoteCache();
|
||||||
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getPowerpackSharedCache(): Promise<RemoteCacheV2 | null> {
|
||||||
|
try {
|
||||||
|
const { getRemoteCache } = await import(
|
||||||
|
this.resolvePackage('@nx/powerpack-shared-cache')
|
||||||
|
);
|
||||||
|
return getRemoteCache();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolvePackage(pkg: string) {
|
||||||
|
return require.resolve(pkg, {
|
||||||
|
paths: [process.cwd(), workspaceRoot, __dirname],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private assertCacheIsValid() {
|
||||||
|
// User has customized the cache directory - this could be because they
|
||||||
|
// are using a shared cache in the custom directory. The db cache is not
|
||||||
|
// stored in the cache directory, and is keyed by machine ID so they would
|
||||||
|
// hit issues. If we detect this, we can create a fallback db cache in the
|
||||||
|
// custom directory, and check if the entries are there when the main db
|
||||||
|
// cache misses.
|
||||||
|
if (isCI() && !this.cache.checkCacheFsInSync()) {
|
||||||
|
const warning = [
|
||||||
|
`Nx found unrecognized artifacts in the cache directory and will not be able to use them.`,
|
||||||
|
`Nx can only restore artifacts it has metadata about.`,
|
||||||
|
`Read about this warning and how to address it here: https://nx.dev/troubleshooting/unknown-local-cache`,
|
||||||
|
``,
|
||||||
|
].join('\n');
|
||||||
|
console.warn(warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -51,6 +51,7 @@ import { TasksRunner, TaskStatus } from './tasks-runner';
|
|||||||
import { shouldStreamOutput } from './utils';
|
import { shouldStreamOutput } from './utils';
|
||||||
import chalk = require('chalk');
|
import chalk = require('chalk');
|
||||||
import type { Observable } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
|
import { printPowerpackLicense } from '../utils/powerpack';
|
||||||
|
|
||||||
async function getTerminalOutputLifeCycle(
|
async function getTerminalOutputLifeCycle(
|
||||||
initiatingProject: string,
|
initiatingProject: string,
|
||||||
@ -241,6 +242,8 @@ export async function runCommandForTasks(
|
|||||||
|
|
||||||
await renderIsDone;
|
await renderIsDone;
|
||||||
|
|
||||||
|
await printPowerpackLicense();
|
||||||
|
|
||||||
return taskResults;
|
return taskResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,9 +2,32 @@ import { connectToNxDb, ExternalObject } from '../native';
|
|||||||
import { workspaceDataDirectory } from './cache-directory';
|
import { workspaceDataDirectory } from './cache-directory';
|
||||||
import { version as NX_VERSION } from '../../package.json';
|
import { version as NX_VERSION } from '../../package.json';
|
||||||
|
|
||||||
let dbConnection: ExternalObject<any>;
|
const dbConnectionMap = new Map<string, ExternalObject<any>>();
|
||||||
|
|
||||||
export function getDbConnection(directory = workspaceDataDirectory) {
|
export function getDbConnection(
|
||||||
dbConnection ??= connectToNxDb(directory, NX_VERSION);
|
opts: {
|
||||||
return dbConnection;
|
directory?: string;
|
||||||
|
dbName?: string;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
opts.directory ??= workspaceDataDirectory;
|
||||||
|
const key = `${opts.directory}:${opts.dbName ?? 'default'}`;
|
||||||
|
const connection = getEntryOrSet(dbConnectionMap, key, () =>
|
||||||
|
connectToNxDb(opts.directory, NX_VERSION, opts.dbName)
|
||||||
|
);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEntryOrSet<TKey, TVal>(
|
||||||
|
map: Map<TKey, TVal>,
|
||||||
|
key: TKey,
|
||||||
|
defaultValue: () => TVal
|
||||||
|
) {
|
||||||
|
const existing = map.get(key);
|
||||||
|
if (existing) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
const val = defaultValue();
|
||||||
|
map.set(key, val);
|
||||||
|
return val;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,6 +61,13 @@ export function listAlsoAvailableCorePlugins(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function listPowerpackPlugins(): void {
|
||||||
|
const powerpackLink = 'https://nx.dev/plugin-registry';
|
||||||
|
output.log({
|
||||||
|
title: `Available Powerpack Plugins: ${powerpackLink}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function listPluginCapabilities(
|
export async function listPluginCapabilities(
|
||||||
pluginName: string,
|
pluginName: string,
|
||||||
projects: Record<string, ProjectConfiguration>
|
projects: Record<string, ProjectConfiguration>
|
||||||
|
|||||||
44
packages/nx/src/utils/powerpack.ts
Normal file
44
packages/nx/src/utils/powerpack.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { logger } from './logger';
|
||||||
|
import { getPackageManagerCommand } from './package-manager';
|
||||||
|
import { workspaceRoot } from './workspace-root';
|
||||||
|
|
||||||
|
export async function printPowerpackLicense() {
|
||||||
|
try {
|
||||||
|
const { organizationName, seatCount, workspaceCount } =
|
||||||
|
await getPowerpackLicenseInformation();
|
||||||
|
|
||||||
|
logger.log(
|
||||||
|
`Nx Powerpack Licensed to ${organizationName} for ${seatCount} user${
|
||||||
|
seatCount > 1 ? '' : 's'
|
||||||
|
} in ${workspaceCount} workspace${workspaceCount > 1 ? '' : 's'}`
|
||||||
|
);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPowerpackLicenseInformation() {
|
||||||
|
try {
|
||||||
|
const { getPowerpackLicenseInformation } = (await import(
|
||||||
|
// @ts-ignore
|
||||||
|
'@nx/powerpack-license'
|
||||||
|
// TODO(@FrozenPandaz): Provide the right type here.
|
||||||
|
)) as any;
|
||||||
|
// )) as typeof import('@nx/powerpack-license');
|
||||||
|
return getPowerpackLicenseInformation(workspaceRoot);
|
||||||
|
} catch (e) {
|
||||||
|
if ('code' in e && e.code === 'ERR_MODULE_NOT_FOUND') {
|
||||||
|
throw new NxPowerpackNotInstalledError(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NxPowerpackNotInstalledError extends Error {
|
||||||
|
constructor(e: Error) {
|
||||||
|
super(
|
||||||
|
`The "@nx/powerpack-license" package is needed to use Nx Powerpack enabled features. Please install the @nx/powerpack-license with ${
|
||||||
|
getPackageManagerCommand().addDev
|
||||||
|
} @nx/powerpack-license`,
|
||||||
|
{ cause: e }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user