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:
Jason Jean 2024-09-18 13:41:24 -04:00 committed by GitHub
parent fb91ed5e7f
commit b06f515059
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 455 additions and 32 deletions

View 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. |

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View 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. |

View File

@ -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",

View File

@ -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)

View File

@ -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.'
);
}
}
});
}

View File

@ -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);
},
};

View File

@ -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) =>

View File

@ -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',

View File

@ -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);
},
};
}
}

View File

@ -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(

View File

@ -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)
}
}
}

View File

@ -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)?;

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
}
/**

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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>

View 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 }
);
}
}