nx 23.3.0-beta.1 → 23.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/init-local.js +7 -1
- package/dist/src/command-line/add/add.js +5 -1
- package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +0 -3
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +0 -3
- package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +0 -3
- package/dist/src/command-line/init/implementation/angular/index.js +0 -3
- package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +0 -3
- package/dist/src/command-line/init/implementation/utils.d.ts +0 -1
- package/dist/src/command-line/init/implementation/utils.js +6 -9
- package/dist/src/command-line/init/init-v2.js +0 -3
- package/dist/src/command-line/migrate/agentic/capture-generator-output.d.ts +25 -0
- package/dist/src/command-line/migrate/agentic/capture-generator-output.js +121 -5
- package/dist/src/command-line/migrate/agentic/close-agent-session.d.ts +21 -0
- package/dist/src/command-line/migrate/agentic/close-agent-session.js +126 -0
- package/dist/src/command-line/migrate/agentic/definitions.d.ts +24 -0
- package/dist/src/command-line/migrate/agentic/definitions.js +5 -2
- package/dist/src/command-line/migrate/agentic/handoff.d.ts +23 -0
- package/dist/src/command-line/migrate/agentic/handoff.js +62 -3
- package/dist/src/command-line/migrate/agentic/master/invocations.d.ts +12 -0
- package/dist/src/command-line/migrate/agentic/master/invocations.js +54 -0
- package/dist/src/command-line/migrate/agentic/master/run-master-session.d.ts +12 -0
- package/dist/src/command-line/migrate/agentic/master/run-master-session.js +99 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.d.ts +31 -0
- package/dist/src/command-line/migrate/agentic/master/spawn-master.js +217 -0
- package/dist/src/command-line/migrate/agentic/prompts/fragments.js +3 -0
- package/dist/src/command-line/migrate/agentic/runner.d.ts +0 -21
- package/dist/src/command-line/migrate/agentic/runner.js +12 -249
- package/dist/src/command-line/migrate/agentic/terminal-repair.d.ts +1 -0
- package/dist/src/command-line/migrate/agentic/terminal-repair.js +27 -0
- package/dist/src/command-line/migrate/agentic/windows-cmd.d.ts +33 -0
- package/dist/src/command-line/migrate/agentic/windows-cmd.js +88 -0
- package/dist/src/command-line/migrate/deferred-output.d.ts +44 -0
- package/dist/src/command-line/migrate/deferred-output.js +92 -0
- package/dist/src/command-line/migrate/execute-migration.d.ts +9 -3
- package/dist/src/command-line/migrate/execute-migration.js +53 -16
- package/dist/src/command-line/migrate/migrate-commits.d.ts +4 -4
- package/dist/src/command-line/migrate/migrate-commits.js +7 -6
- package/dist/src/command-line/migrate/migrate.js +28 -13
- package/dist/src/command-line/migrate/run/broker.d.ts +138 -0
- package/dist/src/command-line/migrate/run/broker.js +507 -0
- package/dist/src/command-line/migrate/run/clean-retry.d.ts +12 -0
- package/dist/src/command-line/migrate/run/clean-retry.js +98 -0
- package/dist/src/command-line/migrate/run/index.d.ts +4 -3
- package/dist/src/command-line/migrate/run/index.js +7 -1
- package/dist/src/command-line/migrate/run/issues.d.ts +8 -1
- package/dist/src/command-line/migrate/run/issues.js +40 -6
- package/dist/src/command-line/migrate/run/orchestrator.d.ts +18 -1
- package/dist/src/command-line/migrate/run/orchestrator.js +409 -377
- package/dist/src/command-line/migrate/run/run-state.d.ts +16 -0
- package/dist/src/command-line/migrate/run/run-state.js +33 -2
- package/dist/src/command-line/migrate/run/runbook.js +8 -4
- package/dist/src/command-line/migrate/run/state-machine.d.ts +24 -1
- package/dist/src/command-line/migrate/run/state-machine.js +112 -21
- package/dist/src/command-line/migrate/run/util.d.ts +2 -1
- package/dist/src/command-line/migrate/run/util.js +3 -3
- package/dist/src/command-line/migrate/run/worker.js +170 -164
- package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +0 -10
- package/dist/src/core/graph/main.js +1 -1
- package/dist/src/daemon/client/client.d.ts +2 -0
- package/dist/src/daemon/client/client.js +7 -0
- package/dist/src/daemon/message-types/get-plugin-capabilities.d.ts +5 -0
- package/dist/src/daemon/message-types/get-plugin-capabilities.js +11 -0
- package/dist/src/daemon/server/handle-get-plugin-capabilities.d.ts +2 -0
- package/dist/src/daemon/server/handle-get-plugin-capabilities.js +13 -0
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +1 -1
- package/dist/src/daemon/server/server.js +16 -8
- package/dist/src/native/index.d.ts +20 -0
- package/dist/src/native/native-bindings.js +1 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/project-graph/affected/locators/project-glob-changes.js +19 -26
- package/dist/src/project-graph/error-types.d.ts +5 -0
- package/dist/src/project-graph/error-types.js +44 -5
- package/dist/src/project-graph/file-utils.d.ts +2 -0
- package/dist/src/project-graph/file-utils.js +6 -1
- package/dist/src/project-graph/nx-deps-cache.d.ts +5 -2
- package/dist/src/project-graph/nx-deps-cache.js +4 -4
- package/dist/src/project-graph/plugins/get-plugins.d.ts +3 -1
- package/dist/src/project-graph/plugins/get-plugins.js +60 -82
- package/dist/src/project-graph/plugins/in-process-loader.d.ts +1 -1
- package/dist/src/project-graph/plugins/in-process-loader.js +1 -4
- package/dist/src/project-graph/plugins/isolation/index.d.ts +1 -1
- package/dist/src/project-graph/plugins/isolation/index.js +3 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +8 -0
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +40 -5
- package/dist/src/project-graph/plugins/isolation/load-isolated-plugin.d.ts +10 -1
- package/dist/src/project-graph/plugins/isolation/load-isolated-plugin.js +67 -17
- package/dist/src/project-graph/plugins/loaded-nx-plugin.d.ts +2 -0
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +9 -0
- package/dist/src/project-graph/plugins/nx-plugin-capabilities.d.ts +6 -0
- package/dist/src/project-graph/plugins/nx-plugin-capabilities.js +28 -0
- package/dist/src/project-graph/plugins/tasks-execution-hooks.js +12 -0
- package/dist/src/project-graph/project-graph.js +41 -42
- package/dist/src/tasks-runner/run-command.js +3 -0
- package/dist/src/utils/ab-testing.d.ts +1 -1
- package/dist/src/utils/ab-testing.js +0 -4
- package/dist/src/utils/git-utils.d.ts +12 -0
- package/dist/src/utils/git-utils.js +76 -30
- package/dist/src/utils/handle-errors.js +5 -43
- package/dist/src/utils/package-json.js +8 -0
- package/dist/src/utils/plugins/plugin-capabilities.js +7 -2
- package/dist/src/utils/serialize-overrides-into-command-line.js +2 -4
- package/package.json +11 -12
package/dist/bin/init-local.js
CHANGED
|
@@ -9,6 +9,7 @@ const client_1 = require("../src/daemon/client/client");
|
|
|
9
9
|
const prompt_helpers_1 = require("../src/utils/prompt-helpers");
|
|
10
10
|
const output_1 = require("../src/utils/output");
|
|
11
11
|
const analytics_1 = require("../src/analytics");
|
|
12
|
+
const is_ci_1 = require("../src/utils/is-ci");
|
|
12
13
|
/**
|
|
13
14
|
* Nx is being run inside a workspace.
|
|
14
15
|
*
|
|
@@ -119,6 +120,11 @@ function shouldDelegateToAngularCLI() {
|
|
|
119
120
|
return commands.indexOf(command) > -1;
|
|
120
121
|
}
|
|
121
122
|
async function ensureNxConsoleInstalledViaDaemon() {
|
|
123
|
+
// Asking the daemon makes a fresh daemon install nx@latest in the background,
|
|
124
|
+
// so skip it whenever the prompt could not be shown anyway.
|
|
125
|
+
if (!process.stdout.isTTY || (0, is_ci_1.isCI)()) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
122
128
|
// Only proceed if daemon is available
|
|
123
129
|
if (!client_1.daemonClient.enabled() || !(await client_1.daemonClient.isServerAvailable())) {
|
|
124
130
|
return;
|
|
@@ -126,7 +132,7 @@ async function ensureNxConsoleInstalledViaDaemon() {
|
|
|
126
132
|
// Get status from daemon
|
|
127
133
|
const status = await client_1.daemonClient.getNxConsoleStatus();
|
|
128
134
|
// If we should prompt the user
|
|
129
|
-
if (status.shouldPrompt
|
|
135
|
+
if (status.shouldPrompt) {
|
|
130
136
|
output_1.output.log({
|
|
131
137
|
title: "Install Nx's official editor extension to:",
|
|
132
138
|
bodyLines: [
|
|
@@ -49,12 +49,16 @@ async function installPackage(pkgName, version, nxJson) {
|
|
|
49
49
|
// pnpm 11+ fails the install when the plugin's own dependency tree
|
|
50
50
|
// carries unacknowledged build scripts, and the plugin's generators can
|
|
51
51
|
// only record allowBuilds decisions after this install. Warn and skip
|
|
52
|
-
// for this one install, like pnpm 10 did.
|
|
52
|
+
// for this one install, like pnpm 10 did. pnpm 12 gave `--config` a
|
|
53
|
+
// meaning of its own and takes the setting from the environment instead.
|
|
54
|
+
const env = { ...process.env };
|
|
53
55
|
if (pm === 'pnpm' && (0, semver_1.gte)(pmv, '11.0.0')) {
|
|
54
56
|
command += ' --config.strictDepBuilds=false';
|
|
57
|
+
env.PNPM_CONFIG_STRICT_DEP_BUILDS = 'false';
|
|
55
58
|
}
|
|
56
59
|
await new Promise((resolve) => (0, child_process_1.exec)(command, {
|
|
57
60
|
windowsHide: true,
|
|
61
|
+
env,
|
|
58
62
|
}, (error, stdout, stderr) => {
|
|
59
63
|
if (error) {
|
|
60
64
|
spinner.fail();
|
|
@@ -65,9 +65,6 @@ async function addNxToMonorepo(options, guided = true) {
|
|
|
65
65
|
output_1.output.log({ title: '🛠️ Setting up Nx Cloud' });
|
|
66
66
|
await (0, utils_1.initCloud)('nx-init-monorepo');
|
|
67
67
|
}
|
|
68
|
-
else if (nxCloudChoice === 'never') {
|
|
69
|
-
(0, utils_1.setNeverConnectToCloud)(repoRoot);
|
|
70
|
-
}
|
|
71
68
|
}
|
|
72
69
|
// scanning package.json files
|
|
73
70
|
function allProjectPackageJsonFiles(repoRoot) {
|
|
@@ -92,9 +92,6 @@ async function addNxToNest(options, packageJson) {
|
|
|
92
92
|
output_1.output.log({ title: '🛠️ Setting up Nx Cloud' });
|
|
93
93
|
await (0, utils_1.initCloud)('nx-init-nest');
|
|
94
94
|
}
|
|
95
|
-
else if (nxCloudChoice === 'never') {
|
|
96
|
-
(0, utils_1.setNeverConnectToCloud)(repoRoot);
|
|
97
|
-
}
|
|
98
95
|
}
|
|
99
96
|
function addNestPluginToPackageJson(repoRoot) {
|
|
100
97
|
const path = (0, path_1.join)(repoRoot, `package.json`);
|
|
@@ -64,7 +64,4 @@ async function addNxToNpmRepo(options, guided = true) {
|
|
|
64
64
|
output_1.output.log({ title: '🛠️ Setting up Nx Cloud' });
|
|
65
65
|
await (0, utils_1.initCloud)('nx-init-npm-repo');
|
|
66
66
|
}
|
|
67
|
-
else if (nxCloudChoice === 'never') {
|
|
68
|
-
(0, utils_1.setNeverConnectToCloud)(repoRoot);
|
|
69
|
-
}
|
|
70
67
|
}
|
|
@@ -56,9 +56,6 @@ async function addNxToAngularCliRepo(options) {
|
|
|
56
56
|
output_1.output.log({ title: '🛠️ Setting up Nx Cloud' });
|
|
57
57
|
await (0, utils_1.initCloud)('nx-init-angular');
|
|
58
58
|
}
|
|
59
|
-
else if (nxCloudChoice === 'never') {
|
|
60
|
-
(0, utils_1.setNeverConnectToCloud)(repoRoot);
|
|
61
|
-
}
|
|
62
59
|
}
|
|
63
60
|
async function collectCacheableOperations(options) {
|
|
64
61
|
let cacheableOperations;
|
|
@@ -119,9 +119,6 @@ async function getLegacyMigrationFunctionIfApplicable(repoRoot, options) {
|
|
|
119
119
|
output_1.output.log({ title: '🛠️ Setting up Nx Cloud' });
|
|
120
120
|
await (0, utils_1.initCloud)('nx-init-angular');
|
|
121
121
|
}
|
|
122
|
-
else if (nxCloudChoice === 'never') {
|
|
123
|
-
(0, utils_1.setNeverConnectToCloud)(repoRoot);
|
|
124
|
-
}
|
|
125
122
|
};
|
|
126
123
|
}
|
|
127
124
|
async function installDependencies(repoRoot, pkgInfo, pmc) {
|
|
@@ -24,7 +24,6 @@ export declare function toErrorString(error: unknown): string;
|
|
|
24
24
|
export declare function readErrorStderr(error: unknown): string;
|
|
25
25
|
export declare function extractErrorName(error: unknown, stderr: string): string;
|
|
26
26
|
export declare function initCloud(installationSource: 'nx-init' | 'nx-init-angular' | 'nx-init-monorepo' | 'nx-init-nest' | 'nx-init-npm-repo' | 'nx-init-turborepo'): Promise<void>;
|
|
27
|
-
export declare function setNeverConnectToCloud(repoRoot: string): void;
|
|
28
27
|
export declare function addVsCodeRecommendedExtensions(repoRoot: string, extensions: string[]): void;
|
|
29
28
|
export declare function markRootPackageJsonAsNxProjectLegacy(repoRoot: string, cacheableScripts: string[], pmc: PackageManagerCommands): void;
|
|
30
29
|
export declare function markPackageJsonAsNxProject(packageJsonPath: string): void;
|
|
@@ -10,7 +10,6 @@ exports.toErrorString = toErrorString;
|
|
|
10
10
|
exports.readErrorStderr = readErrorStderr;
|
|
11
11
|
exports.extractErrorName = extractErrorName;
|
|
12
12
|
exports.initCloud = initCloud;
|
|
13
|
-
exports.setNeverConnectToCloud = setNeverConnectToCloud;
|
|
14
13
|
exports.addVsCodeRecommendedExtensions = addVsCodeRecommendedExtensions;
|
|
15
14
|
exports.markRootPackageJsonAsNxProjectLegacy = markRootPackageJsonAsNxProjectLegacy;
|
|
16
15
|
exports.markPackageJsonAsNxProject = markPackageJsonAsNxProject;
|
|
@@ -253,13 +252,17 @@ function updateGitIgnore(root) {
|
|
|
253
252
|
}
|
|
254
253
|
function runInstall(repoRoot, packageManager = (0, package_manager_1.detectPackageManager)(repoRoot), pmc = (0, package_manager_1.getPackageManagerCommand)(packageManager)) {
|
|
255
254
|
let command = pmc.install;
|
|
255
|
+
const env = { ...process.env };
|
|
256
256
|
// Plugins added during init can pull build-script deps whose allowBuilds
|
|
257
257
|
// entries are only recorded by their init generators after this install;
|
|
258
|
-
// warn and skip for this one install, like pnpm 10 did.
|
|
258
|
+
// warn and skip for this one install, like pnpm 10 did. pnpm 12 gave
|
|
259
|
+
// `--config` a meaning of its own and takes the setting from the
|
|
260
|
+
// environment instead.
|
|
259
261
|
if (packageManager === 'pnpm') {
|
|
260
262
|
try {
|
|
261
263
|
if ((0, semver_1.gte)((0, package_manager_1.getPackageManagerVersion)('pnpm', repoRoot), '11.0.0')) {
|
|
262
264
|
command += ' --config.strictDepBuilds=false';
|
|
265
|
+
env.PNPM_CONFIG_STRICT_DEP_BUILDS = 'false';
|
|
263
266
|
}
|
|
264
267
|
}
|
|
265
268
|
catch {
|
|
@@ -272,6 +275,7 @@ function runInstall(repoRoot, packageManager = (0, package_manager_1.detectPacka
|
|
|
272
275
|
encoding: 'utf8',
|
|
273
276
|
cwd: repoRoot,
|
|
274
277
|
windowsHide: true,
|
|
278
|
+
env,
|
|
275
279
|
});
|
|
276
280
|
}
|
|
277
281
|
catch (e) {
|
|
@@ -341,13 +345,6 @@ async function initCloud(installationSource) {
|
|
|
341
345
|
});
|
|
342
346
|
await (0, connect_to_nx_cloud_1.printSuccessMessage)(token, installationSource);
|
|
343
347
|
}
|
|
344
|
-
function setNeverConnectToCloud(repoRoot) {
|
|
345
|
-
const nxJsonPath = (0, path_1.join)(repoRoot, 'nx.json');
|
|
346
|
-
const nxJson = (0, fileutils_1.readJsonFile)(nxJsonPath);
|
|
347
|
-
nxJson.neverConnectToCloud = true;
|
|
348
|
-
(0, fileutils_1.writeJsonFile)(nxJsonPath, nxJson);
|
|
349
|
-
(0, format_1.recordInitWrite)(nxJsonPath);
|
|
350
|
-
}
|
|
351
348
|
function addVsCodeRecommendedExtensions(repoRoot, extensions) {
|
|
352
349
|
const vsCodeExtensionsPath = (0, path_1.join)(repoRoot, '.vscode/extensions.json');
|
|
353
350
|
if ((0, fileutils_1.fileExists)(vsCodeExtensionsPath)) {
|
|
@@ -379,9 +379,6 @@ async function runInit(options, baseMeta) {
|
|
|
379
379
|
if (nxCloudChoice === 'yes') {
|
|
380
380
|
await (0, utils_1.initCloud)('nx-init');
|
|
381
381
|
}
|
|
382
|
-
else if (nxCloudChoice === 'never') {
|
|
383
|
-
(0, utils_1.setNeverConnectToCloud)(repoRoot);
|
|
384
|
-
}
|
|
385
382
|
const analyticsPrompt = await (0, analytics_prompt_1.ensureAnalyticsPreferenceSet)(repoRoot, options.interactive);
|
|
386
383
|
await (0, ab_testing_1.recordStat)({
|
|
387
384
|
command: 'init',
|
|
@@ -3,11 +3,36 @@
|
|
|
3
3
|
* preserving the original behavior. Does not intercept
|
|
4
4
|
* `process.{stdout,stderr}.write` — those bypass `console` and would also
|
|
5
5
|
* pick up unrelated framework output. Restoration is idempotent.
|
|
6
|
+
*
|
|
7
|
+
* `flush()` returns at most `MAX_GENERATOR_OUTPUT_BYTES`: up to the first
|
|
8
|
+
* `HEAD_BYTES` of output, a marker with the omitted byte count, and the most
|
|
9
|
+
* recent output. The terminal still receives everything through the tee.
|
|
6
10
|
*/
|
|
7
11
|
export interface GeneratorOutputCapture {
|
|
8
12
|
flush(): string;
|
|
9
13
|
restore(): void;
|
|
10
14
|
}
|
|
15
|
+
export declare const MAX_GENERATOR_OUTPUT_BYTES = 16384;
|
|
16
|
+
export declare const MARKER_BYTES: number;
|
|
17
|
+
export declare class BoundedOutput {
|
|
18
|
+
private readonly head;
|
|
19
|
+
private headBytes;
|
|
20
|
+
private headOpen;
|
|
21
|
+
private headSplit;
|
|
22
|
+
private readonly tail;
|
|
23
|
+
private tailBytes;
|
|
24
|
+
private omitted;
|
|
25
|
+
append(record: string): void;
|
|
26
|
+
render(): string;
|
|
27
|
+
}
|
|
28
|
+
export declare class BoundedChunks {
|
|
29
|
+
private head;
|
|
30
|
+
private headOpen;
|
|
31
|
+
private tail;
|
|
32
|
+
private omitted;
|
|
33
|
+
append(chunk: string): void;
|
|
34
|
+
render(): string;
|
|
35
|
+
}
|
|
11
36
|
export declare function installGeneratorOutputCapture(): GeneratorOutputCapture;
|
|
12
37
|
/**
|
|
13
38
|
* Convenience wrapper that installs the capture, runs `fn`, restores on
|
|
@@ -1,9 +1,127 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BoundedChunks = exports.BoundedOutput = exports.MARKER_BYTES = exports.MAX_GENERATOR_OUTPUT_BYTES = void 0;
|
|
3
4
|
exports.installGeneratorOutputCapture = installGeneratorOutputCapture;
|
|
4
5
|
exports.withGeneratorOutputCapture = withGeneratorOutputCapture;
|
|
5
6
|
const node_util_1 = require("node:util");
|
|
6
7
|
const logger_1 = require("../../../utils/logger");
|
|
8
|
+
const handoff_1 = require("./handoff");
|
|
9
|
+
exports.MAX_GENERATOR_OUTPUT_BYTES = 16384;
|
|
10
|
+
const HEAD_BYTES = 4096;
|
|
11
|
+
const omittedMarker = (omitted) => `[nx migrate: ${omitted} bytes of output omitted]`;
|
|
12
|
+
// Reserved at the widest number spelling so a growing count cannot push a
|
|
13
|
+
// flush over the cap.
|
|
14
|
+
exports.MARKER_BYTES = Buffer.byteLength(`\n${omittedMarker(Number.MAX_VALUE)}\n`);
|
|
15
|
+
const TAIL_LIMIT = exports.MAX_GENERATOR_OUTPUT_BYTES - HEAD_BYTES - exports.MARKER_BYTES;
|
|
16
|
+
// Keeps the last `maxBytes` of `value`, cut on a code point.
|
|
17
|
+
function keepTailUtf8(value, maxBytes) {
|
|
18
|
+
let start = value.length;
|
|
19
|
+
let kept = 0;
|
|
20
|
+
while (start > 0) {
|
|
21
|
+
const low = value.charCodeAt(start - 1);
|
|
22
|
+
const high = start > 1 ? value.charCodeAt(start - 2) : 0;
|
|
23
|
+
const from = low >= 0xdc00 && low <= 0xdfff && high >= 0xd800 && high <= 0xdbff
|
|
24
|
+
? start - 2
|
|
25
|
+
: start - 1;
|
|
26
|
+
const bytes = Buffer.byteLength(value.slice(from, start));
|
|
27
|
+
if (kept + bytes > maxBytes)
|
|
28
|
+
break;
|
|
29
|
+
kept += bytes;
|
|
30
|
+
start = from;
|
|
31
|
+
}
|
|
32
|
+
return value.slice(start);
|
|
33
|
+
}
|
|
34
|
+
// The head/marker/tail buffer behind `flush()` above.
|
|
35
|
+
class BoundedOutput {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.head = [];
|
|
38
|
+
this.headBytes = 0;
|
|
39
|
+
this.headOpen = true;
|
|
40
|
+
// The head ends mid-record, so no newline separates it from the tail.
|
|
41
|
+
this.headSplit = false;
|
|
42
|
+
this.tail = [];
|
|
43
|
+
this.tailBytes = 0;
|
|
44
|
+
this.omitted = 0;
|
|
45
|
+
}
|
|
46
|
+
append(record) {
|
|
47
|
+
if (this.headOpen) {
|
|
48
|
+
const separator = this.head.length ? 1 : 0;
|
|
49
|
+
const bytes = Buffer.byteLength(record);
|
|
50
|
+
if (this.headBytes + separator + bytes <= HEAD_BYTES) {
|
|
51
|
+
this.head.push(record);
|
|
52
|
+
this.headBytes += separator + bytes;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.headOpen = false;
|
|
56
|
+
const prefix = (0, handoff_1.truncateUtf8)(record, HEAD_BYTES - this.headBytes - separator);
|
|
57
|
+
if (prefix) {
|
|
58
|
+
this.head.push(prefix);
|
|
59
|
+
this.headBytes += separator + Buffer.byteLength(prefix);
|
|
60
|
+
this.headSplit = true;
|
|
61
|
+
record = record.slice(prefix.length);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Each tail record is charged its bytes plus a following newline; the last
|
|
65
|
+
// record's unused newline is one byte of slack at render time.
|
|
66
|
+
const cost = Buffer.byteLength(record) + 1;
|
|
67
|
+
this.tail.push({ text: record, cost });
|
|
68
|
+
this.tailBytes += cost;
|
|
69
|
+
while (this.tailBytes > TAIL_LIMIT && this.tail.length > 1) {
|
|
70
|
+
const dropped = this.tail.shift().cost;
|
|
71
|
+
this.tailBytes -= dropped;
|
|
72
|
+
this.omitted += dropped;
|
|
73
|
+
}
|
|
74
|
+
if (this.tailBytes > TAIL_LIMIT) {
|
|
75
|
+
const text = keepTailUtf8(this.tail[0].text, TAIL_LIMIT - 1);
|
|
76
|
+
const kept = Buffer.byteLength(text) + 1;
|
|
77
|
+
this.omitted += this.tailBytes - kept;
|
|
78
|
+
this.tail[0] = { text, cost: kept };
|
|
79
|
+
this.tailBytes = kept;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
render() {
|
|
83
|
+
const head = this.head.join('\n');
|
|
84
|
+
const tail = this.tail.map((record) => record.text).join('\n');
|
|
85
|
+
if (this.omitted > 0) {
|
|
86
|
+
return `${head}\n${omittedMarker(this.omitted)}\n${tail}`;
|
|
87
|
+
}
|
|
88
|
+
if (!this.tail.length)
|
|
89
|
+
return head;
|
|
90
|
+
return this.headSplit ? head + tail : `${head}\n${tail}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.BoundedOutput = BoundedOutput;
|
|
94
|
+
// The same head/marker/tail bound over a byte stream: chunks carry no record
|
|
95
|
+
// boundary, so the cuts land on code points wherever the budgets run out and
|
|
96
|
+
// the result does not depend on how the stream was chunked.
|
|
97
|
+
class BoundedChunks {
|
|
98
|
+
constructor() {
|
|
99
|
+
this.head = '';
|
|
100
|
+
this.headOpen = true;
|
|
101
|
+
this.tail = '';
|
|
102
|
+
this.omitted = 0;
|
|
103
|
+
}
|
|
104
|
+
append(chunk) {
|
|
105
|
+
if (this.headOpen) {
|
|
106
|
+
const prefix = (0, handoff_1.truncateUtf8)(chunk, HEAD_BYTES - Buffer.byteLength(this.head));
|
|
107
|
+
this.head += prefix;
|
|
108
|
+
chunk = chunk.slice(prefix.length);
|
|
109
|
+
if (!chunk)
|
|
110
|
+
return;
|
|
111
|
+
this.headOpen = false;
|
|
112
|
+
}
|
|
113
|
+
const joined = this.tail + chunk;
|
|
114
|
+
this.tail = keepTailUtf8(joined, TAIL_LIMIT);
|
|
115
|
+
this.omitted += Buffer.byteLength(joined) - Buffer.byteLength(this.tail);
|
|
116
|
+
}
|
|
117
|
+
render() {
|
|
118
|
+
if (this.omitted > 0) {
|
|
119
|
+
return `${this.head}\n${omittedMarker(this.omitted)}\n${this.tail}`;
|
|
120
|
+
}
|
|
121
|
+
return this.head + this.tail;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
exports.BoundedChunks = BoundedChunks;
|
|
7
125
|
const CONSOLE_METHODS = [
|
|
8
126
|
'log',
|
|
9
127
|
'warn',
|
|
@@ -20,15 +138,13 @@ const NOOP_CAPTURE = {
|
|
|
20
138
|
restore: () => { },
|
|
21
139
|
};
|
|
22
140
|
function installGeneratorOutputCapture() {
|
|
23
|
-
// Refuse to layer if the previous install never restored. Returns a noop
|
|
24
|
-
// handle so callers' `flush()` / `restore()` calls remain safe.
|
|
25
141
|
for (const method of CONSOLE_METHODS) {
|
|
26
142
|
if (console[method][CAPTURED_MARKER]) {
|
|
27
143
|
logger_1.logger.verbose(`nx migrate: refusing to layer a second generator-output capture; the previous one was not restored. This typically means a caller skipped its \`try/finally\`. The inner caller's \`flush()\` will return empty, but its console output is still being captured by the outer install.`);
|
|
28
144
|
return NOOP_CAPTURE;
|
|
29
145
|
}
|
|
30
146
|
}
|
|
31
|
-
const buffer =
|
|
147
|
+
const buffer = new BoundedOutput();
|
|
32
148
|
const originals = new Map();
|
|
33
149
|
for (const method of CONSOLE_METHODS) {
|
|
34
150
|
originals.set(method, console[method]);
|
|
@@ -36,7 +152,7 @@ function installGeneratorOutputCapture() {
|
|
|
36
152
|
const wrapper = ((...args) => {
|
|
37
153
|
original(...args);
|
|
38
154
|
try {
|
|
39
|
-
buffer.
|
|
155
|
+
buffer.append((0, node_util_1.format)(...args));
|
|
40
156
|
}
|
|
41
157
|
catch {
|
|
42
158
|
// `format` is robust against the common pathologies but a user arg
|
|
@@ -55,7 +171,7 @@ function installGeneratorOutputCapture() {
|
|
|
55
171
|
let restored = false;
|
|
56
172
|
return {
|
|
57
173
|
flush() {
|
|
58
|
-
return buffer.
|
|
174
|
+
return buffer.render();
|
|
59
175
|
},
|
|
60
176
|
restore() {
|
|
61
177
|
if (restored)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ChildProcess } from 'child_process';
|
|
2
|
+
export declare const AGENT_GRACEFUL_EXIT_MS = 5000;
|
|
3
|
+
export declare const FORCE_KILL_WAIT_MS = 500;
|
|
4
|
+
export interface ExitInfo {
|
|
5
|
+
code?: number | null;
|
|
6
|
+
signal?: NodeJS.Signals | null;
|
|
7
|
+
error?: Error;
|
|
8
|
+
}
|
|
9
|
+
export declare function waitForExit(child: ChildProcess): Promise<ExitInfo>;
|
|
10
|
+
/**
|
|
11
|
+
* Stops a running agent process.
|
|
12
|
+
*
|
|
13
|
+
* POSIX: SIGINT, then SIGKILL after a bounded wait. SIGTERM is skipped so a
|
|
14
|
+
* child that ignored the first graceful signal does not get a second one.
|
|
15
|
+
*
|
|
16
|
+
* Windows: `taskkill /T /F`. `child.kill` is a `TerminateProcess` call whatever
|
|
17
|
+
* the signal name, which on the `cmd.exe` shim path would kill the shim and
|
|
18
|
+
* orphan the agent. `taskkill` failures are swallowed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function closeAgentSession(child: ChildProcess, exitPromise: Promise<ExitInfo>, gracefulExitMs: number, forceKillWaitMs: number): Promise<void>;
|
|
21
|
+
export declare function raceWithTimeout(promise: Promise<unknown>, timeoutMs: number): Promise<void>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FORCE_KILL_WAIT_MS = exports.AGENT_GRACEFUL_EXIT_MS = void 0;
|
|
4
|
+
exports.waitForExit = waitForExit;
|
|
5
|
+
exports.closeAgentSession = closeAgentSession;
|
|
6
|
+
exports.raceWithTimeout = raceWithTimeout;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
exports.AGENT_GRACEFUL_EXIT_MS = 5_000;
|
|
9
|
+
// Safety bound after force-kill. SIGKILL normally reaps in microseconds;
|
|
10
|
+
// the bound exists for uninterruptible kernel calls or taskkill returning
|
|
11
|
+
// before the process actually exits.
|
|
12
|
+
exports.FORCE_KILL_WAIT_MS = 500;
|
|
13
|
+
// Merge window so a paired exit + error both land in one ExitInfo. On
|
|
14
|
+
// error-only paths like spawn ENOENT, where Node fires error but never exit,
|
|
15
|
+
// this timer is the only settlement mechanism.
|
|
16
|
+
const EXIT_MERGE_WINDOW_MS = 10;
|
|
17
|
+
function waitForExit(child) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const info = {};
|
|
20
|
+
let pending = null;
|
|
21
|
+
let settled = false;
|
|
22
|
+
const settle = () => {
|
|
23
|
+
if (settled)
|
|
24
|
+
return;
|
|
25
|
+
settled = true;
|
|
26
|
+
if (pending)
|
|
27
|
+
clearTimeout(pending);
|
|
28
|
+
resolve(info);
|
|
29
|
+
};
|
|
30
|
+
const onFirst = () => {
|
|
31
|
+
if (settled || pending)
|
|
32
|
+
return;
|
|
33
|
+
pending = setTimeout(settle, EXIT_MERGE_WINDOW_MS);
|
|
34
|
+
};
|
|
35
|
+
child.on('exit', (code, signal) => {
|
|
36
|
+
info.code = code;
|
|
37
|
+
info.signal = signal;
|
|
38
|
+
onFirst();
|
|
39
|
+
});
|
|
40
|
+
child.on('error', (error) => {
|
|
41
|
+
info.error = error;
|
|
42
|
+
onFirst();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Stops a running agent process.
|
|
48
|
+
*
|
|
49
|
+
* POSIX: SIGINT, then SIGKILL after a bounded wait. SIGTERM is skipped so a
|
|
50
|
+
* child that ignored the first graceful signal does not get a second one.
|
|
51
|
+
*
|
|
52
|
+
* Windows: `taskkill /T /F`. `child.kill` is a `TerminateProcess` call whatever
|
|
53
|
+
* the signal name, which on the `cmd.exe` shim path would kill the shim and
|
|
54
|
+
* orphan the agent. `taskkill` failures are swallowed.
|
|
55
|
+
*/
|
|
56
|
+
async function closeAgentSession(child, exitPromise, gracefulExitMs, forceKillWaitMs) {
|
|
57
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
58
|
+
return;
|
|
59
|
+
if (process.platform === 'win32') {
|
|
60
|
+
await forceKillWindowsTree(child, exitPromise, forceKillWaitMs);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
child.kill('SIGINT');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// child already gone between the check above and here
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let escalation;
|
|
71
|
+
try {
|
|
72
|
+
await Promise.race([
|
|
73
|
+
exitPromise,
|
|
74
|
+
new Promise((resolve) => {
|
|
75
|
+
escalation = setTimeout(resolve, gracefulExitMs);
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
if (escalation)
|
|
81
|
+
clearTimeout(escalation);
|
|
82
|
+
}
|
|
83
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
child.kill('SIGKILL');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* child already gone */
|
|
90
|
+
}
|
|
91
|
+
await raceWithTimeout(exitPromise, forceKillWaitMs);
|
|
92
|
+
}
|
|
93
|
+
async function forceKillWindowsTree(child, exitPromise, forceKillWaitMs) {
|
|
94
|
+
const pid = child.pid;
|
|
95
|
+
// No pid means spawn itself failed, which the guard in `closeAgentSession`
|
|
96
|
+
// normally short-circuits. Without one there is nothing to taskkill.
|
|
97
|
+
if (pid !== undefined) {
|
|
98
|
+
try {
|
|
99
|
+
(0, child_process_1.execSync)(`taskkill /T /F /PID ${pid}`, {
|
|
100
|
+
stdio: 'ignore',
|
|
101
|
+
windowsHide: true,
|
|
102
|
+
// Bound so a hung Windows shell can't block the orchestrator.
|
|
103
|
+
timeout: 2_000,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* taskkill missing, pid already dead, or timed out */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
await raceWithTimeout(exitPromise, forceKillWaitMs);
|
|
111
|
+
}
|
|
112
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
113
|
+
let timer;
|
|
114
|
+
try {
|
|
115
|
+
await Promise.race([
|
|
116
|
+
promise,
|
|
117
|
+
new Promise((resolve) => {
|
|
118
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
119
|
+
}),
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
finally {
|
|
123
|
+
if (timer)
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import { AgentDefinition, AgentId } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Pre-authorizes the handoff write. Claude Code's default permission mode
|
|
4
|
+
* asks before a file write it has no allow rule for, so without this every
|
|
5
|
+
* step ends on an approval prompt for Nx's own handoff scratch.
|
|
6
|
+
*
|
|
7
|
+
* The rule reaches one run's handoffs and nothing else. Its own run
|
|
8
|
+
* directory also holds state Nx wrote and reads back, and the sibling
|
|
9
|
+
* directories belong to other runs, including an orchestrated run whose
|
|
10
|
+
* handoffs decide how its steps settle. `Edit` is the only tool name file
|
|
11
|
+
* rules are matched against, and it covers creating the file as well as
|
|
12
|
+
* correcting one already written. Prefix-less patterns resolve against the
|
|
13
|
+
* session cwd, which every caller pins to the workspace root.
|
|
14
|
+
*
|
|
15
|
+
* Returns null for a name it cannot express, which costs the approval prompt
|
|
16
|
+
* this exists to avoid. That beats widening the rule, and nothing narrower is
|
|
17
|
+
* available: Claude Code has no escape for a literal path.
|
|
18
|
+
*/
|
|
19
|
+
export declare function claudeCodeHandoffAllowedTools(runDirName: string): string | null;
|
|
2
20
|
export declare const claudeCodeDefinition: AgentDefinition;
|
|
21
|
+
/**
|
|
22
|
+
* Encodes a single-line TOML string for codex overrides. Codex treats a parse
|
|
23
|
+
* failure as literal text instead of reporting it, so the result is parsed
|
|
24
|
+
* back before it reaches the command line.
|
|
25
|
+
*/
|
|
26
|
+
export declare function encodeTomlString(value: string): string;
|
|
3
27
|
export declare const codexDefinition: AgentDefinition;
|
|
4
28
|
export declare const opencodeDefinition: AgentDefinition;
|
|
5
29
|
export declare const AGENT_DEFINITIONS: readonly AgentDefinition[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AGENT_DEFINITIONS = exports.opencodeDefinition = exports.codexDefinition = exports.claudeCodeDefinition = void 0;
|
|
4
|
+
exports.claudeCodeHandoffAllowedTools = claudeCodeHandoffAllowedTools;
|
|
5
|
+
exports.encodeTomlString = encodeTomlString;
|
|
4
6
|
exports.getAgentDefinition = getAgentDefinition;
|
|
5
7
|
const os_1 = require("os");
|
|
6
8
|
const path_1 = require("path");
|
|
@@ -35,7 +37,7 @@ const RULE_SAFE_RUN_DIR_NAME = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
|
|
|
35
37
|
* handoffs decide how its steps settle. `Edit` is the only tool name file
|
|
36
38
|
* rules are matched against, and it covers creating the file as well as
|
|
37
39
|
* correcting one already written. Prefix-less patterns resolve against the
|
|
38
|
-
* session cwd,
|
|
40
|
+
* session cwd, which every caller pins to the workspace root.
|
|
39
41
|
*
|
|
40
42
|
* Returns null for a name it cannot express, which costs the approval prompt
|
|
41
43
|
* this exists to avoid. That beats widening the rule, and nothing narrower is
|
|
@@ -74,7 +76,8 @@ function codexWellKnownPaths() {
|
|
|
74
76
|
return [];
|
|
75
77
|
}
|
|
76
78
|
// No handoff permission flag: codex's default sandbox already allows writes
|
|
77
|
-
// inside the cwd tree, and a user-hardened read-only config
|
|
79
|
+
// inside the cwd tree (`.git` excepted), and a user-hardened read-only config
|
|
80
|
+
// is theirs to keep.
|
|
78
81
|
//
|
|
79
82
|
// `-c model_instructions_file` replaces codex's built-in instructions rather
|
|
80
83
|
// than adding this context to them, so the context stays on the command line,
|
|
@@ -20,6 +20,7 @@ export declare function mkdirSafely(dir: string, purpose: string): void;
|
|
|
20
20
|
* inspection.
|
|
21
21
|
*/
|
|
22
22
|
export declare function initRunDir(workspaceRoot: string, runId: string): string;
|
|
23
|
+
export declare function truncateUtf8(value: string, maxBytes: number): string;
|
|
23
24
|
export declare function stepHandoffPath(runDir: string, migration: {
|
|
24
25
|
package: string;
|
|
25
26
|
name: string;
|
|
@@ -44,6 +45,18 @@ export type HandoffReadResult = {
|
|
|
44
45
|
* handoff read and removal wherever it points.
|
|
45
46
|
*/
|
|
46
47
|
export declare function handoffsDirState(handoffsDir: string): 'directory' | 'missing' | 'other';
|
|
48
|
+
/**
|
|
49
|
+
* Non-recursive mkdir: the run dir exists, and a symlink created after the
|
|
50
|
+
* missing-path check fails with EEXIST instead of being followed.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ensureRunSubdir(dir: string, notADirectory: () => Error): void;
|
|
53
|
+
/**
|
|
54
|
+
* Thrown by {@link readInspectedFile} when the opened descriptor is not the
|
|
55
|
+
* file the caller's lstat described: a symlink followed on Windows, or an
|
|
56
|
+
* atomic replacement between the lstat and the open.
|
|
57
|
+
*/
|
|
58
|
+
export declare class FileReplacedDuringReadError extends Error {
|
|
59
|
+
}
|
|
47
60
|
/**
|
|
48
61
|
* Reads the file `stat` describes, refusing a symlink swapped in after the
|
|
49
62
|
* caller's lstat: O_NOFOLLOW fails the open with ELOOP, and O_NONBLOCK keeps a
|
|
@@ -53,6 +66,16 @@ export declare function handoffsDirState(handoffsDir: string): 'directory' | 'mi
|
|
|
53
66
|
* the agent cannot read must not pass.
|
|
54
67
|
*/
|
|
55
68
|
export declare function readInspectedFile(filePath: string, stat: BigIntStats, replacedMessage: string): string;
|
|
69
|
+
/**
|
|
70
|
+
* Reads a file its owner publishes atomically (tmp + rename), with
|
|
71
|
+
* {@link readInspectedFile}'s refusal of a symlink or FIFO but tolerant of the
|
|
72
|
+
* publish: a rename swaps the inode between the lstat and the open, read as a
|
|
73
|
+
* replacement, so re-lstat and retry to read the new file. A pre-existing
|
|
74
|
+
* symlink or FIFO fails the isFile check before any open; a file that keeps
|
|
75
|
+
* changing past the retry budget is refused. Throws `notRegularMessage` for a
|
|
76
|
+
* non-regular file; ENOENT and ELOOP propagate.
|
|
77
|
+
*/
|
|
78
|
+
export declare function readAtomicallyPublishedFile(filePath: string, notRegularMessage: string): string;
|
|
56
79
|
/**
|
|
57
80
|
* Splits "not written yet" from "written but garbage" so callers can surface a
|
|
58
81
|
* malformed handoff instead of collapsing it into the generic
|