🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

nx

Package Overview
Dependencies
Maintainers
8
Versions
2646
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nx - npm Package Compare versions

Comparing version
23.2.0-beta.2
to
23.2.0-beta.3
+8
dist/src/utils/catalog/bun-manager-utils.d.ts
import type { Tree } from '../../generators/tree';
import type { CatalogDefinitions } from './types';
export declare function readBunCatalogDefinitions(filename: string, treeOrRoot: Tree | string, cache: Map<string, CatalogDefinitions | null>): CatalogDefinitions | null;
export declare function updateBunCatalogVersionsInFile(filename: string, treeOrRoot: Tree | string, updates: Array<{
packageName: string;
version: string;
catalogName?: string;
}>): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readBunCatalogDefinitions = readBunCatalogDefinitions;
exports.updateBunCatalogVersionsInFile = updateBunCatalogVersionsInFile;
const jsonc_parser_1 = require("jsonc-parser");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const fileutils_1 = require("../fileutils");
const json_1 = require("../json");
const output_1 = require("../output");
// Extracts catalog definitions from a parsed bun package.json, normalizing the
// top-level and `workspaces`-nested locations into a single CatalogDefinitions.
// Bun treats the locations as all-or-nothing: when either catalog field exists
// under `workspaces`, the top-level fields are ignored entirely rather than
// merged.
function normalizeBunCatalogDefinitions(packageJson) {
const nested = packageJson.workspaces && !Array.isArray(packageJson.workspaces)
? packageJson.workspaces
: undefined;
const source = nested?.catalog !== undefined || nested?.catalogs !== undefined
? nested
: packageJson;
const { catalog, catalogs } = source;
if (!catalog && !catalogs) {
return null;
}
return { catalog, catalogs };
}
function readBunCatalogConfigFromFs(filename, fullPath) {
try {
return normalizeBunCatalogDefinitions((0, fileutils_1.readJsonFile)(fullPath));
}
catch (error) {
output_1.output.warn({
title: `Unable to parse ${filename}`,
bodyLines: [error.toString()],
});
return null;
}
}
function readBunCatalogConfigFromTree(filename, tree) {
const content = tree.read(filename, 'utf-8');
try {
return normalizeBunCatalogDefinitions((0, json_1.parseJson)(content));
}
catch (error) {
output_1.output.warn({
title: `Unable to parse ${filename}`,
bodyLines: [error.toString()],
});
return null;
}
}
// Mirror of readCatalogDefinitions for bun's package.json-based catalogs: the
// fs (string-root) branch is cached per pass, the Tree branch stays live.
function readBunCatalogDefinitions(filename, treeOrRoot, cache) {
if (typeof treeOrRoot === 'string') {
if (cache.has(treeOrRoot)) {
return cache.get(treeOrRoot);
}
const configPath = (0, node_path_1.join)(treeOrRoot, filename);
const defs = (0, node_fs_1.existsSync)(configPath)
? readBunCatalogConfigFromFs(filename, configPath)
: null;
cache.set(treeOrRoot, defs);
return defs;
}
if (!treeOrRoot.exists(filename)) {
return null;
}
return readBunCatalogConfigFromTree(filename, treeOrRoot);
}
function isRecord(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
// Resolves the jsonc path a catalog update should target. Follows the same
// all-or-nothing routing as the reader: updates land in the `workspaces`
// location when it holds a catalog field, otherwise at the top level.
function resolveBunCatalogTargetPath(packageJson, packageName, catalogName) {
const nested = packageJson.workspaces && !Array.isArray(packageJson.workspaces)
? packageJson.workspaces
: undefined;
const nestedIsActive = nested?.catalog !== undefined || nested?.catalogs !== undefined;
const prefix = nestedIsActive ? ['workspaces'] : [];
return catalogName
? [...prefix, 'catalogs', catalogName, packageName]
: [...prefix, 'catalog', packageName];
}
// jsonc-parser's `modify` creates missing intermediate objects but throws on
// null (or other non-object) ones — the JSON counterpart of pnpm's empty
// `catalog:` placeholder. Walk the target path and, at the first non-object
// step, replace that node wholesale with the remaining path nested as fresh
// objects.
function planBunCatalogEdit(packageJson, targetPath, version) {
let node = packageJson;
for (let i = 0; i < targetPath.length - 1; i++) {
if (!isRecord(node)) {
break;
}
const next = node[targetPath[i]];
if (next !== undefined && !isRecord(next)) {
const value = targetPath
.slice(i + 1)
.reduceRight((acc, key) => ({ [key]: acc }), version);
return { path: targetPath.slice(0, i + 1), value };
}
node = next;
}
return { path: targetPath, value: version };
}
function updateBunCatalogVersionsInFile(filename, treeOrRoot, updates) {
let checkExists;
let readContent;
let writeContent;
if (typeof treeOrRoot === 'string') {
const configPath = (0, node_path_1.join)(treeOrRoot, filename);
checkExists = () => (0, node_fs_1.existsSync)(configPath);
readContent = () => (0, node_fs_1.readFileSync)(configPath, 'utf-8');
writeContent = (content) => (0, node_fs_1.writeFileSync)(configPath, content, 'utf-8');
}
else {
checkExists = () => treeOrRoot.exists(filename);
readContent = () => treeOrRoot.read(filename, 'utf-8');
writeContent = (content) => treeOrRoot.write(filename, content);
}
if (!checkExists()) {
output_1.output.warn({
title: `No ${filename} found`,
bodyLines: [
`Cannot update catalog versions without a ${filename} file.`,
`Create a ${filename} file to use catalogs.`,
],
});
return;
}
try {
let content = readContent();
// parseJson surfaces a genuine syntax error here rather than letting a
// broken file be silently rewritten.
let packageJson = (0, json_1.parseJson)(content);
let hasChanges = false;
for (const update of updates) {
const { packageName, version, catalogName } = update;
const targetPath = resolveBunCatalogTargetPath(packageJson, packageName, catalogName);
// `modify` emits an edit even for an identical value, so check first to
// keep an already-matching file untouched.
const currentValue = targetPath.reduce((node, key) => (isRecord(node) ? node[key] : undefined), packageJson);
if (currentValue === version) {
continue;
}
const { path, value } = planBunCatalogEdit(packageJson, targetPath, version);
const edits = (0, jsonc_parser_1.modify)(content, path, value, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
if (edits.length > 0) {
content = (0, jsonc_parser_1.applyEdits)(content, edits);
// Re-parse so the next update routes against the just-applied edit
// (e.g. a null `catalog` placeholder replaced with a fresh map).
packageJson = (0, json_1.parseJson)(content);
hasChanges = true;
}
}
if (hasChanges) {
writeContent(content);
}
}
catch (error) {
output_1.output.error({
title: 'Failed to update catalog versions',
bodyLines: [error instanceof Error ? error.message : String(error)],
});
throw error;
}
}
import type { Tree } from '../../generators/tree';
import { type CatalogManager } from './manager';
import type { CatalogDefinitions, CatalogReference, CatalogReferenceMatch } from './types';
/**
* Bun-specific catalog manager implementation.
*
* Bun declares catalogs in the root package.json `catalog`/`catalogs` fields,
* either at the top level or nested under the object form of `workspaces`.
* Unlike pnpm, the name "default" is not special: `catalog:` resolves only
* against `catalog`, and `catalog:default` against `catalogs.default`.
*/
export declare class BunCatalogManager implements CatalogManager {
readonly name = "bun";
readonly catalogProtocol = "catalog:";
private definitionsByRoot;
isCatalogReference(version: string): boolean;
parseCatalogReference(version: string): CatalogReference | null;
getCatalogDefinitionFilePaths(): string[];
getCatalogDefinitions(treeOrRoot: Tree | string): CatalogDefinitions | null;
resolveCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): string | null;
getCatalogReferencesForPackage(treeOrRoot: Tree | string, packageName: string): CatalogReferenceMatch[];
validateCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): void;
updateCatalogVersions(treeOrRoot: Tree | string, updates: Array<{
packageName: string;
version: string;
catalogName?: string;
}>): void;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BunCatalogManager = void 0;
const manager_1 = require("./manager");
const bun_manager_utils_1 = require("./bun-manager-utils");
const BUN_CATALOG_FILENAME = 'package.json';
/**
* Bun-specific catalog manager implementation.
*
* Bun declares catalogs in the root package.json `catalog`/`catalogs` fields,
* either at the top level or nested under the object form of `workspaces`.
* Unlike pnpm, the name "default" is not special: `catalog:` resolves only
* against `catalog`, and `catalog:default` against `catalogs.default`.
*/
class BunCatalogManager {
constructor() {
this.name = 'bun';
this.catalogProtocol = 'catalog:';
// Parsed fs-root definitions, cached per pass. See readBunCatalogDefinitions.
this.definitionsByRoot = new Map();
}
isCatalogReference(version) {
return version.startsWith(this.catalogProtocol);
}
parseCatalogReference(version) {
if (!this.isCatalogReference(version)) {
return null;
}
const catalogName = version.substring(this.catalogProtocol.length).trim();
// Only an empty/whitespace name selects the default catalog; "default" is
// a regular named catalog in bun.
const isDefault = !catalogName;
return {
catalogName: isDefault ? undefined : catalogName,
isDefaultCatalog: isDefault,
};
}
getCatalogDefinitionFilePaths() {
return [BUN_CATALOG_FILENAME];
}
getCatalogDefinitions(treeOrRoot) {
return (0, bun_manager_utils_1.readBunCatalogDefinitions)(BUN_CATALOG_FILENAME, treeOrRoot, this.definitionsByRoot);
}
resolveCatalogReference(treeOrRoot, packageName, version) {
const catalogRef = this.parseCatalogReference(version);
if (!catalogRef) {
return null;
}
const catalogDefs = this.getCatalogDefinitions(treeOrRoot);
if (!catalogDefs) {
return null;
}
let catalogToUse;
if (catalogRef.isDefaultCatalog) {
catalogToUse = catalogDefs.catalog;
}
else if (catalogRef.catalogName) {
catalogToUse = catalogDefs.catalogs?.[catalogRef.catalogName];
}
return catalogToUse?.[packageName] || null;
}
getCatalogReferencesForPackage(treeOrRoot, packageName) {
return (0, manager_1.collectCatalogReferencesForPackage)(this, treeOrRoot, packageName);
}
validateCatalogReference(treeOrRoot, packageName, version) {
const catalogRef = this.parseCatalogReference(version);
if (!catalogRef) {
throw new Error(`Invalid catalog reference syntax: "${version}". Expected format: "catalog:" or "catalog:name"`);
}
const catalogDefs = this.getCatalogDefinitions(treeOrRoot);
if (!catalogDefs) {
throw new Error((0, manager_1.formatCatalogError)(`Cannot get Bun catalog definitions. No catalog defined in ${BUN_CATALOG_FILENAME}.`, [
`Add a "catalog" or "catalogs" field to ${BUN_CATALOG_FILENAME} in your workspace root`,
]));
}
let catalogToUse;
if (catalogRef.isDefaultCatalog) {
catalogToUse = catalogDefs.catalog;
if (!catalogToUse) {
const availableCatalogs = Object.keys(catalogDefs.catalogs || {});
const suggestions = [
`Define a default catalog in ${BUN_CATALOG_FILENAME} under the "catalog" key`,
];
if (availableCatalogs.length > 0) {
suggestions.push(`Or select from the available named catalogs: ${availableCatalogs
.map((c) => `"catalog:${c}"`)
.join(', ')}`);
}
throw new Error((0, manager_1.formatCatalogError)(`No default catalog defined in ${BUN_CATALOG_FILENAME}`, suggestions));
}
}
else if (catalogRef.catalogName) {
catalogToUse = catalogDefs.catalogs?.[catalogRef.catalogName];
if (!catalogToUse) {
const availableCatalogs = Object.keys(catalogDefs.catalogs || {});
const suggestions = [
`Define the catalog in ${BUN_CATALOG_FILENAME} under the "catalogs" key`,
];
if (availableCatalogs.length > 0) {
suggestions.push(`Or select from the available named catalogs: ${availableCatalogs
.map((c) => `"catalog:${c}"`)
.join(', ')}`);
}
if (catalogDefs.catalog) {
suggestions.push(`Or use the default catalog ("catalog:")`);
}
throw new Error((0, manager_1.formatCatalogError)(`Catalog "${catalogRef.catalogName}" not found in ${BUN_CATALOG_FILENAME}`, suggestions));
}
}
if (!catalogToUse[packageName]) {
const catalogName = catalogRef.isDefaultCatalog
? 'default catalog ("catalog")'
: `catalog '${catalogRef.catalogName}'`;
const availablePackages = Object.keys(catalogToUse);
const suggestions = [
`Add "${packageName}" to ${catalogName} in ${BUN_CATALOG_FILENAME}`,
];
if (availablePackages.length > 0) {
suggestions.push(`Or select from the available packages in ${catalogName}: ${availablePackages
.map((p) => `"${p}"`)
.join(', ')}`);
}
throw new Error((0, manager_1.formatCatalogError)(`Package "${packageName}" not found in ${catalogName}`, suggestions));
}
}
updateCatalogVersions(treeOrRoot, updates) {
(0, bun_manager_utils_1.updateBunCatalogVersionsInFile)(BUN_CATALOG_FILENAME, treeOrRoot, updates);
}
}
exports.BunCatalogManager = BunCatalogManager;
import type { NxJsonConfiguration } from '../config/nx-json';
/**
* The workspace's analytics identity: the Nx Cloud id when the workspace has
* one (most stable — it survives repo moves and renames), else the repo key.
* Null when neither is available, in which case nothing is reported.
*/
export declare function generateWorkspaceId(root: string, nxJson: NxJsonConfiguration | null): string | null;
/**
* Derive the stable, unsalted key identifying this workspace in the
* repoTelemetry registry: `sha256(<repo identity> + '#' + <workspace path
* relative to the git root>)`.
*
* The repo identity is the normalized `domain/slug` from the git remote
* (protocol-independent: ssh, https, and token-authenticated URLs of the
* same repo produce the same key), falling back to the first-commit SHA
* when no remote exists. Returns null when no identity is derivable — not
* a git repository, or a shallow clone without a remote.
*/
export declare function deriveRepoKey(directory: string): string | null;
export declare function computeRepoKey(identity: string, relativePath: string): string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateWorkspaceId = generateWorkspaceId;
exports.deriveRepoKey = deriveRepoKey;
exports.computeRepoKey = computeRepoKey;
const crypto_1 = require("crypto");
const git_utils_1 = require("./git-utils");
/**
* The workspace's analytics identity: the Nx Cloud id when the workspace has
* one (most stable — it survives repo moves and renames), else the repo key.
* Null when neither is available, in which case nothing is reported.
*/
function generateWorkspaceId(root, nxJson) {
const nxCloudId = nxJson?.nxCloudId ?? nxJson?.nxCloudAccessToken;
if (nxCloudId) {
return nxCloudId;
}
return deriveRepoKey(root);
}
/**
* Derive the stable, unsalted key identifying this workspace in the
* repoTelemetry registry: `sha256(<repo identity> + '#' + <workspace path
* relative to the git root>)`.
*
* The repo identity is the normalized `domain/slug` from the git remote
* (protocol-independent: ssh, https, and token-authenticated URLs of the
* same repo produce the same key), falling back to the first-commit SHA
* when no remote exists. Returns null when no identity is derivable — not
* a git repository, or a shallow clone without a remote.
*/
function deriveRepoKey(directory) {
const identity = getRepoIdentity(directory);
if (!identity) {
return null;
}
return computeRepoKey(identity, (0, git_utils_1.getGitRootRelativePath)(directory) ?? '');
}
function computeRepoKey(identity, relativePath) {
return (0, crypto_1.createHash)('sha256')
.update(`${identity}#${relativePath}`)
.digest('hex');
}
function getRepoIdentity(directory) {
const remote = (0, git_utils_1.getVcsRemoteInfo)(directory);
if (remote) {
// Hosts route case-insensitively and hold one canonical casing, so a
// hand-typed remote must key the same as the canonical one the claim
// flow derives from the host's API.
return `${remote.domain}/${remote.slug}`.toLowerCase().replace(/\/+$/, '');
}
// Without a remote the first commit is the only stable identity, and a
// shallow clone doesn't have a real one.
return (0, git_utils_1.isShallowRepository)(directory) ? null : (0, git_utils_1.getFirstCommitSha)(directory);
}
+1
-0

@@ -8,2 +8,3 @@ #!/usr/bin/env node

if (process.env.FORCE_COLOR === '0') {
process.env.NX_ORIGINAL_FORCE_COLOR = '0';
process.env.NO_COLOR = '1';

@@ -10,0 +11,0 @@ delete process.env.FORCE_COLOR;

+9
-7

@@ -22,3 +22,3 @@ "use strict";

const is_ci_1 = require("../utils/is-ci");
const analytics_prompt_1 = require("../utils/analytics-prompt");
const workspace_id_1 = require("../utils/workspace-id");
const db_connection_1 = require("../utils/db-connection");

@@ -62,7 +62,7 @@ // Conditionally import telemetry functions only on non-WASM platforms

try {
if (!isAnalyticsEnabled()) {
const nxJson = (0, nx_json_1.readNxJson)(workspace_root_1.workspaceRoot);
if (!isAnalyticsEnabled(nxJson)) {
return;
}
const nxJson = (0, nx_json_1.readNxJson)(workspace_root_1.workspaceRoot);
const workspaceId = (0, analytics_prompt_1.generateWorkspaceId)();
const workspaceId = (0, workspace_id_1.generateWorkspaceId)(workspace_root_1.workspaceRoot, nxJson);
if (!workspaceId) {

@@ -73,3 +73,6 @@ // Not a git repo — no telemetry

const isNxCloud = !!(nxJson?.nxCloudId ?? nxJson?.nxCloudAccessToken);
const userId = await getTelemetryUserId(workspaceId);
// A CI fleet is not a user: shared images bake in /etc/machine-id, so a
// uid would collapse whole fleets into one GA "user" (and trip per-user
// collection caps). GA falls back to cid = workspace for CI traffic.
const userId = (0, is_ci_1.isCI)() ? undefined : await getTelemetryUserId(workspaceId);
const packageManagerInfo = getPackageManagerInfo();

@@ -238,4 +241,3 @@ const nodeVersion = (0, semver_1.parse)(process.version);

}
function isAnalyticsEnabled() {
const nxJson = (0, nx_json_1.readNxJson)(workspace_root_1.workspaceRoot);
function isAnalyticsEnabled(nxJson) {
return nxJson?.analytics === true;

@@ -242,0 +244,0 @@ }

@@ -53,2 +53,3 @@ import { ProjectFileMap, ProjectGraph, ProjectGraphDependency, ProjectGraphProjectNode } from '../../config/project-graph';

}, affectedProjects: string[]): Promise<void>;
export declare function getExpandedTaskInputs(depGraphClientResponse: ProjectGraphClientResponse, expandedTaskInputsCache: Map<string, Record<string, string[]>>, taskId: string): Promise<Record<string, string[]>>;
/**

@@ -55,0 +56,0 @@ * The data type that `nx graph --file graph.json` or `nx build --graph graph.json` contains

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateGraph = generateGraph;
exports.getExpandedTaskInputs = getExpandedTaskInputs;
const tslib_1 = require("tslib");

@@ -12,3 +13,2 @@ const crypto_1 = require("crypto");

const node_url_1 = require("node:url");
const open_1 = tslib_1.__importDefault(require("open"));
const node_path_1 = require("node:path");

@@ -38,2 +38,3 @@ const net = tslib_1.__importStar(require("node:net"));

const nx_cloud_utils_1 = require("../../utils/nx-cloud-utils");
const split_target_1 = require("../../utils/split-target");
// maps file extention to MIME types

@@ -354,3 +355,7 @@ const mimeType = {

if (args.open) {
(0, open_1.default)(url.toString());
new Function('return import("open")')()
.then((m) => m.default(url.toString()))
.catch(() => {
// Ignore errors when opening browser (e.g. no browser available)
});
}

@@ -444,3 +449,3 @@ return new Promise((res) => {

res.writeHead(200, { 'Content-Type': 'application/json' });
const inputs = await getExpandedTaskInputs(taskId);
const inputs = await getExpandedTaskInputs(currentProjectGraphClientResponse, expandedTaskInputsCache, taskId);
node_perf_hooks_1.performance.mark('task input generation:end');

@@ -830,3 +835,3 @@ res.end(JSON.stringify({ [taskId]: inputs }));

}
async function getExpandedTaskInputs(taskId) {
async function getExpandedTaskInputs(depGraphClientResponse, expandedTaskInputsCache, taskId) {
// Check cache first

@@ -837,3 +842,7 @@ if (expandedTaskInputsCache.has(taskId)) {

// Use the optimized version that only creates the specific task graph needed
const [projectName, targetName, configuration] = taskId.split(':');
// Use colon-aware splitting so that target names containing colons
// (e.g. "test:integration") are parsed correctly instead of being
// mistaken for a target + configuration pair.
const projectNodes = Object.fromEntries(depGraphClientResponse.projects.map((p) => [p.name, p]));
const [projectName, targetName, configuration] = (0, split_target_1.splitTargetFromNodes)(taskId, projectNodes, { silent: true });
const taskGraphResponse = await createTaskGraphForTargetsAndProjects([targetName], [projectName], configuration);

@@ -844,3 +853,3 @@ const allWorkspaceFiles = await (0, all_file_data_1.allFileData)();

if (inputs) {
result = expandInputs(inputs, currentProjectGraphClientResponse.projects.find((p) => p.name === projectName), allWorkspaceFiles, currentProjectGraphClientResponse);
result = expandInputs(inputs, depGraphClientResponse.projects.find((p) => p.name === projectName), allWorkspaceFiles, depGraphClientResponse);
}

@@ -847,0 +856,0 @@ // Cache the result

@@ -29,3 +29,2 @@ "use strict";

const ora = require('ora');
const open = require('open');
function onlyDefaultRunnerIsUsed(nxJson) {

@@ -165,2 +164,3 @@ const defaultRunner = nxJson.tasksRunnerOptions?.default?.runner;

await sleep(2000);
const { default: open } = await new Function('return import("open")')();
await open(connectCloudUrl);

@@ -167,0 +167,0 @@ cloudConnectSpinner.succeed();

@@ -238,3 +238,3 @@ "use strict";

}
const open = require('open');
const { default: open } = await new Function('return import("open")')();
await open(result.url)

@@ -241,0 +241,0 @@ .then(() => {

@@ -160,3 +160,3 @@ "use strict";

}
const open = require('open');
const { default: open } = await new Function('return import("open")')();
await open(result.url)

@@ -163,0 +163,0 @@ .then(() => {

@@ -57,4 +57,6 @@ import type ChangelogRenderer from '../../release/changelog-renderer';

* Restrict the default to targets originated by a specific plugin
* (e.g. `@nx/vite`). Matches against the plugin that wrote the target's
* `executor` or `command`.
* (e.g. `@nx/vite`). Matches against the plugin from nx.json's `plugins`
* that wrote the target's `executor` or `command`. Targets whose
* executor/command comes from `project.json` or `package.json` have no
* source plugin and never match.
*/

@@ -61,0 +63,0 @@ plugin?: string;

@@ -495,3 +495,3 @@ /**

*/
export declare function initializeTelemetry(connection: ExternalObject<NxDbConnection>, workspaceId: string, userId: string, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): string
export declare function initializeTelemetry(connection: ExternalObject<NxDbConnection>, workspaceId: string, userId: string | undefined | null, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): string

@@ -503,3 +503,3 @@ /**

*/
export declare function initializeTelemetryWithSessionId(sessionId: string, workspaceId: string, userId: string, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): void
export declare function initializeTelemetryWithSessionId(sessionId: string, workspaceId: string, userId: string | undefined | null, nxVersion: string, packageManagerName: string, packageManagerVersion: string | undefined | null, nodeVersion: string, osArch: string, osPlatform: string, osRelease: string, isCi: boolean, isNxCloud: boolean): void

@@ -506,0 +506,0 @@ export interface InputsInput {

@@ -164,9 +164,17 @@ "use strict";

const version = (0, semver_1.clean)(externalPackageJson.version);
let matchingExternalNode = this.npmProjects[`npm:${externalPackageJson.name}@${version}`];
if (!matchingExternalNode) {
// check if it's a package alias, where the resolved package key is used as the version
const isAliasImport = packageName !== externalPackageJson.name;
let matchingExternalNode = null;
if (isAliasImport) {
// Prefer the alias node when both the alias import and the resolved package
// exist in the graph, otherwise generated package.json files lose the alias key.
const aliasNpmProjectKey = `npm:${packageName}@npm:${externalPackageJson.name}@${version}`;
matchingExternalNode = this.npmProjects[aliasNpmProjectKey];
matchingExternalNode =
this.npmProjects[aliasNpmProjectKey] ??
this.npmProjects[`npm:${packageName}`];
}
if (!matchingExternalNode) {
matchingExternalNode =
this.npmProjects[`npm:${externalPackageJson.name}@${version}`];
}
if (!matchingExternalNode) {
// Fallback to package name as key. This can happen if the version in project graph is not the same as in the resolved package.json.

@@ -173,0 +181,0 @@ // e.g. Version in project graph is a git remote, but the resolved version is semver.

@@ -63,15 +63,18 @@ import { NxJsonConfiguration } from '../../config/nx-json';

*
* Specified plugin results are merged once into the manager. Default
* plugin results are first staged into an intermediate rootMap (with
* `'...'` spreads deferred) so that synthesis can read each layer's
* contribution without re-running the merge. The synthetic result from
* `createTargetDefaultsResults` is then merged into the manager, and
* the staged intermediate is replayed on top — that replay is where
* deferred spreads expand against the final (specified + synth) base.
* Every layer merges into the manager through the same source-map-aware
* merge, in precedence order:
*
* Synthesis itself doesn't materialize a second rootMap. Per
* (root, target) it does an on-the-fly merge of the two layered
* contributions to learn the eventual executor/command, then matches
* defaults against that merged shape. This keeps specified-plugin
* merge work to a single pass.
* specified plugins → synthetic target defaults → default plugins
*
* so field-level provenance is decided by the merge itself for all three
* layers — whichever layer a field's final value came from owns its
* attribution, and `'...'` spreads in default-plugin configs resolve against
* the accumulated specified + target-defaults base.
*
* Target-default synthesis needs the *merged* shape of the default layer
* (to predict each target's eventual executor/command) before that layer
* merges into the manager. To get it, default results are first staged into
* a throwaway intermediate rootMap with unresolvable `'...'` spreads
* deferred. The staging output feeds only `createTargetDefaultsResults`; the
* default plugins then merge into the manager from their original results.
*/

@@ -78,0 +81,0 @@ export declare function mergeCreateNodesResults(specifiedResults: CreateNodesResultEntry[][], defaultResults: CreateNodesResultEntry[][], nxJsonConfiguration: NxJsonConfiguration, workspaceRoot: string, errors: MergeError[]): {

@@ -16,6 +16,6 @@ "use strict";

const error_types_1 = require("../error-types");
const source_maps_1 = require("./project-configuration/source-maps");
const target_defaults_1 = require("./project-configuration/target-defaults");
var target_merging_1 = require("./project-configuration/target-merging");
Object.defineProperty(exports, "mergeTargetConfigurations", { enumerable: true, get: function () { return target_merging_1.mergeTargetConfigurations; } });
const target_merging_2 = require("./project-configuration/target-merging");
var target_defaults_2 = require("./project-configuration/target-defaults");

@@ -127,10 +127,9 @@ Object.defineProperty(exports, "readTargetDefaultsForTarget", { enumerable: true, get: function () { return target_defaults_2.readTargetDefaultsForTarget; } });

* 1. Every project node in every plugin result is handed to `mergeFn`,
* which decides where it lands (the manager's rootMap, an
* intermediate rootMap, etc.). Any failure is collected into
* `errors`; processing keeps going. External nodes are accumulated
* onto the shared `externalNodes` record.
* which merges it into the manager's rootMap. Any failure is
* collected into `errors`; processing keeps going. External nodes
* are accumulated onto the shared `externalNodes` record.
* 2. After every project in the batch has been merged, name-reference
* sentinels for the batch are registered against `nameRefRootMap` —
* the rootMap the batch was merged into — so sentinels point at the
* target objects that actually received the merges.
* sentinels for the batch are registered against the manager's
* rootMap, so sentinels point at the target objects that actually
* received the merges.
*

@@ -143,3 +142,7 @@ * The two passes can't be collapsed: a sentinel registered too early

*/
function mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeFn, nodesManager, nameRefRootMap, externalNodes, errors) {
function mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeFn, nodesManager, externalNodes, errors) {
mergeSinglePluginResults(pluginResults, mergeFn, externalNodes, errors);
registerNameRefsFromSinglePlugin(pluginResults, nodesManager, errors);
}
function mergeSinglePluginResults(pluginResults, mergeFn, externalNodes, errors) {
for (const result of pluginResults) {

@@ -162,2 +165,4 @@ const [pluginName, file, nodes, pluginIndex] = result;

}
}
function registerNameRefsFromSinglePlugin(pluginResults, nodesManager, errors) {
for (const result of pluginResults) {

@@ -167,3 +172,3 @@ const [pluginName, file, nodes, pluginIndex] = result;

try {
nodesManager.registerNameRefs(projectNodes, nameRefRootMap);
nodesManager.registerNameRefs(projectNodes);
}

@@ -178,15 +183,18 @@ catch (error) {

*
* Specified plugin results are merged once into the manager. Default
* plugin results are first staged into an intermediate rootMap (with
* `'...'` spreads deferred) so that synthesis can read each layer's
* contribution without re-running the merge. The synthetic result from
* `createTargetDefaultsResults` is then merged into the manager, and
* the staged intermediate is replayed on top — that replay is where
* deferred spreads expand against the final (specified + synth) base.
* Every layer merges into the manager through the same source-map-aware
* merge, in precedence order:
*
* Synthesis itself doesn't materialize a second rootMap. Per
* (root, target) it does an on-the-fly merge of the two layered
* contributions to learn the eventual executor/command, then matches
* defaults against that merged shape. This keeps specified-plugin
* merge work to a single pass.
* specified plugins → synthetic target defaults → default plugins
*
* so field-level provenance is decided by the merge itself for all three
* layers — whichever layer a field's final value came from owns its
* attribution, and `'...'` spreads in default-plugin configs resolve against
* the accumulated specified + target-defaults base.
*
* Target-default synthesis needs the *merged* shape of the default layer
* (to predict each target's eventual executor/command) before that layer
* merges into the manager. To get it, default results are first staged into
* a throwaway intermediate rootMap with unresolvable `'...'` spreads
* deferred. The staging output feeds only `createTargetDefaultsResults`; the
* default plugins then merge into the manager from their original results.
*/

@@ -198,85 +206,57 @@ function mergeCreateNodesResults(specifiedResults, defaultResults, nxJsonConfiguration, workspaceRoot, errors) {

const configurationSourceMaps = {};
const intermediateDefaultRootMap = {};
// Kept separate so the intermediate merge doesn't clobber
// specified/TD attribution on fields the defaults don't touch.
const defaultConfigurationSourceMaps = {};
const mergeToManager = (project, sourceInfo) => nodesManager.mergeProjectNode(project, configurationSourceMaps, sourceInfo);
const mergeToIntermediate = (project, sourceInfo) => {
(0, project_nodes_manager_1.mergeProjectConfigurationIntoRootMap)(intermediateDefaultRootMap, project, defaultConfigurationSourceMaps, sourceInfo);
};
for (const pluginResults of specifiedResults) {
mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToManager, nodesManager, nodesManager.getRootMap(), externalNodes, errors);
mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToManager, nodesManager, externalNodes, errors);
}
for (const pluginResults of defaultResults) {
mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToIntermediate, nodesManager, intermediateDefaultRootMap, externalNodes, errors);
}
const targetDefaultsResults = (0, target_defaults_1.createTargetDefaultsResults)(nodesManager.getRootMap(), intermediateDefaultRootMap, nxJsonConfiguration, configurationSourceMaps, defaultConfigurationSourceMaps);
if (targetDefaultsResults.length > 0) {
mergeCreateNodesResultsFromSinglePlugin(targetDefaultsResults, mergeToManager, nodesManager, nodesManager.getRootMap(), externalNodes, errors);
}
// Apply the intermediate default rootMap as a single layer. Preserved
// spread sentinels resolve here against the real specified + TD base.
// Source maps are intentionally not written — TD attribution for
// fields that yield to the base (e.g. keys before `...`) stays intact.
for (const root in intermediateDefaultRootMap) {
const project = intermediateDefaultRootMap[root];
try {
nodesManager.mergeProjectNode(project, undefined, undefined);
// Without target defaults there is nothing to synthesize, and the staging
// pass exists only to feed synthesis — skip straight to the default-plugin
// merge.
if (Object.keys(nxJsonConfiguration.targetDefaults ?? {}).length > 0) {
// Throwaway staging area: the default layer's merged shape (unresolvable
// `'...'` spreads deferred), read only by target-default synthesis. The
// default plugins merge into the manager from their original results, not
// from this map. No source maps are kept for it — synthesis attributes
// targets without them (a default plugin can never be named by a
// `filter.plugin`); the real default merge writes the manager's.
const intermediateDefaultRootMap = {};
// The rootMap merge adopts input arrays/objects by reference and grows
// them in place (e.g. `mergeMetadata`), so staging works on deep clones —
// handing it the plugin results themselves would corrupt them before the
// real merge below reads them.
const mergeToIntermediate = (project, sourceInfo) => {
(0, project_nodes_manager_1.mergeProjectConfigurationIntoRootMap)(intermediateDefaultRootMap, (0, target_merging_2.deepClone)(project), undefined, sourceInfo, false, true);
};
// Stage the default layer for synthesis. Merge errors are discarded and
// external nodes land in a scratch object — the same results merge into
// the manager below, where both surface once with proper plugin context.
// The discard is safe because every merge throw is base-independent in
// both its condition and its reachability: each throw's condition reads
// only the plugin's own config, and the spread-ambiguity throws fire even
// when a key is dropped by a base-owns-key shortcut, because those
// shortcuts eagerly validate the dropped value (`assertNoIntegerLikeSpreadKey`).
// So a given config raises the same error in this pass and the real merge
// below despite their bases differing.
// Name references are NOT registered here: `applySubstitutions` sweeps only
// the manager's `rootMap`, so sentinels registered against this throwaway
// rootMap would never be visited and would never resolve.
const stagingErrors = [];
const stagingExternalNodes = {};
for (const pluginResults of defaultResults) {
mergeSinglePluginResults(pluginResults, mergeToIntermediate, stagingExternalNodes, stagingErrors);
}
catch (error) {
errors.push(new error_types_1.MergeNodesError({
file: 'nx.json',
pluginName: 'nx/default-plugins',
error,
pluginIndex: undefined,
}));
const targetDefaultsResults = (0, target_defaults_1.createTargetDefaultsResults)(nodesManager.getRootMap(), intermediateDefaultRootMap, nxJsonConfiguration, configurationSourceMaps);
if (targetDefaultsResults.length > 0) {
mergeCreateNodesResultsFromSinglePlugin(targetDefaultsResults, mergeToManager, nodesManager, externalNodes, errors);
}
}
// The intermediate apply may have rebuilt dependsOn / inputs arrays via
// spread merges, introducing name-ref strings that weren't visible in any
// single plugin result. Re-walking the final merged targets sentinelizes
// them so the final substitution sweep resolves them too.
nodesManager.registerNameRefs(intermediateDefaultRootMap);
// Overlay default-plugin attribution onto the main source maps using
// "only fill missing" semantics. Any key already present in
// configurationSourceMaps was written by a specified plugin or by
// target defaults, and that attribution is strictly more correct:
// - For fields the default plugin never shadowed, the existing entry
// already matches what the default plugin would overlay.
// - For fields where a default plugin placed `...` after other keys,
// those keys yielded to the base during the single-layer apply
// above. The stale default-plugin entry in
// `defaultConfigurationSourceMaps` must NOT clobber the base
// attribution that the specified plugin / TD already recorded.
const mainRootMap = nodesManager.getRootMap();
for (const root in defaultConfigurationSourceMaps) {
const existing = (configurationSourceMaps[root] ??= {});
const incoming = defaultConfigurationSourceMaps[root];
// A default plugin's targets are synthesized into the main rootmap by
// target defaults *before* the default layer is applied, so target
// defaults wrote the main source map's entry for the target node and its
// identity fields first — even though it never authored them (it only
// stamps them as a merge guard and cannot bring a target into existence).
// The identity fields are the executor/command plus, for run-commands, the
// `options.command`/`options.commands` the synthetic copies from the winner
// to stay compatible (#36067). For those keys, the real default plugin's
// attribution must override the target-defaults stamp.
const identityKeys = new Set();
for (const targetName in mainRootMap[root]?.targets ?? {}) {
const base = (0, source_maps_1.targetSourceMapKey)(targetName);
identityKeys.add(base);
identityKeys.add(`${base}.executor`);
identityKeys.add(`${base}.command`);
identityKeys.add(`${base}.options.command`);
identityKeys.add(`${base}.options.commands`);
}
for (const key in incoming) {
if (existing[key] === undefined) {
existing[key] = incoming[key];
}
else if (identityKeys.has(key) &&
existing[key][1] === source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME) {
existing[key] = incoming[key];
}
}
// Merge the default plugins into the manager on top of the specified + TD
// base, from their original results. This is the same source-map-aware path
// the other layers take, so every field a default plugin wins — including
// fields it overrides on a specified/TD target — is attributed to it by the
// merge itself, `'...'` spreads resolve against the real base (keys a spread
// lets the base win keep their base attribution), and identity provenance
// follows the node-ownership rules in `recordTargetIdentitySourceMapInfo` /
// `getMergeValueResult`.
for (const pluginResults of defaultResults) {
mergeCreateNodesResultsFromSinglePlugin(pluginResults, mergeToManager, nodesManager, externalNodes, errors);
}

@@ -283,0 +263,0 @@ const projectRootMap = nodesManager.getRootMap();

@@ -37,2 +37,3 @@ import { ProjectConfiguration } from '../../../config/workspace-json-project-json';

private pendingByName;
private nameHistory;
constructor(getNameMap?: () => Record<string, ProjectConfiguration>);

@@ -39,0 +40,0 @@ registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>): void;

@@ -54,2 +54,5 @@ "use strict";

this.pendingByName = new Map();
// name → root for every name a project has ever been identified by, so refs
// to a since-renamed name still bind to the right root (see createRef).
this.nameHistory = new Map();
this.getNameMap = getNameMap ?? (() => ({}));

@@ -152,5 +155,10 @@ }

}
// Builds a sentinel and registers it.
// Builds a sentinel and registers it. When `referencedName` isn't in the
// current name map, fall back to the rename history: a batch may reference a
// project by a name that a project earlier in the same batch already renamed
// (refs register after the whole batch merges), and the old name still
// identifies the same root.
createRef(referencedName, targetPart) {
const referencedRoot = this.getNameMap()[referencedName]?.root;
const referencedRoot = this.getNameMap()[referencedName]?.root ??
this.nameHistory.get(referencedName);
const ref = referencedRoot !== undefined

@@ -173,2 +181,7 @@ ? new RootRef(referencedRoot, targetPart)

identifyProjectWithRoot(root, name) {
// Every name a root has ever gone by, including pre-rename names. A later
// ref to a stale name resolves to the root it identified at the time.
// (If two roots use the same name over a graph construction, the later
// one wins — matching how the live name map would have resolved it.)
this.nameHistory.set(name, root);
const pending = this.pendingByName.get(name);

@@ -213,2 +226,3 @@ if (!pending)

this.pendingByName.clear();
this.nameHistory.clear();
}

@@ -215,0 +229,0 @@ substituteInArray(entries, nameByRoot) {

@@ -31,11 +31,8 @@ import { ProjectConfiguration } from '../../../config/workspace-json-project-json';

* Inserts project-name sentinels into `inputs` and `dependsOn` on the
* merged objects from `mergedRootMap` (defaulting to this manager's
* rootMap). Walking the merged entries matters because a spread-produced
* array is a fresh instance.
*
* Pass a different `mergedRootMap` for the default-plugin intermediate
* pass, then call again with `this.rootMap` after it's applied so
* name refs introduced by that merge are sentinelized as well.
* merged objects in this manager's rootMap. Walking the merged entries —
* not the plugin results — matters because a spread-produced array is a
* fresh instance, and because sentinels written into plugin-result arrays
* would corrupt them for any later merge that re-reads the results.
*/
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>, mergedRootMap?: Record<string, ProjectConfiguration>): void;
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>): void;
/**

@@ -42,0 +39,0 @@ * Applies all pending name substitutions. Call once after all plugin

@@ -132,6 +132,6 @@ "use strict";

if (sourceMap) {
// A target default stamping fields onto an existing target must not
// steal provenance for the target's existence; real plugins still win
// last. Field-level attribution is handled inside
// `mergeTargetConfigurations`.
// Claims the node key for a newly-created target (or reclaims it from
// a weak target-defaults stamp). Whether an *existing* target changes
// owners is decided inside `mergeTargetConfigurations`, which knows
// whether this merge changed the target's identity.
(0, source_maps_1.recordTargetIdentitySourceMapInfo)(sourceMap, (0, source_maps_1.targetSourceMapKey)(targetName), sourceInformation);

@@ -261,11 +261,8 @@ }

* Inserts project-name sentinels into `inputs` and `dependsOn` on the
* merged objects from `mergedRootMap` (defaulting to this manager's
* rootMap). Walking the merged entries matters because a spread-produced
* array is a fresh instance.
*
* Pass a different `mergedRootMap` for the default-plugin intermediate
* pass, then call again with `this.rootMap` after it's applied so
* name refs introduced by that merge are sentinelized as well.
* merged objects in this manager's rootMap. Walking the merged entries —
* not the plugin results — matters because a spread-produced array is a
* fresh instance, and because sentinels written into plugin-result arrays
* would corrupt them for any later merge that re-reads the results.
*/
registerNameRefs(pluginResultProjects, mergedRootMap = this.rootMap) {
registerNameRefs(pluginResultProjects) {
if (!pluginResultProjects)

@@ -275,4 +272,4 @@ return;

for (const root in pluginResultProjects) {
if (mergedRootMap[root]) {
scoped[root] = mergedRootMap[root];
if (this.rootMap[root]) {
scoped[root] = this.rootMap[root];
}

@@ -279,0 +276,0 @@ }

@@ -12,8 +12,15 @@ /** [file, plugin] that contributed a configuration property. */

/**
* Write the source for a target's "identity" key (the target node itself, or
* its executor/command). Real plugins win last; a target-defaults stamp only
* claims the key when no real plugin already recorded it — target defaults
* never bring a target into existence, they only layer fields onto one.
* Write the source for the target node key (`targets.<name>`). Ownership of a
* target follows its identity, not the last writer:
*
* - An unowned key goes to whoever writes it first (the creator).
* - A target-defaults stamp is weak — it never authors a target's existence,
* so any real plugin reclaims the key from it, and it can never take the
* key from a real plugin.
* - Between real plugins, the key only changes hands when the merge changed
* the target's identity (executor/command) — a plugin that merely layers
* fields (dependsOn, options, …) onto an existing target does not become
* its owner.
*/
export declare function recordTargetIdentitySourceMapInfo(sourceMap: Record<string, SourceInformation>, key: string, sourceInfo: SourceInformation): void;
export declare function recordTargetIdentitySourceMapInfo(sourceMap: Record<string, SourceInformation>, key: string, sourceInfo: SourceInformation, identityChanged?: boolean): void;
/** Source map per project root. */

@@ -20,0 +27,0 @@ export type ConfigurationSourceMaps = Record<string, Record<string, SourceInformation>>;

@@ -24,12 +24,26 @@ "use strict";

/**
* Write the source for a target's "identity" key (the target node itself, or
* its executor/command). Real plugins win last; a target-defaults stamp only
* claims the key when no real plugin already recorded it — target defaults
* never bring a target into existence, they only layer fields onto one.
* Write the source for the target node key (`targets.<name>`). Ownership of a
* target follows its identity, not the last writer:
*
* - An unowned key goes to whoever writes it first (the creator).
* - A target-defaults stamp is weak — it never authors a target's existence,
* so any real plugin reclaims the key from it, and it can never take the
* key from a real plugin.
* - Between real plugins, the key only changes hands when the merge changed
* the target's identity (executor/command) — a plugin that merely layers
* fields (dependsOn, options, …) onto an existing target does not become
* its owner.
*/
function recordTargetIdentitySourceMapInfo(sourceMap, key, sourceInfo) {
if (sourceInfo[1] !== exports.TARGET_DEFAULTS_PLUGIN_NAME ||
sourceMap[key] === undefined) {
function recordTargetIdentitySourceMapInfo(sourceMap, key, sourceInfo, identityChanged = false) {
const existing = sourceMap[key];
if (existing === undefined) {
sourceMap[key] = sourceInfo;
return;
}
if (sourceInfo[1] === exports.TARGET_DEFAULTS_PLUGIN_NAME) {
return;
}
if (existing[1] === exports.TARGET_DEFAULTS_PLUGIN_NAME || identityChanged) {
sourceMap[key] = sourceInfo;
}
}

@@ -36,0 +50,0 @@ // Iterates `${prefixKey}.0`, `${prefixKey}.1`, ... for each index of `array`.

@@ -22,3 +22,3 @@ import { NormalizedTargetDefaults, NxJsonConfiguration, TargetDefaults } from '../../../config/nx-json';

*/
export declare function createTargetDefaultsResults(specifiedPluginRootMap: Record<string, ProjectConfiguration>, defaultPluginRootMap: Record<string, ProjectConfiguration>, nxJsonConfiguration: NxJsonConfiguration, specifiedSourceMaps?: ConfigurationSourceMaps, defaultSourceMaps?: ConfigurationSourceMaps): CreateNodesResultEntry[];
export declare function createTargetDefaultsResults(specifiedPluginRootMap: Record<string, ProjectConfiguration>, defaultPluginRootMap: Record<string, ProjectConfiguration>, nxJsonConfiguration: NxJsonConfiguration, specifiedSourceMaps?: ConfigurationSourceMaps): CreateNodesResultEntry[];
/**

@@ -25,0 +25,0 @@ * Public reader that resolves the target defaults applying to a given

@@ -24,3 +24,3 @@ "use strict";

*/
function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMap, nxJsonConfiguration, specifiedSourceMaps, defaultSourceMaps) {
function createTargetDefaultsResults(specifiedPluginRootMap, defaultPluginRootMap, nxJsonConfiguration, specifiedSourceMaps) {
const targetDefaultsConfig = nxJsonConfiguration.targetDefaults;

@@ -69,3 +69,3 @@ if (!targetDefaultsConfig) {

const sourcePlugin = needsSourcePlugin
? resolveSourcePlugin(root, targetName, specifiedSourceMaps, defaultSourceMaps)
? resolveSourcePlugin(root, targetName, defaultTargets[targetName], specifiedSourceMaps)
: undefined;

@@ -377,30 +377,29 @@ const syntheticTargets = buildSyntheticTargetsForRoot(targetName, root, effective, targetDefaults, projectName, projectNode, sourcePlugin);

}
function resolveSourcePlugin(root, targetName, specifiedSourceMaps, defaultSourceMaps) {
// Default-plugin attribution overrides specified-plugin attribution in the
// merge, so check it first. The executor/command keys carry the plugin that
// *created* the target, which is what `filter.plugin` ("targets originated by
// X") means. The top-level `targets.<name>` node key is deliberately NOT used
// as a fallback: it tracks the last writer, so a later plugin augmenting the
// target would mis-attribute it. The trade-off is that a target with neither
// an executor nor a command (a rare, non-runnable shape) resolves to no
// source plugin and won't match a `filter.plugin` default — accepted, since
// every runnable target carries one of these keys.
const executorKey = `${(0, source_maps_1.targetSourceMapKey)(targetName)}.executor`;
const commandKey = `${(0, source_maps_1.targetSourceMapKey)(targetName)}.command`;
const candidates = [
pluginFromSourceMap(defaultSourceMaps, root, executorKey),
pluginFromSourceMap(defaultSourceMaps, root, commandKey),
pluginFromSourceMap(specifiedSourceMaps, root, executorKey),
pluginFromSourceMap(specifiedSourceMaps, root, commandKey),
];
for (const candidate of candidates) {
if (candidate && candidate !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME)
return candidate;
function resolveSourcePlugin(root, targetName, defaultTarget, specifiedSourceMaps) {
// `filter.plugin` ("targets originated by X") can only name a plugin from
// nx.json's `plugins` — the specified set. When the default layer authors
// the target's identity (its merged config carries an executor or command,
// which the merge lets win), the originator is a default plugin and the
// target has no matchable source plugin; no source maps are needed to see
// that. Otherwise the specified layer's executor/command attribution names
// the originator. The top-level `targets.<name>` node key is deliberately
// NOT used as a fallback: it tracks ownership, so a later plugin augmenting
// the target would mis-attribute it. The trade-off is that a target with
// neither an executor nor a command (a rare, non-runnable shape) resolves to
// no source plugin and won't match a `filter.plugin` default — accepted,
// since every runnable target carries one of these keys.
if (defaultTarget &&
(defaultTarget.executor !== undefined ||
defaultTarget.command !== undefined)) {
return undefined;
}
const sourceMap = specifiedSourceMaps?.[root];
for (const identityKey of ['executor', 'command']) {
const plugin = sourceMap?.[`${(0, source_maps_1.targetSourceMapKey)(targetName)}.${identityKey}`]?.[1];
if (plugin && plugin !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME) {
return plugin;
}
}
return undefined;
}
function pluginFromSourceMap(maps, root, key) {
const entry = maps?.[root]?.[key];
return entry?.[1];
}
// Builds a name → MatcherProjectNode view for `findMatchingProjects` to

@@ -407,0 +406,0 @@ // consult, across both layered rootMaps. Tags are unioned across layers. A

@@ -173,2 +173,7 @@ "use strict";

if (baseHasConfig) {
// Base wins, so the incoming config is dropped without reaching
// `mergeConfigurationValue`. Validate its nested spread here so an
// integer-like-key ambiguity throws regardless of which side owns
// the config name (mirrors the base-independent target-level check).
(0, utils_1.assertNoIntegerLikeSpreadKey)(newConfigurations?.[configName], configIdentifier ? `Object at "${configIdentifier}"` : 'Object');
mergedConfigurations[configName] = baseConfigurations[configName];

@@ -286,4 +291,7 @@ }

// Integer-like keys get hoisted to targetKeys[0], making their position
// relative to '...' unrecoverable.
if (hasSpread &&
// relative to '...' unrecoverable. This is a property of the authored
// config, not of the base, so it throws regardless of compatibility —
// which also keeps the error identical between the target-defaults staging
// merge and the real merge, whose bases differ.
if (spreadPosInTarget >= 0 &&
targetKeys[0] &&

@@ -309,2 +317,9 @@ utils_1.INTEGER_LIKE_KEY_PATTERN.test(targetKeys[0])) {

// Before '...': base wins; fall through to target only if base lacks it.
// When base wins, the incoming `target[key]` is dropped without ever
// reaching `getMergeValueResult`, so validate its nested spread here —
// the integer-like-key ambiguity is a property of the authored value,
// not of which side owns the key, and must throw either way.
(0, utils_1.assertNoIntegerLikeSpreadKey)(target[key], projectConfigSourceMap
? `Object at "${targetIdentifier}.${key}"`
: 'Object');
result[key] =

@@ -387,9 +402,16 @@ key in mergeBase

}
// Update source map once after loop. Real plugins win last, but a target
// default — which only stamps fields onto an existing target and never
// authors its existence — must not steal the node key from the plugin that
// introduced the target. An incompatible replace clears these keys above, so
// a replacing real plugin still re-owns the node.
// Update the node key once after the loop. Ownership follows identity: this
// merge claims `targets.<name>` only when it changed the target's identity
// (a new/different executor or command, or an incompatible replace — whose
// key purge above empties the slot anyway). A plugin that only layers fields
// onto an existing target leaves the node with its creator; weak
// target-defaults stamps are always reclaimable.
if (projectConfigSourceMap) {
(0, source_maps_1.recordTargetIdentitySourceMapInfo)(projectConfigSourceMap, targetIdentifier, sourceInformation);
const identityChanged = !isCompatible ||
(target.executor !== undefined &&
target.executor !== baseTarget?.executor) ||
(target.command !== undefined &&
target.command !== baseTarget?.command) ||
suppliesNewOptionsIdentity(baseTarget, target);
(0, source_maps_1.recordTargetIdentitySourceMapInfo)(projectConfigSourceMap, targetIdentifier, sourceInformation, identityChanged);
}

@@ -450,2 +472,22 @@ // merge options if there are any

}
/**
* Run-commands and run-script targets carry their runnable identity in
* `options` (see {@link isCompatibleTarget}). A layer that supplies that
* identity where the base had none changed what the target runs — the same
* identity change as setting an executor on a target that had none.
*/
function suppliesNewOptionsIdentity(baseTarget, target) {
const executor = target.executor ?? baseTarget?.executor;
if (executor === 'nx:run-commands') {
const baseCommand = baseTarget?.options?.command ??
baseTarget?.options?.commands?.join(' && ');
const targetCommand = target.options?.command ?? target.options?.commands?.join(' && ');
return !!targetCommand && targetCommand !== baseCommand;
}
if (executor === 'nx:run-script') {
const targetScript = target.options?.script;
return !!targetScript && targetScript !== baseTarget?.options?.script;
}
return false;
}
function resolveNxTokensInOptions(object, project, key) {

@@ -452,0 +494,0 @@ const result = Array.isArray(object) ? [...object] : { ...object };

@@ -118,5 +118,18 @@ "use strict";

// the resulting project to change names from earlier plugins...
if (!project.name &&
(0, node_fs_1.existsSync)((0, path_1.join)(workspaceRoot, project.root, 'project.json'))) {
project.name = (0, to_project_name_1.toProjectName)((0, path_1.join)(root, 'project.json'));
if (!project.name) {
const projectJsonPath = (0, path_1.join)(workspaceRoot, project.root, 'project.json');
if ((0, node_fs_1.existsSync)(projectJsonPath)) {
// The project.json plugin may not have run (e.g. when a single
// plugin is run in isolation via `addPlugin` from a generator), so
// prefer the name declared in project.json before deriving one from
// the directory name.
let nameFromProjectJson;
try {
nameFromProjectJson =
(0, fileutils_1.readJsonFile)(projectJsonPath).name;
}
catch { }
project.name =
nameFromProjectJson ?? (0, to_project_name_1.toProjectName)((0, path_1.join)(root, 'project.json'));
}
}

@@ -123,0 +136,0 @@ try {

@@ -11,2 +11,17 @@ import { type SourceInformation } from './source-maps';

}
/**
* Throws `IntegerLikeSpreadKeyError` when `value` is a `'...'` spread object
* whose enumeration hoists an integer-like key ahead of the spread, making its
* authored position ambiguous.
*
* The ambiguity is a property of the authored value alone, not of any merge
* base. `mergeObjectWithSpread` runs this the moment it merges such a value —
* but a merge layer that lets the base win a key drops the incoming value
* without merging it, so the check must also be run eagerly at those
* base-owns-key shortcuts. Otherwise the error would surface or vanish
* depending on which side owns the key (e.g. it fires in the target-defaults
* staging merge but not the real merge), which is exactly the divergence that
* makes discarding staging errors unsafe.
*/
export declare function assertNoIntegerLikeSpreadKey(value: unknown, errorContext: string): void;
type SourceMapContext = {

@@ -13,0 +28,0 @@ sourceMap: Record<string, SourceInformation>;

@@ -5,2 +5,3 @@ "use strict";

exports.uniqueKeysInObjects = uniqueKeysInObjects;
exports.assertNoIntegerLikeSpreadKey = assertNoIntegerLikeSpreadKey;
exports.getMergeValueResult = getMergeValueResult;

@@ -35,2 +36,25 @@ const source_maps_1 = require("./source-maps");

/**
* Throws `IntegerLikeSpreadKeyError` when `value` is a `'...'` spread object
* whose enumeration hoists an integer-like key ahead of the spread, making its
* authored position ambiguous.
*
* The ambiguity is a property of the authored value alone, not of any merge
* base. `mergeObjectWithSpread` runs this the moment it merges such a value —
* but a merge layer that lets the base win a key drops the incoming value
* without merging it, so the check must also be run eagerly at those
* base-owns-key shortcuts. Otherwise the error would surface or vanish
* depending on which side owns the key (e.g. it fires in the target-defaults
* staging merge but not the real merge), which is exactly the divergence that
* makes discarding staging errors unsafe.
*/
function assertNoIntegerLikeSpreadKey(value, errorContext) {
if (!isObject(value) || value[exports.NX_SPREAD_TOKEN] !== true) {
return;
}
const keys = Object.keys(value);
if (keys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(keys[0])) {
throw new IntegerLikeSpreadKeyError(keys[0], errorContext);
}
}
/**
* `"..."` in `newValue` (as an array element or a key set to `true`)

@@ -57,3 +81,9 @@ * expands the base at that position; otherwise `newValue` replaces

// or a different one) still attributes to the new layer.
if (!isUnchangedPrimitive(newValue, baseValue)) {
//
// The exception cuts the other way too: when the *existing* attribution is a
// target-defaults stamp (TD merges before the plugin whose value it
// predicted, so its stamp lands first), a real plugin re-stating that value
// is the genuine author and reclaims the key.
if (!isUnchangedPrimitive(newValue, baseValue) ||
isReclaimableTargetDefaultsStamp(sourceMapContext)) {
writeTopLevelSourceMap(sourceMapContext);

@@ -63,2 +93,10 @@ }

}
// Whether the key's current attribution is a weak target-defaults stamp that
// the (non-target-defaults) writer should reclaim despite the value being
// unchanged.
function isReclaimableTargetDefaultsStamp(ctx) {
return (!!ctx &&
ctx.sourceMap[ctx.key]?.[1] === source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME &&
ctx.sourceInformation[1] !== source_maps_1.TARGET_DEFAULTS_PLUGIN_NAME);
}
// Whether `newValue` leaves a defined primitive base untouched. Objects fall

@@ -127,8 +165,6 @@ // through (always re-attributed) to preserve the existing replace semantics —

: 'Object';
// Integer-like keys are hoisted to the front of enumeration, so one
// alongside `'...'` makes its authored position ambiguous.
assertNoIntegerLikeSpreadKey(newValue, errorContext);
const newKeys = Object.keys(newValue);
// Integer-like keys are hoisted to the front of enumeration, so if one
// exists alongside `'...'` it must be newKeys[0].
if (newKeys[0] && exports.INTEGER_LIKE_KEY_PATTERN.test(newKeys[0])) {
throw new IntegerLikeSpreadKeyError(newKeys[0], errorContext);
}
// Base per-key sources captured lazily — only for shared keys the new

@@ -135,0 +171,0 @@ // object overwrites before `'...'`, since writing their new source

import { ProjectGraph } from '../config/project-graph';
import { Task } from '../config/task-graph';
/**
* Resolves the FORCE_COLOR value for forked child processes.
*
* When the user sets FORCE_COLOR=0, bin/nx.ts deletes it from process.env
* (workaround for picocolors treating "0" as truthy) and saves the original
* value in NX_ORIGINAL_FORCE_COLOR. Without this check, the undefined
* FORCE_COLOR would default to 'true', re-enabling colors in all children.
*/
export declare function getForceColorForChild(): string;
export declare function getEnvVariablesForBatchProcess(skipNxCache: boolean, captureStderr: boolean): NodeJS.ProcessEnv;

@@ -4,0 +13,0 @@ export declare function getTaskSpecificEnv(task: Task, graph: ProjectGraph): NodeJS.ProcessEnv;

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getForceColorForChild = getForceColorForChild;
exports.getEnvVariablesForBatchProcess = getEnvVariablesForBatchProcess;

@@ -14,9 +15,29 @@ exports.getTaskSpecificEnv = getTaskSpecificEnv;

const task_env_paths_1 = require("./task-env-paths");
/**
* Resolves the FORCE_COLOR value for forked child processes.
*
* When the user sets FORCE_COLOR=0, bin/nx.ts deletes it from process.env
* (workaround for picocolors treating "0" as truthy) and saves the original
* value in NX_ORIGINAL_FORCE_COLOR. Without this check, the undefined
* FORCE_COLOR would default to 'true', re-enabling colors in all children.
*/
function getForceColorForChild() {
if (process.env.FORCE_COLOR !== undefined) {
return process.env.FORCE_COLOR;
}
if (process.env.NX_ORIGINAL_FORCE_COLOR === '0') {
return '0';
}
return 'true';
}
function getEnvVariablesForBatchProcess(skipNxCache, captureStderr) {
return {
const res = {
// User Process Env Variables override Dotenv Variables
...process.env,
// Nx Env Variables overrides everything
...getNxEnvVariablesForForkedProcess(process.env.FORCE_COLOR === undefined ? 'true' : process.env.FORCE_COLOR, skipNxCache, captureStderr),
...getNxEnvVariablesForForkedProcess(getForceColorForChild(), skipNxCache, captureStderr),
};
// NX_ORIGINAL_FORCE_COLOR is an internal signal and should not leak into child processes
delete res.NX_ORIGINAL_FORCE_COLOR;
return res;
}

@@ -62,2 +83,5 @@ // The orchestrator now calls this eagerly during the coordinator pre-hash

delete res.NX_SET_CLI;
// NX_ORIGINAL_FORCE_COLOR is an internal signal used by getForceColorForChild()
// and should not leak into child processes
delete res.NX_ORIGINAL_FORCE_COLOR;
return res;

@@ -64,0 +88,0 @@ }

@@ -10,7 +10,1 @@ /**

export declare function promptForAnalyticsPreference(): Promise<boolean>;
/**
* Generates a deterministic workspace ID.
* Priority: nxCloudId > git remote URL (hashed).
* Returns null if neither is available (no telemetry).
*/
export declare function generateWorkspaceId(cwd?: string): string | null;

@@ -5,5 +5,2 @@ "use strict";

exports.promptForAnalyticsPreference = promptForAnalyticsPreference;
exports.generateWorkspaceId = generateWorkspaceId;
const crypto_1 = require("crypto");
const child_process_1 = require("child_process");
const fs_1 = require("fs");

@@ -89,49 +86,1 @@ const enquirer_1 = require("enquirer");

}
/**
* Generates a deterministic workspace ID.
* Priority: nxCloudId > git remote URL (hashed).
* Returns null if neither is available (no telemetry).
*/
function generateWorkspaceId(cwd) {
const root = cwd ?? workspace_root_1.workspaceRoot;
// Use nxCloudId if available — most stable identifier
const nxJson = (0, nx_json_1.readNxJson)(root);
const nxCloudId = nxJson?.nxCloudId ?? nxJson?.nxCloudAccessToken;
if (nxCloudId) {
return nxCloudId;
}
// Fall back to git remote URL hash
try {
const remoteUrl = (0, child_process_1.execSync)('git remote get-url origin', {
stdio: 'pipe',
cwd: root,
windowsHide: true,
})
.toString()
.trim();
if (remoteUrl) {
return (0, crypto_1.createHash)('sha256').update(remoteUrl).digest('hex').slice(0, 32);
}
}
catch {
// No git remote available
}
// Fall back to first commit SHA — already a hash
try {
const firstCommit = (0, child_process_1.execSync)('git rev-list --max-parents=0 HEAD', {
stdio: 'pipe',
cwd: root,
windowsHide: true,
})
.toString()
.trim()
.split('\n')[0];
if (firstCommit) {
return firstCommit;
}
}
catch {
// Not a git repo
}
return null;
}

@@ -7,3 +7,3 @@ import type { Tree } from '../../generators/tree';

/**
* Dereferences a pnpm/yarn catalog reference to a concrete version spec. Returns
* Dereferences a pnpm/yarn/bun catalog reference to a concrete version spec. Returns
* the input unchanged when it is not a catalog reference (or no catalog manager

@@ -10,0 +10,0 @@ * applies). Throws when the reference cannot be resolved.

@@ -12,3 +12,3 @@ "use strict";

/**
* Dereferences a pnpm/yarn catalog reference to a concrete version spec. Returns
* Dereferences a pnpm/yarn/bun catalog reference to a concrete version spec. Returns
* the input unchanged when it is not a catalog reference (or no catalog manager

@@ -15,0 +15,0 @@ * applies). Throws when the reference cannot be resolved.

@@ -5,2 +5,3 @@ "use strict";

const package_manager_1 = require("../package-manager");
const bun_manager_1 = require("./bun-manager");
const pnpm_manager_1 = require("./pnpm-manager");

@@ -18,2 +19,4 @@ const yarn_manager_1 = require("./yarn-manager");

return new yarn_manager_1.YarnCatalogManager();
case 'bun':
return new bun_manager_1.BunCatalogManager();
default:

@@ -20,0 +23,0 @@ return null;

@@ -8,2 +8,9 @@ import type { Tree } from '../../generators/tree';

catalogName?: string;
}>): void;
}>, options?: {
/**
* Treat "default" as an alias for the `catalog` field and route default
* updates through a populated `catalogs.default` (pnpm semantics). When
* false, "default" is an ordinary named catalog (yarn semantics).
*/
aliasDefaultCatalog?: boolean;
}): void;

@@ -139,3 +139,4 @@ "use strict";

}
function updateCatalogVersionsInFile(filename, treeOrRoot, updates) {
function updateCatalogVersionsInFile(filename, treeOrRoot, updates, options) {
const aliasDefaultCatalog = options?.aliasDefaultCatalog ?? true;
let checkExists;

@@ -198,3 +199,5 @@ let readYaml;

const { packageName, version, catalogName } = update;
const normalizedCatalogName = catalogName === 'default' ? undefined : catalogName;
const normalizedCatalogName = aliasDefaultCatalog && catalogName === 'default'
? undefined
: catalogName;
let targetPath;

@@ -204,4 +207,6 @@ if (!normalizedCatalogName) {

// when `catalogs.default` is populated; that would create a
// duplicate-default config rejected by pnpm.
if (isMapAt(doc, ['catalog'])) {
// duplicate-default config rejected by pnpm. Without the alias,
// "default" is an ordinary named catalog and the default route is
// always the `catalog` field.
if (!aliasDefaultCatalog || isMapAt(doc, ['catalog'])) {
targetPath = ['catalog', packageName];

@@ -208,0 +213,0 @@ }

import type { Tree } from '../../generators/tree';
import type { CatalogDefinitions, CatalogReference } from './types';
import type { CatalogDefinitions, CatalogReference, CatalogReferenceMatch } from './types';
export declare function formatCatalogError(error: string, suggestions: string[]): string;
/**
* Shared implementation of getCatalogReferencesForPackage: enumerates the
* default and named catalog references and keeps those the manager resolves,
* so per-manager default-catalog semantics apply without duplication.
*/
export declare function collectCatalogReferencesForPackage(manager: CatalogManager, treeOrRoot: Tree | string, packageName: string): CatalogReferenceMatch[];
/**
* Interface for catalog managers that handle package manager-specific catalog implementations.

@@ -23,2 +29,8 @@ */

/**
* Get every catalog reference that resolves to a version for a package,
* following the package manager's own default-catalog semantics.
*/
getCatalogReferencesForPackage(workspaceRoot: string, packageName: string): CatalogReferenceMatch[];
getCatalogReferencesForPackage(tree: Tree, packageName: string): CatalogReferenceMatch[];
/**
* Check that a catalog reference is valid.

@@ -25,0 +37,0 @@ */

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatCatalogError = formatCatalogError;
exports.collectCatalogReferencesForPackage = collectCatalogReferencesForPackage;
function formatCatalogError(error, suggestions) {

@@ -14,1 +15,30 @@ let message = error;

}
/**
* Shared implementation of getCatalogReferencesForPackage: enumerates the
* default and named catalog references and keeps those the manager resolves,
* so per-manager default-catalog semantics apply without duplication.
*/
function collectCatalogReferencesForPackage(manager, treeOrRoot, packageName) {
// The overload pairs don't accept the Tree | string union directly.
const source = treeOrRoot;
const catalogDefs = manager.getCatalogDefinitions(source);
if (!catalogDefs) {
return [];
}
const catalogRefs = ['catalog:'];
for (const name of Object.keys(catalogDefs.catalogs ?? {})) {
// Skip names the manager treats as the default catalog (e.g. pnpm's
// "default") — already covered by the `catalog:` candidate.
if (!manager.parseCatalogReference(`catalog:${name}`)?.isDefaultCatalog) {
catalogRefs.push(`catalog:${name}`);
}
}
const matches = [];
for (const catalogRef of catalogRefs) {
const versionSpec = manager.resolveCatalogReference(source, packageName, catalogRef);
if (versionSpec) {
matches.push({ catalogRef, versionSpec });
}
}
return matches;
}
import type { Tree } from '../../generators/tree';
import { type CatalogManager } from './manager';
import type { CatalogDefinitions, CatalogReference } from './types';
import type { CatalogDefinitions, CatalogReference, CatalogReferenceMatch } from './types';
/**

@@ -16,2 +16,3 @@ * PNPM-specific catalog manager implementation

resolveCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): string | null;
getCatalogReferencesForPackage(treeOrRoot: Tree | string, packageName: string): CatalogReferenceMatch[];
validateCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): void;

@@ -18,0 +19,0 @@ updateCatalogVersions(treeOrRoot: Tree | string, updates: Array<{

@@ -57,2 +57,5 @@ "use strict";

}
getCatalogReferencesForPackage(treeOrRoot, packageName) {
return (0, manager_1.collectCatalogReferencesForPackage)(this, treeOrRoot, packageName);
}
validateCatalogReference(treeOrRoot, packageName, version) {

@@ -59,0 +62,0 @@ const catalogRef = this.parseCatalogReference(version);

@@ -5,2 +5,6 @@ export interface CatalogReference {

}
export interface CatalogReferenceMatch {
catalogRef: string;
versionSpec: string;
}
export interface CatalogEntry {

@@ -7,0 +11,0 @@ [packageName: string]: string;

import type { Tree } from '../../generators/tree';
import { type CatalogManager } from './manager';
import type { CatalogDefinitions, CatalogReference } from './types';
import type { CatalogDefinitions, CatalogReference, CatalogReferenceMatch } from './types';
/**
* Yarn Berry (v4+) catalog manager implementation
* Yarn Berry (v4.10+) catalog manager implementation.
*
* Unlike pnpm, the name "default" is not special: `catalog:` resolves only
* against `catalog`, and `catalog:default` against `catalogs.default`.
*/

@@ -16,2 +19,3 @@ export declare class YarnCatalogManager implements CatalogManager {

resolveCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): string | null;
getCatalogReferencesForPackage(treeOrRoot: Tree | string, packageName: string): CatalogReferenceMatch[];
validateCatalogReference(treeOrRoot: Tree | string, packageName: string, version: string): void;

@@ -18,0 +22,0 @@ updateCatalogVersions(treeOrRoot: Tree | string, updates: Array<{

@@ -8,3 +8,6 @@ "use strict";

/**
* Yarn Berry (v4+) catalog manager implementation
* Yarn Berry (v4.10+) catalog manager implementation.
*
* Unlike pnpm, the name "default" is not special: `catalog:` resolves only
* against `catalog`, and `catalog:default` against `catalogs.default`.
*/

@@ -26,4 +29,5 @@ class YarnCatalogManager {

const catalogName = version.substring(this.catalogProtocol.length);
// Normalize both "catalog:" and "catalog:default" to the same representation
const isDefault = !catalogName || catalogName === 'default';
// Only an empty name selects the default catalog; unlike pnpm, "default"
// is a regular named catalog in yarn.
const isDefault = !catalogName;
return {

@@ -51,4 +55,3 @@ catalogName: isDefault ? undefined : catalogName,

if (catalogRef.isDefaultCatalog) {
// Check both locations for default catalog
catalogToUse = catalogDefs.catalog ?? catalogDefs.catalogs?.default;
catalogToUse = catalogDefs.catalog;
}

@@ -60,2 +63,5 @@ else if (catalogRef.catalogName) {

}
getCatalogReferencesForPackage(treeOrRoot, packageName) {
return (0, manager_1.collectCatalogReferencesForPackage)(this, treeOrRoot, packageName);
}
validateCatalogReference(treeOrRoot, packageName, version) {

@@ -72,9 +78,5 @@ const catalogRef = this.parseCatalogReference(version);

if (catalogRef.isDefaultCatalog) {
const hasCatalog = !!catalogDefs.catalog;
const hasCatalogsDefault = !!catalogDefs.catalogs?.default;
// Error if both defined
if (hasCatalog && hasCatalogsDefault) {
throw new Error("The 'default' catalog was defined multiple times. Use the 'catalog' field or 'catalogs.default', but not both.");
}
catalogToUse = catalogDefs.catalog ?? catalogDefs.catalogs?.default;
// Yarn's default catalog is only the `catalog` field; unlike pnpm,
// `catalogs.default` does not act as a fallback.
catalogToUse = catalogDefs.catalog;
if (!catalogToUse) {

@@ -96,8 +98,3 @@ const availableCatalogs = Object.keys(catalogDefs.catalogs || {});

if (!catalogToUse) {
const availableCatalogs = Object.keys(catalogDefs.catalogs || {}).filter((c) => c !== 'default');
const defaultCatalog = !!catalogDefs.catalog
? 'catalog'
: !catalogDefs.catalogs?.default
? 'catalogs.default'
: null;
const availableCatalogs = Object.keys(catalogDefs.catalogs || {});
const suggestions = [

@@ -111,4 +108,4 @@ `Define the catalog in ${YARNRC_FILENAME} under the "catalogs" key`,

}
if (defaultCatalog) {
suggestions.push(`Or use the default catalog ("${defaultCatalog}")`);
if (catalogDefs.catalog) {
suggestions.push(`Or use the default catalog ("catalog:")`);
}

@@ -119,13 +116,5 @@ throw new Error((0, manager_1.formatCatalogError)(`Catalog "${catalogRef.catalogName}" not found in ${YARNRC_FILENAME}`, suggestions));

if (!catalogToUse[packageName]) {
let catalogName;
if (catalogRef.isDefaultCatalog) {
// Context-aware messaging based on which location exists
const hasCatalog = !!catalogDefs.catalog;
catalogName = hasCatalog
? 'default catalog ("catalog")'
: 'default catalog ("catalogs.default")';
}
else {
catalogName = `catalog '${catalogRef.catalogName}'`;
}
const catalogName = catalogRef.isDefaultCatalog
? 'default catalog ("catalog")'
: `catalog '${catalogRef.catalogName}'`;
const availablePackages = Object.keys(catalogToUse);

@@ -144,5 +133,7 @@ const suggestions = [

updateCatalogVersions(treeOrRoot, updates) {
(0, manager_utils_1.updateCatalogVersionsInFile)(YARNRC_FILENAME, treeOrRoot, updates);
(0, manager_utils_1.updateCatalogVersionsInFile)(YARNRC_FILENAME, treeOrRoot, updates, {
aliasDefaultCatalog: false,
});
}
}
exports.YarnCatalogManager = YarnCatalogManager;

@@ -39,2 +39,17 @@ export declare function cloneFromUpstream(url: string, destination: string, { originName, depth }?: {

export declare function getVcsRemoteInfo(directory?: string): VcsRemoteInfo | null;
export declare function getGitRootPath(cwd?: string): string;
/**
* Path of `directory` relative to its git root, posix-separated so it is
* identical on every OS, and '' when the directory is the git root itself.
* Null outside a git repository.
*/
export declare function getGitRootRelativePath(directory: string): string | null;
/** A shallow clone's truncated history has no stable root commit. */
export declare function isShallowRepository(directory?: string): boolean;
/**
* SHA of the repository's first commit. Merged unrelated histories leave
* several root commits — the sorted-first one is picked so every clone
* agrees. Null when there are no commits, or outside a git repository.
*/
export declare function getFirstCommitSha(directory?: string): string | null;
export declare function isGitRepository(directory?: string): boolean;

@@ -41,0 +56,0 @@ export declare function getGitCurrentBranch(directory?: string): string | null;

@@ -7,2 +7,6 @@ "use strict";

exports.getVcsRemoteInfo = getVcsRemoteInfo;
exports.getGitRootPath = getGitRootPath;
exports.getGitRootRelativePath = getGitRootRelativePath;
exports.isShallowRepository = isShallowRepository;
exports.getFirstCommitSha = getFirstCommitSha;
exports.isGitRepository = isGitRepository;

@@ -53,8 +57,3 @@ exports.getGitCurrentBranch = getGitCurrentBranch;

getGitRootPath(cwd) {
return (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
cwd,
windowsHide: true,
})
.toString()
.trim();
return getGitRootPath(cwd);
}

@@ -302,2 +301,61 @@ async hasUncommittedChanges() {

}
function getGitRootPath(cwd) {
return (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
cwd,
windowsHide: true,
})
.toString()
.trim();
}
/**
* Path of `directory` relative to its git root, posix-separated so it is
* identical on every OS, and '' when the directory is the git root itself.
* Null outside a git repository.
*/
function getGitRootRelativePath(directory) {
try {
return (0, path_1.relative)(getGitRootPath(directory), directory)
.split(path_1.sep)
.join(path_1.posix.sep);
}
catch {
return null;
}
}
/** A shallow clone's truncated history has no stable root commit. */
function isShallowRepository(directory) {
try {
return ((0, child_process_1.execFileSync)('git', ['rev-parse', '--is-shallow-repository'], {
encoding: 'utf8',
stdio: 'pipe',
cwd: directory,
windowsHide: true,
}).trim() === 'true');
}
catch {
return false;
}
}
/**
* SHA of the repository's first commit. Merged unrelated histories leave
* several root commits — the sorted-first one is picked so every clone
* agrees. Null when there are no commits, or outside a git repository.
*/
function getFirstCommitSha(directory) {
try {
const roots = (0, child_process_1.execFileSync)('git', ['rev-list', '--max-parents=0', 'HEAD'], {
encoding: 'utf8',
stdio: 'pipe',
cwd: directory,
windowsHide: true,
})
.trim()
.split(/\r?\n/)
.filter(Boolean);
return roots.sort()[0] ?? null;
}
catch {
return null;
}
}
function isGitRepository(directory) {

@@ -304,0 +362,0 @@ try {

@@ -124,3 +124,5 @@ export type PackageManager = 'yarn' | 'pnpm' | 'npm' | 'bun';

}): Promise<string>;
export declare function packageRegistryPack(cwd: string, pkg: string, version: string): Promise<{
export declare function packageRegistryPack(cwd: string, pkg: string, version: string, options?: {
bypassMinReleaseAge?: boolean;
}): Promise<{
tarballPath: string;

@@ -127,0 +129,0 @@ }>;

@@ -352,2 +352,16 @@ "use strict";

doc.delete('patchedDependencies');
// link:/file: overrides (e.g. written by `pnpm link`) point at paths that
// don't exist in the temp dir, and an override would hijack an exact-version
// add (`pnpm add pkg@x.y.z` would install the linked dir instead).
const overrides = doc.toJS()?.overrides;
if (overrides && typeof overrides === 'object') {
for (const [name, spec] of Object.entries(overrides)) {
if (typeof spec === 'string' && /^(link|file):/.test(spec)) {
doc.deleteIn(['overrides', name]);
}
}
if (Object.keys(doc.toJS()?.overrides ?? {}).length === 0) {
doc.delete('overrides');
}
}
return doc.toString();

@@ -523,3 +537,3 @@ }

}
async function packageRegistryPack(cwd, pkg, version) {
async function packageRegistryPack(cwd, pkg, version, options) {
/**

@@ -538,3 +552,9 @@ * Only `npm pack` supports downloading a tarball of a specified remote

// download working in workspaces that pin a non-npm manager (onFail: error).
env: { ...process.env, npm_config_force: 'true' },
env: {
...process.env,
npm_config_force: 'true',
...(options?.bypassMinReleaseAge
? { npm_config_min_release_age: '0' }
: {}),
},
});

@@ -541,0 +561,0 @@ const tarballPath = stdout.trim();

{
"name": "nx",
"version": "23.2.0-beta.2",
"version": "23.2.0-beta.3",
"private": false,

@@ -70,2 +70,3 @@ "type": "commonjs",

"buffer": "5.7.1",
"bundle-name": "4.1.0",
"call-bind-apply-helpers": "1.0.2",

@@ -81,4 +82,6 @@ "chalk": "4.1.2",

"debug": "4.4.3",
"default-browser": "5.2.1",
"default-browser-id": "5.0.0",
"defaults": "1.0.4",
"define-lazy-prop": "2.0.0",
"define-lazy-prop": "3.0.0",
"delayed-stream": "1.0.0",

@@ -116,7 +119,8 @@ "dotenv": "16.4.7",

"inherits": "2.0.4",
"is-docker": "2.2.1",
"is-docker": "3.0.0",
"is-fullwidth-code-point": "3.0.0",
"is-inside-container": "1.0.0",
"is-interactive": "1.0.0",
"is-unicode-supported": "0.1.0",
"is-wsl": "2.2.0",
"is-wsl": "3.1.0",
"isexe": "2.0.0",

@@ -137,3 +141,3 @@ "json5": "2.2.3",

"onetime": "5.1.2",
"open": "8.4.2",
"open": "10.1.0",
"ora": "5.4.1",

@@ -147,2 +151,3 @@ "path-key": "3.1.1",

"restore-cursor": "3.1.0",
"run-applescript": "7.0.0",
"safe-buffer": "5.2.1",

@@ -184,12 +189,12 @@ "semver": "7.8.4",

"optionalDependencies": {
"@nx/nx-darwin-arm64": "23.2.0-beta.2",
"@nx/nx-darwin-x64": "23.2.0-beta.2",
"@nx/nx-freebsd-x64": "23.2.0-beta.2",
"@nx/nx-linux-arm-gnueabihf": "23.2.0-beta.2",
"@nx/nx-linux-arm64-gnu": "23.2.0-beta.2",
"@nx/nx-linux-arm64-musl": "23.2.0-beta.2",
"@nx/nx-linux-x64-gnu": "23.2.0-beta.2",
"@nx/nx-linux-x64-musl": "23.2.0-beta.2",
"@nx/nx-win32-arm64-msvc": "23.2.0-beta.2",
"@nx/nx-win32-x64-msvc": "23.2.0-beta.2"
"@nx/nx-darwin-arm64": "23.2.0-beta.3",
"@nx/nx-darwin-x64": "23.2.0-beta.3",
"@nx/nx-freebsd-x64": "23.2.0-beta.3",
"@nx/nx-linux-arm-gnueabihf": "23.2.0-beta.3",
"@nx/nx-linux-arm64-gnu": "23.2.0-beta.3",
"@nx/nx-linux-arm64-musl": "23.2.0-beta.3",
"@nx/nx-linux-x64-gnu": "23.2.0-beta.3",
"@nx/nx-linux-x64-musl": "23.2.0-beta.3",
"@nx/nx-win32-arm64-msvc": "23.2.0-beta.3",
"@nx/nx-win32-x64-msvc": "23.2.0-beta.3"
},

@@ -196,0 +201,0 @@ "nx-migrations": {

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display