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": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "activate-powerpack",
|
||||
"path": "/nx-api/nx/documents/activate-powerpack",
|
||||
"id": "activate-powerpack",
|
||||
"isExternal": false,
|
||||
"children": [],
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"name": "reset",
|
||||
"path": "/nx-api/nx/documents/reset",
|
||||
|
||||
@ -1880,6 +1880,17 @@
|
||||
"tags": ["cache-task-results", "distribute-task-execution"],
|
||||
"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": {
|
||||
"id": "reset",
|
||||
"name": "reset",
|
||||
|
||||
@ -235,6 +235,13 @@
|
||||
"name": "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.",
|
||||
"file": "generated/packages/nx/documents/reset",
|
||||
|
||||
@ -1859,6 +1859,17 @@
|
||||
"tags": ["cache-task-results", "distribute-task-execution"],
|
||||
"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",
|
||||
"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"],
|
||||
"file": "generated/cli/connect"
|
||||
},
|
||||
{
|
||||
"name": "activate-powerpack",
|
||||
"tags": ["cache-task-results"],
|
||||
"id": "activate-powerpack",
|
||||
"file": "generated/cli/activate-powerpack"
|
||||
},
|
||||
{
|
||||
"name": "reset",
|
||||
"id": "reset",
|
||||
|
||||
@ -556,6 +556,7 @@
|
||||
- [report](/nx-api/nx/documents/report)
|
||||
- [list](/nx-api/nx/documents/list)
|
||||
- [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)
|
||||
- [repair](/nx-api/nx/documents/repair)
|
||||
- [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[];
|
||||
}
|
||||
|
||||
export const yargsAddCommand: CommandModule<
|
||||
Record<string, unknown>,
|
||||
AddOptions
|
||||
> = {
|
||||
export const yargsAddCommand: CommandModule<{}, AddOptions> = {
|
||||
command: 'add <packageSpecifier>',
|
||||
describe: 'Install a plugin and initialize it.',
|
||||
builder: (yargs) =>
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
listPlugins,
|
||||
} from '../../utils/plugins';
|
||||
import { workspaceRoot } from '../../utils/workspace-root';
|
||||
import { listPowerpackPlugins } from '../../utils/plugins/output';
|
||||
|
||||
export interface ListArgs {
|
||||
/** The name of an installed plugin to query */
|
||||
@ -46,6 +47,7 @@ export async function listHandler(args: ListArgs): Promise<void> {
|
||||
}
|
||||
listPlugins(installedPlugins, 'Installed plugins:');
|
||||
listAlsoAvailableCorePlugins(installedPlugins);
|
||||
listPowerpackPlugins();
|
||||
|
||||
output.note({
|
||||
title: 'Community Plugins',
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import * as chalk from 'chalk';
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import { yargsActivatePowerpackCommand } from './activate-powerpack/command-object';
|
||||
import {
|
||||
yargsAffectedBuildCommand,
|
||||
yargsAffectedCommand,
|
||||
@ -63,6 +64,7 @@ export const commandsObject = yargs
|
||||
.parserConfiguration(parserConfiguration)
|
||||
.usage(chalk.bold('Smart Monorepos · Fast CI'))
|
||||
.demandCommand(1, '')
|
||||
.command(yargsActivatePowerpackCommand)
|
||||
.command(yargsAddCommand)
|
||||
.command(yargsAffectedBuildCommand)
|
||||
.command(yargsAffectedCommand)
|
||||
@ -98,9 +100,27 @@ export const commandsObject = yargs
|
||||
.command(yargsNxInfixCommand)
|
||||
.command(yargsLoginCommand)
|
||||
.command(yargsLogoutCommand)
|
||||
.command(resolveConformanceCommandObject())
|
||||
.scriptName('nx')
|
||||
.help()
|
||||
// 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
|
||||
// hit, as the implementation in nx.ts is hit first and calls process.exit(0).
|
||||
.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 { ProjectGraph } from '../../config/project-graph';
|
||||
import { ProjectGraphError } from '../../project-graph/error-types';
|
||||
import { getPowerpackLicenseInformation } from '../../utils/powerpack';
|
||||
|
||||
const nxPackageJson = readJsonFile<typeof import('../../../package.json')>(
|
||||
join(__dirname, '../../../package.json')
|
||||
@ -39,6 +40,7 @@ export const packagesWeCareAbout = [
|
||||
|
||||
export const patternsWeIgnoreInCommunityReport: Array<string | RegExp> = [
|
||||
...packagesWeCareAbout,
|
||||
new RegExp('@nx/powerpack*'),
|
||||
'@schematics/angular',
|
||||
new RegExp('@angular/*'),
|
||||
'@nestjs/schematics',
|
||||
@ -58,7 +60,9 @@ export async function reportHandler() {
|
||||
const {
|
||||
pm,
|
||||
pmVersion,
|
||||
powerpackLicense,
|
||||
localPlugins,
|
||||
powerpackPlugins,
|
||||
communityPlugins,
|
||||
registeredPlugins,
|
||||
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) {
|
||||
bodyLines.push(LINE_SEPARATOR);
|
||||
bodyLines.push('Registered Plugins:');
|
||||
@ -147,6 +181,9 @@ export async function reportHandler() {
|
||||
export interface ReportData {
|
||||
pm: PackageManager;
|
||||
pmVersion: string;
|
||||
// TODO(@FrozenPandaz): Provide the right type here.
|
||||
powerpackLicense: any | null;
|
||||
powerpackPlugins: PackageJson[];
|
||||
localPlugins: string[];
|
||||
communityPlugins: PackageJson[];
|
||||
registeredPlugins: string[];
|
||||
@ -174,6 +211,7 @@ export async function getReportData(): Promise<ReportData> {
|
||||
|
||||
const nxJson = readNxJson();
|
||||
const localPlugins = await findLocalPlugins(graph, nxJson);
|
||||
const powerpackPlugins = findInstalledPowerpackPlugins();
|
||||
const communityPlugins = findInstalledCommunityPlugins();
|
||||
const registeredPlugins = findRegisteredPluginsBeingUsed(nxJson);
|
||||
|
||||
@ -193,8 +231,15 @@ export async function getReportData(): Promise<ReportData> {
|
||||
|
||||
const native = isNativeAvailable();
|
||||
|
||||
let powerpackLicense = null;
|
||||
try {
|
||||
powerpackLicense = await getPowerpackLicenseInformation();
|
||||
} catch {}
|
||||
|
||||
return {
|
||||
pm,
|
||||
powerpackLicense,
|
||||
powerpackPlugins,
|
||||
pmVersion,
|
||||
localPlugins,
|
||||
communityPlugins,
|
||||
@ -294,6 +339,13 @@ export function findMisalignedPackagesForPackage(
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function findInstalledPowerpackPlugins(): PackageJson[] {
|
||||
const installedPlugins = findInstalledPlugins();
|
||||
return installedPlugins.filter((dep) =>
|
||||
new RegExp('@nx/powerpack*').test(dep.name)
|
||||
);
|
||||
}
|
||||
|
||||
export function findInstalledCommunityPlugins(): PackageJson[] {
|
||||
const installedPlugins = findInstalledPlugins();
|
||||
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 napi::bindgen_prelude::*;
|
||||
use regex::Regex;
|
||||
use rusqlite::{params, Connection, OptionalExtension};
|
||||
use tracing::trace;
|
||||
|
||||
use crate::native::cache::expand_outputs::_expand_outputs;
|
||||
use crate::native::cache::file_ops::_copy;
|
||||
use crate::native::machine_id::get_machine_id;
|
||||
use crate::native::utils::Normalize;
|
||||
|
||||
#[napi(object)]
|
||||
@ -36,8 +36,7 @@ impl NxCache {
|
||||
cache_path: String,
|
||||
db_connection: External<Connection>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let machine_id = get_machine_id();
|
||||
let cache_path = PathBuf::from(&cache_path).join(machine_id);
|
||||
let cache_path = PathBuf::from(&cache_path);
|
||||
|
||||
create_dir_all(&cache_path)?;
|
||||
create_dir_all(cache_path.join("terminalOutputs"))?;
|
||||
@ -143,7 +142,11 @@ impl NxCache {
|
||||
}
|
||||
|
||||
#[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;
|
||||
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 {
|
||||
self.cache_path
|
||||
.join("terminalOutputs")
|
||||
.join(hash)
|
||||
self.cache_path.join("terminalOutputs").join(hash)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
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<()> {
|
||||
@ -192,11 +194,12 @@ impl NxCache {
|
||||
.as_slice(),
|
||||
)?;
|
||||
|
||||
trace!("Copying Files from Cache {:?} -> {:?}", &outputs_path, &self.workspace_root);
|
||||
_copy(
|
||||
outputs_path,
|
||||
&self.workspace_root,
|
||||
)?;
|
||||
trace!(
|
||||
"Copying Files from Cache {:?} -> {:?}",
|
||||
&outputs_path,
|
||||
&self.workspace_root
|
||||
);
|
||||
_copy(outputs_path, &self.workspace_root)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -224,4 +227,42 @@ impl NxCache {
|
||||
|
||||
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(
|
||||
cache_dir: String,
|
||||
nx_version: String,
|
||||
db_name: Option<String>,
|
||||
) -> anyhow::Result<External<Connection>> {
|
||||
let machine_id = get_machine_id();
|
||||
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)?;
|
||||
|
||||
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
|
||||
copyFilesFromCache(cachedResult: CachedResult, outputs: Array<string>): void
|
||||
removeOldCacheRecords(): void
|
||||
checkCacheFsInSync(): boolean
|
||||
}
|
||||
|
||||
export declare class NxTaskHistory {
|
||||
@ -96,7 +97,7 @@ export interface CachedResult {
|
||||
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
|
||||
|
||||
|
||||
@ -16,7 +16,9 @@ describe('Cache', () => {
|
||||
force: true,
|
||||
});
|
||||
|
||||
const dbConnection = getDbConnection(join(__dirname, 'temp-db'));
|
||||
const dbConnection = getDbConnection({
|
||||
directory: join(__dirname, 'temp-db'),
|
||||
});
|
||||
|
||||
taskDetails = new TaskDetails(dbConnection);
|
||||
|
||||
|
||||
@ -17,7 +17,9 @@ describe('NxTaskHistory', () => {
|
||||
force: true,
|
||||
});
|
||||
|
||||
const dbConnection = getDbConnection(join(__dirname, 'temp-db'));
|
||||
const dbConnection = getDbConnection({
|
||||
directory: join(__dirname, 'temp-db'),
|
||||
});
|
||||
taskHistory = new NxTaskHistory(dbConnection);
|
||||
taskDetails = new TaskDetails(dbConnection);
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import { isNxCloudUsed } from '../utils/nx-cloud-utils';
|
||||
import { readNxJson } from '../config/nx-json';
|
||||
import { verifyOrUpdateNxCloudClient } from '../nx-cloud/update-manager';
|
||||
import { getCloudOptions } from '../nx-cloud/utilities/get-cloud-options';
|
||||
import { isCI } from '../utils/is-ci';
|
||||
|
||||
export type CachedResult = {
|
||||
terminalOutput: string;
|
||||
@ -40,15 +41,20 @@ export function getCache(options: DefaultTasksRunnerOptions) {
|
||||
|
||||
export class DbCache {
|
||||
private cache = new NxCache(workspaceRoot, cacheDir, getDbConnection());
|
||||
|
||||
private remoteCache: RemoteCacheV2 | null;
|
||||
private remoteCachePromise: Promise<RemoteCacheV2>;
|
||||
|
||||
async setup() {
|
||||
this.remoteCache = await this.getRemoteCache();
|
||||
}
|
||||
|
||||
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> {
|
||||
const res = this.cache.get(task.hash);
|
||||
|
||||
@ -58,7 +64,6 @@ export class DbCache {
|
||||
remote: false,
|
||||
};
|
||||
}
|
||||
await this.setup();
|
||||
if (this.remoteCache) {
|
||||
// didn't find it locally but we have a remote cache
|
||||
// attempt remote cache
|
||||
@ -92,10 +97,10 @@ export class DbCache {
|
||||
outputs: string[],
|
||||
code: number
|
||||
) {
|
||||
await this.assertCacheIsValid();
|
||||
return tryAndRetry(async () => {
|
||||
this.cache.put(task.hash, terminalOutput, outputs, code);
|
||||
|
||||
await this.setup();
|
||||
if (this.remoteCache) {
|
||||
await this.remoteCache.store(
|
||||
task.hash,
|
||||
@ -142,9 +147,59 @@ export class DbCache {
|
||||
return await RemoteCacheV2.fromCacheV1(this.options.nxCloudRemoteCache);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 chalk = require('chalk');
|
||||
import type { Observable } from 'rxjs';
|
||||
import { printPowerpackLicense } from '../utils/powerpack';
|
||||
|
||||
async function getTerminalOutputLifeCycle(
|
||||
initiatingProject: string,
|
||||
@ -241,6 +242,8 @@ export async function runCommandForTasks(
|
||||
|
||||
await renderIsDone;
|
||||
|
||||
await printPowerpackLicense();
|
||||
|
||||
return taskResults;
|
||||
}
|
||||
|
||||
|
||||
@ -2,9 +2,32 @@ import { connectToNxDb, ExternalObject } from '../native';
|
||||
import { workspaceDataDirectory } from './cache-directory';
|
||||
import { version as NX_VERSION } from '../../package.json';
|
||||
|
||||
let dbConnection: ExternalObject<any>;
|
||||
const dbConnectionMap = new Map<string, ExternalObject<any>>();
|
||||
|
||||
export function getDbConnection(directory = workspaceDataDirectory) {
|
||||
dbConnection ??= connectToNxDb(directory, NX_VERSION);
|
||||
return dbConnection;
|
||||
export function getDbConnection(
|
||||
opts: {
|
||||
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(
|
||||
pluginName: string,
|
||||
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