@webpresso/agent-kit 3.3.2 → 3.3.4
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/THIRD-PARTY-NOTICES.md +12 -10
- package/catalog/AGENTS.md.tpl +3 -5
- package/catalog/agent/rules/changeset-release.md +15 -4
- package/catalog/agent/rules/managed-tool-binaries.md +67 -0
- package/catalog/agent/rules/parallel-orchestration-quota.md +99 -1
- package/catalog/agent/rules/pre-implementation.md +36 -20
- package/catalog/agent/rules/rtk-routing.md +22 -16
- package/catalog/agent/skills/autopilot/SKILL.md +4 -3
- package/catalog/agent/skills/ultragoal/SKILL.md +11 -9
- package/catalog/agent/skills/verify/SKILL.md +12 -12
- package/catalog/compose/admin-blocks-registry.json +659 -0
- package/dist/esm/audit/hook-surface.js +5 -1
- package/dist/esm/audit/repo-guardrails.d.ts +8 -0
- package/dist/esm/audit/repo-guardrails.js +71 -1
- package/dist/esm/blueprint/core/parser.js +40 -16
- package/dist/esm/blueprint/core/validation/criteria.js +2 -1
- package/dist/esm/blueprint/execution/artifacts.js +8 -4
- package/dist/esm/blueprint/execution/frontmatter-keys.d.ts +17 -0
- package/dist/esm/blueprint/execution/frontmatter-keys.js +24 -0
- package/dist/esm/blueprint/execution/metadata.js +8 -4
- package/dist/esm/blueprint/lifecycle/audit.d.ts +21 -0
- package/dist/esm/blueprint/lifecycle/audit.js +89 -3
- package/dist/esm/blueprint/lifecycle/review-provenance.js +148 -40
- package/dist/esm/blueprint/lifecycle/rollout-anchor.d.ts +22 -0
- package/dist/esm/blueprint/lifecycle/rollout-anchor.js +37 -1
- package/dist/esm/blueprint/markdown/blocked-line.d.ts +49 -0
- package/dist/esm/blueprint/markdown/blocked-line.js +63 -0
- package/dist/esm/blueprint/markdown/helpers.js +10 -4
- package/dist/esm/blueprint/tracked-document/parser.js +3 -3
- package/dist/esm/blueprint/trust/gate-row.d.ts +55 -0
- package/dist/esm/blueprint/trust/gate-row.js +113 -0
- package/dist/esm/blueprint/trust/promotion.d.ts +17 -0
- package/dist/esm/blueprint/trust/promotion.js +19 -19
- package/dist/esm/build/atomic-file-copy.d.ts +32 -0
- package/dist/esm/build/atomic-file-copy.js +44 -0
- package/dist/esm/build/cli-mcp-parity.js +3 -2
- package/dist/esm/build/package-manifest.js +7 -2
- package/dist/esm/build/vendor-oxlint-plugins.d.ts +8 -0
- package/dist/esm/build/vendor-oxlint-plugins.js +53 -0
- package/dist/esm/cli/commands/audit-core.js +9 -1
- package/dist/esm/cli/commands/audit.js +16 -5
- package/dist/esm/cli/commands/blueprint/mutations.d.ts +15 -0
- package/dist/esm/cli/commands/blueprint/mutations.js +8 -2
- package/dist/esm/cli/commands/compile.d.ts +13 -0
- package/dist/esm/cli/commands/compile.js +182 -62
- package/dist/esm/cli/commands/format.js +17 -1
- package/dist/esm/cli/commands/gain/index.js +10 -3
- package/dist/esm/cli/commands/init/gitignore-patcher.js +6 -0
- package/dist/esm/cli/commands/init/index.js +29 -48
- package/dist/esm/cli/commands/init/mcp-spec.d.ts +54 -4
- package/dist/esm/cli/commands/init/mcp-spec.js +126 -22
- package/dist/esm/cli/commands/init/merge.js +30 -10
- package/dist/esm/cli/commands/init/scaffold-agents-md.js +5 -7
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.d.ts +56 -31
- package/dist/esm/cli/commands/init/scaffolders/codex-mcp/index.js +180 -43
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.d.ts +17 -5
- package/dist/esm/cli/commands/init/scaffolders/rtk/index.js +56 -75
- package/dist/esm/cli/commands/init/scaffolders/runtime-check/index.js +7 -6
- package/dist/esm/cli/commands/init/untracked-collision.d.ts +12 -1
- package/dist/esm/cli/commands/init/untracked-collision.js +23 -1
- package/dist/esm/cli/commands/package-manager.js +8 -2
- package/dist/esm/cli/commands/pr-help.js +1 -1
- package/dist/esm/cli/commands/pr.js +18 -4
- package/dist/esm/cli/commands/review.js +60 -6
- package/dist/esm/cli/commands/worktree/router-dispatch.d.ts +24 -2
- package/dist/esm/cli/commands/worktree/router-dispatch.js +80 -48
- package/dist/esm/cli/commands/worktree/router.js +5 -2
- package/dist/esm/cli/direct-provider-launch.js +12 -4
- package/dist/esm/compiler/flatten.d.ts +10 -1
- package/dist/esm/compiler/flatten.js +14 -4
- package/dist/esm/compose/admin-blocks-registry-integrity.d.ts +22 -0
- package/dist/esm/compose/admin-blocks-registry-integrity.js +90 -0
- package/dist/esm/compose/compose.d.ts +46 -0
- package/dist/esm/compose/compose.js +250 -0
- package/dist/esm/compose/escape-html.d.ts +5 -0
- package/dist/esm/compose/escape-html.js +12 -0
- package/dist/esm/compose/ops-report-render.d.ts +5 -0
- package/dist/esm/compose/ops-report-render.js +71 -0
- package/dist/esm/compose/ops-report-schema.d.ts +42 -0
- package/dist/esm/compose/ops-report-schema.js +60 -0
- package/dist/esm/compose/preview.d.ts +14 -0
- package/dist/esm/compose/preview.js +32 -0
- package/dist/esm/compose/registry-resolve.d.ts +64 -0
- package/dist/esm/compose/registry-resolve.js +200 -0
- package/dist/esm/config/oxlint/graphql-conventions.js +2 -5
- package/dist/esm/config/oxlint/import-hygiene.js +21 -8
- package/dist/esm/config/oxlint/oxlint-config.fixture.d.ts +1 -1
- package/dist/esm/config/oxlint/oxlint-config.fixture.js +2 -3
- package/dist/esm/config/oxlint/oxlintrc.d.ts +5 -7
- package/dist/esm/config/oxlint/oxlintrc.js +18 -21
- package/dist/esm/config/oxlint/path-roles.js +53 -0
- package/dist/esm/config/oxlint/query-patterns.js +2 -5
- package/dist/esm/daemon/domains/blueprint-exec-ledger.d.ts +86 -0
- package/dist/esm/daemon/domains/blueprint-exec-ledger.js +134 -0
- package/dist/esm/daemon/domains/blueprint-exec.d.ts +106 -0
- package/dist/esm/daemon/domains/blueprint-exec.js +161 -0
- package/dist/esm/daemon/domains/launch.d.ts +25 -0
- package/dist/esm/daemon/domains/launch.js +30 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.d.ts +12 -1
- package/dist/esm/daemon/handlers/blueprints-handlers.js +148 -6
- package/dist/esm/daemon/handlers/product.d.ts +10 -0
- package/dist/esm/daemon/handlers/product.js +10 -1
- package/dist/esm/daemon/methods.d.ts +21 -1
- package/dist/esm/daemon/methods.js +20 -0
- package/dist/esm/daemon/protocol/contract.d.ts +58 -0
- package/dist/esm/daemon/protocol/contract.js +38 -0
- package/dist/esm/daemon/server.js +7 -0
- package/dist/esm/docs-linter/blueprint-plan.js +2 -2
- package/dist/esm/errors/wp-error.js +21 -2
- package/dist/esm/git/changed-files.d.ts +20 -0
- package/dist/esm/git/changed-files.js +63 -0
- package/dist/esm/hooks/doctor.d.ts +11 -0
- package/dist/esm/hooks/doctor.js +114 -45
- package/dist/esm/hooks/pretool-guard/dev-routing.js +1 -1
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.d.ts +24 -0
- package/dist/esm/hooks/pretool-guard/rtk-rewrite.js +50 -0
- package/dist/esm/hooks/pretool-guard/runner.js +6 -0
- package/dist/esm/hooks/status/index.js +1 -1
- package/dist/esm/hooks/stop/qa-changed-files.js +6 -1
- package/dist/esm/mcp/blueprint/_shared/lifecycle.js +39 -11
- package/dist/esm/mcp/blueprint/handlers/review-log.d.ts +14 -7
- package/dist/esm/mcp/blueprint/handlers/review-log.js +46 -22
- package/dist/esm/mcp/blueprint/handlers/validate.js +6 -0
- package/dist/esm/mcp/blueprint/registration.js +1 -1
- package/dist/esm/mcp/tools/_names.d.ts +1 -1
- package/dist/esm/mcp/tools/_names.js +4 -0
- package/dist/esm/mcp/tools/_registry.js +8 -0
- package/dist/esm/mcp/tools/_shared/redact.d.ts +22 -0
- package/dist/esm/mcp/tools/_shared/redact.js +114 -8
- package/dist/esm/mcp/tools/gain.js +28 -2
- package/dist/esm/mcp/tools/release-progress.d.ts +9 -0
- package/dist/esm/mcp/tools/release-progress.js +110 -0
- package/dist/esm/mcp/tools/worktree.js +65 -3
- package/dist/esm/mcp/tools/wp-ui-blocks-list.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-blocks-list.js +43 -0
- package/dist/esm/mcp/tools/wp-ui-compose.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-compose.js +96 -0
- package/dist/esm/mcp/tools/wp-ui-preview.d.ts +3 -0
- package/dist/esm/mcp/tools/wp-ui-preview.js +59 -0
- package/dist/esm/package.json +2 -0
- package/dist/esm/review/lifecycle-writers.d.ts +76 -0
- package/dist/esm/review/lifecycle-writers.js +330 -0
- package/dist/esm/review/subject.js +66 -32
- package/dist/esm/status/snapshot.d.ts +5 -0
- package/dist/esm/status/snapshot.js +27 -2
- package/dist/esm/test/shard-durations.json +0 -1
- package/dist/esm/test-helpers/global-setup.js +38 -3
- package/dist/esm/tool-runtime/managed-rtk.d.ts +44 -0
- package/dist/esm/tool-runtime/managed-rtk.js +275 -0
- package/dist/esm/tool-runtime/resolve-runner.js +18 -194
- package/dist/esm/utils/package-root.d.ts +32 -0
- package/dist/esm/utils/package-root.js +82 -0
- package/dist/esm/worktrees/git-metadata.d.ts +41 -0
- package/dist/esm/worktrees/git-metadata.js +48 -0
- package/package.json +27 -21
- package/dist/esm/config/oxlint/code-safety.d.ts +0 -18
- package/dist/esm/config/oxlint/foundation-purity.d.ts +0 -21
- package/dist/esm/config/oxlint/graphql-conventions.d.ts +0 -23
- package/dist/esm/config/oxlint/import-hygiene.d.ts +0 -34
- package/dist/esm/config/oxlint/index.d.ts +0 -22
- package/dist/esm/config/oxlint/index.js +0 -28
- package/dist/esm/config/oxlint/monorepo-paths.d.ts +0 -22
- package/dist/esm/config/oxlint/query-patterns.d.ts +0 -24
- package/dist/esm/config/oxlint/testing-quality.d.ts +0 -35
- package/dist/esm/config/oxlint/tier-boundaries.d.ts +0 -35
- package/dist/esm/output-transforms/rulesync.d.ts +0 -2
- package/dist/esm/output-transforms/rulesync.js +0 -79
|
@@ -40,13 +40,38 @@ async function withDistBuildLock(fn) {
|
|
|
40
40
|
// idempotent, re-checked-under-lock build; every other process blocks until that release, then finds
|
|
41
41
|
// the sentinels already satisfied and returns immediately. In the steady state (dist/ already built)
|
|
42
42
|
// this is a single existsSync check with no lock contention at all.
|
|
43
|
+
const AGENT_CORE_SENTINEL = join(REPO_ROOT, "packages/agent-core/dist/esm/repo-root/index.js");
|
|
44
|
+
const AGENT_CONFIG_OXLINT_SENTINEL = join(REPO_ROOT, "packages/agent-config/dist/esm/oxlint/import-hygiene.js");
|
|
45
|
+
function buildWorkspacePackage(filter) {
|
|
46
|
+
execFileSync("pnpm", ["--filter", filter, "run", "build"], {
|
|
47
|
+
cwd: REPO_ROOT,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
env: { ...process.env, HUSKY: "0" },
|
|
50
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const VENDORED_OXLINT_PLUGIN = join(REPO_ROOT, "dist/esm/config/oxlint/import-hygiene.js");
|
|
54
|
+
/** agent-config oxlint SSOT is imported at build/test time (rules map + vendor source). */
|
|
55
|
+
function ensureAgentConfigBuiltSync() {
|
|
56
|
+
if (!existsSync(AGENT_CORE_SENTINEL)) {
|
|
57
|
+
buildWorkspacePackage("@webpresso/agent-core");
|
|
58
|
+
}
|
|
59
|
+
if (!existsSync(AGENT_CONFIG_OXLINT_SENTINEL)) {
|
|
60
|
+
buildWorkspacePackage("@webpresso/agent-config");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
43
63
|
async function ensureDistPrepared() {
|
|
44
|
-
// Fast path:
|
|
45
|
-
if (existsSync(DIST_SENTINEL) &&
|
|
64
|
+
// Fast path: agent-kit dist + migration assets + vendored inject plugins.
|
|
65
|
+
if (existsSync(DIST_SENTINEL) &&
|
|
66
|
+
existsSync(MIGRATION_SENTINEL) &&
|
|
67
|
+
existsSync(VENDORED_OXLINT_PLUGIN) &&
|
|
68
|
+
existsSync(AGENT_CONFIG_OXLINT_SENTINEL)) {
|
|
46
69
|
return;
|
|
70
|
+
}
|
|
47
71
|
await withDistBuildLock(async () => {
|
|
48
72
|
let builtDist = false;
|
|
49
73
|
// Re-check under the lock: another run may have completed the build while we waited.
|
|
74
|
+
ensureAgentConfigBuiltSync();
|
|
50
75
|
if (!existsSync(DIST_SENTINEL)) {
|
|
51
76
|
execFileSync("./node_modules/.bin/tshy", [], {
|
|
52
77
|
cwd: REPO_ROOT,
|
|
@@ -73,7 +98,17 @@ async function ensureDistPrepared() {
|
|
|
73
98
|
env: { ...process.env, HUSKY: "0" },
|
|
74
99
|
});
|
|
75
100
|
}
|
|
76
|
-
if (builtDist) {
|
|
101
|
+
if (builtDist || !existsSync(VENDORED_OXLINT_PLUGIN)) {
|
|
102
|
+
execFileSync("bun", ["src/build/generate-oxlintrc.ts"], {
|
|
103
|
+
cwd: REPO_ROOT,
|
|
104
|
+
encoding: "utf8",
|
|
105
|
+
env: { ...process.env, HUSKY: "0" },
|
|
106
|
+
});
|
|
107
|
+
execFileSync("bun", ["src/build/vendor-oxlint-plugins.ts"], {
|
|
108
|
+
cwd: REPO_ROOT,
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
env: { ...process.env, HUSKY: "0" },
|
|
111
|
+
});
|
|
77
112
|
execFileSync("bun", ["src/build/normalize-tsconfig-json-exports.ts"], {
|
|
78
113
|
cwd: REPO_ROOT,
|
|
79
114
|
encoding: "utf8",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed RTK binary — single ownership for install + resolve + capability probe.
|
|
3
|
+
*
|
|
4
|
+
* Consumers never brew/cargo install rtk. Setup downloads a pinned upstream
|
|
5
|
+
* release into the Webpresso cache and every call site uses that absolute path.
|
|
6
|
+
* Filters stay upstream (Apache-2.0); we only own packaging + path resolution.
|
|
7
|
+
*/
|
|
8
|
+
/** Pinned upstream token-killer release (https://github.com/rtk-ai/rtk). */
|
|
9
|
+
export declare const RTK_MANAGED_VERSION = "0.43.0";
|
|
10
|
+
export type ManagedRtkEnsureResult = {
|
|
11
|
+
readonly ok: true;
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly installed: boolean;
|
|
14
|
+
} | {
|
|
15
|
+
readonly ok: false;
|
|
16
|
+
readonly error: string;
|
|
17
|
+
};
|
|
18
|
+
export type ManagedRtkDownloader = (url: string, destFile: string) => void;
|
|
19
|
+
/** Test seam. */
|
|
20
|
+
export declare function resetManagedRtkCacheForTests(): void;
|
|
21
|
+
export declare function managedRtkAssetName(platform?: NodeJS.Platform, arch?: string): string | null;
|
|
22
|
+
export declare function managedRtkDownloadUrl(version?: string): string | null;
|
|
23
|
+
/** Cache dir: `envPaths("webpresso").cache/tools/rtk/<version>/`. */
|
|
24
|
+
export declare function managedRtkInstallDir(version?: string): string;
|
|
25
|
+
export declare function managedRtkExecutablePath(version?: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the managed executable if present on disk (no network, no probe).
|
|
28
|
+
* Prefers explicit WP_RTK_BIN, then managed cache install.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveManagedRtkExecutable(): string | null;
|
|
31
|
+
/** Capability probe against a concrete binary (or managed path). Fail-open. */
|
|
32
|
+
export declare function probeRtkCapabilityAt(executable: string): boolean;
|
|
33
|
+
/** Process-cached: managed binary exists and passes capability probe. */
|
|
34
|
+
export declare function isManagedRtkAvailable(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Ensure the pinned managed rtk binary is on disk. Downloads once into the
|
|
37
|
+
* Webpresso cache. No brew/cargo. Idempotent.
|
|
38
|
+
*/
|
|
39
|
+
export declare function ensureManagedRtk(options?: {
|
|
40
|
+
readonly downloader?: ManagedRtkDownloader;
|
|
41
|
+
readonly version?: string;
|
|
42
|
+
}): ManagedRtkEnsureResult;
|
|
43
|
+
/** Write a tiny marker file for diagnostics (optional). */
|
|
44
|
+
export declare function writeManagedRtkVersionMarker(path: string, version: string): void;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managed RTK binary — single ownership for install + resolve + capability probe.
|
|
3
|
+
*
|
|
4
|
+
* Consumers never brew/cargo install rtk. Setup downloads a pinned upstream
|
|
5
|
+
* release into the Webpresso cache and every call site uses that absolute path.
|
|
6
|
+
* Filters stay upstream (Apache-2.0); we only own packaging + path resolution.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { randomBytes } from "node:crypto";
|
|
10
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { basename, dirname, join } from "node:path";
|
|
13
|
+
import envPaths from "env-paths";
|
|
14
|
+
/** Pinned upstream token-killer release (https://github.com/rtk-ai/rtk). */
|
|
15
|
+
export const RTK_MANAGED_VERSION = "0.43.0";
|
|
16
|
+
const RTK_PROBE_TIMEOUT_MS = 3000;
|
|
17
|
+
const RTK_CAPABILITY_ARGS = ["gain", "--help"];
|
|
18
|
+
const BINARY_NAME = process.platform === "win32" ? "rtk.exe" : "rtk";
|
|
19
|
+
let cachedExecutable;
|
|
20
|
+
let cachedCapability;
|
|
21
|
+
/** Test seam. */
|
|
22
|
+
export function resetManagedRtkCacheForTests() {
|
|
23
|
+
cachedExecutable = undefined;
|
|
24
|
+
cachedCapability = undefined;
|
|
25
|
+
}
|
|
26
|
+
export function managedRtkAssetName(platform = process.platform, arch = process.arch) {
|
|
27
|
+
if (platform === "darwin" && arch === "arm64")
|
|
28
|
+
return "rtk-aarch64-apple-darwin.tar.gz";
|
|
29
|
+
if (platform === "darwin" && arch === "x64")
|
|
30
|
+
return "rtk-x86_64-apple-darwin.tar.gz";
|
|
31
|
+
if (platform === "linux" && arch === "arm64")
|
|
32
|
+
return "rtk-aarch64-unknown-linux-gnu.tar.gz";
|
|
33
|
+
if (platform === "linux" && arch === "x64")
|
|
34
|
+
return "rtk-x86_64-unknown-linux-musl.tar.gz";
|
|
35
|
+
if (platform === "win32" && arch === "x64")
|
|
36
|
+
return "rtk-x86_64-pc-windows-msvc.zip";
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function managedRtkDownloadUrl(version = RTK_MANAGED_VERSION) {
|
|
40
|
+
const asset = managedRtkAssetName();
|
|
41
|
+
if (!asset)
|
|
42
|
+
return null;
|
|
43
|
+
return `https://github.com/rtk-ai/rtk/releases/download/v${version}/${asset}`;
|
|
44
|
+
}
|
|
45
|
+
/** Cache dir: `envPaths("webpresso").cache/tools/rtk/<version>/`. */
|
|
46
|
+
export function managedRtkInstallDir(version = RTK_MANAGED_VERSION) {
|
|
47
|
+
const cacheRoot = process.env.WP_RTK_CACHE_ROOT ?? envPaths("webpresso", { suffix: "" }).cache;
|
|
48
|
+
return join(cacheRoot, "tools", "rtk", version);
|
|
49
|
+
}
|
|
50
|
+
export function managedRtkExecutablePath(version = RTK_MANAGED_VERSION) {
|
|
51
|
+
return join(managedRtkInstallDir(version), BINARY_NAME);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the managed executable if present on disk (no network, no probe).
|
|
55
|
+
* Prefers explicit WP_RTK_BIN, then managed cache install.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveManagedRtkExecutable() {
|
|
58
|
+
// Skip is process-env policy for this call — do not poison the path cache.
|
|
59
|
+
if (process.env.WP_SKIP_RTK === "1")
|
|
60
|
+
return null;
|
|
61
|
+
if (cachedExecutable !== undefined)
|
|
62
|
+
return cachedExecutable;
|
|
63
|
+
const override = process.env.WP_RTK_BIN?.trim();
|
|
64
|
+
if (override && existsSync(override)) {
|
|
65
|
+
cachedExecutable = override;
|
|
66
|
+
return cachedExecutable;
|
|
67
|
+
}
|
|
68
|
+
const managed = managedRtkExecutablePath();
|
|
69
|
+
if (existsSync(managed)) {
|
|
70
|
+
cachedExecutable = managed;
|
|
71
|
+
return cachedExecutable;
|
|
72
|
+
}
|
|
73
|
+
cachedExecutable = null;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/** Capability probe against a concrete binary (or managed path). Fail-open. */
|
|
77
|
+
export function probeRtkCapabilityAt(executable) {
|
|
78
|
+
try {
|
|
79
|
+
const result = spawnSync(executable, [...RTK_CAPABILITY_ARGS], {
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
timeout: RTK_PROBE_TIMEOUT_MS,
|
|
82
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
83
|
+
});
|
|
84
|
+
return result.error == null && result.status === 0;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Process-cached: managed binary exists and passes capability probe. */
|
|
91
|
+
export function isManagedRtkAvailable() {
|
|
92
|
+
if (cachedCapability !== undefined)
|
|
93
|
+
return cachedCapability;
|
|
94
|
+
if (process.env.WP_SKIP_RTK === "1") {
|
|
95
|
+
cachedCapability = false;
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const path = resolveManagedRtkExecutable();
|
|
99
|
+
if (!path) {
|
|
100
|
+
cachedCapability = false;
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
cachedCapability = probeRtkCapabilityAt(path);
|
|
104
|
+
return cachedCapability;
|
|
105
|
+
}
|
|
106
|
+
function defaultDownloader(url, destFile) {
|
|
107
|
+
// Prefer curl when present (HTTP/2, redirects); fall back to node fetch via spawn node -e is avoided.
|
|
108
|
+
const curl = spawnSync("curl", ["-fsSL", "--retry", "2", "--retry-delay", "1", "-o", destFile, url], { encoding: "utf8", timeout: 120_000 });
|
|
109
|
+
if (curl.error || curl.status !== 0) {
|
|
110
|
+
throw new Error(`download failed (${curl.status ?? "error"}): ${String(curl.stderr ?? curl.error ?? "").trim() || url}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isRegularFile(path) {
|
|
114
|
+
try {
|
|
115
|
+
return lstatSync(path).isFile();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Prefer a real file named rtk / rtk.exe — ignore directories of the same name. */
|
|
122
|
+
function findExtractedBinary(root) {
|
|
123
|
+
const stack = [root];
|
|
124
|
+
while (stack.length > 0) {
|
|
125
|
+
const dir = stack.pop();
|
|
126
|
+
let entries;
|
|
127
|
+
try {
|
|
128
|
+
entries = readdirSync(dir);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
for (const name of entries) {
|
|
134
|
+
const full = join(dir, name);
|
|
135
|
+
if ((name === BINARY_NAME || name === "rtk") && isRegularFile(full)) {
|
|
136
|
+
return full;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
// shallow walk only one extra level for nested release layouts
|
|
140
|
+
const nested = readdirSync(full);
|
|
141
|
+
for (const child of nested) {
|
|
142
|
+
const nestedFull = join(full, child);
|
|
143
|
+
if ((child === BINARY_NAME || child === "rtk") && isRegularFile(nestedFull)) {
|
|
144
|
+
return nestedFull;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// not a dir
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function extractArchive(archivePath, extractDir) {
|
|
156
|
+
mkdirSync(extractDir, { recursive: true });
|
|
157
|
+
if (archivePath.endsWith(".zip")) {
|
|
158
|
+
const unzip = spawnSync("unzip", ["-qo", archivePath, "-d", extractDir], {
|
|
159
|
+
encoding: "utf8",
|
|
160
|
+
timeout: 60_000,
|
|
161
|
+
});
|
|
162
|
+
if (unzip.status !== 0) {
|
|
163
|
+
throw new Error(`unzip failed: ${String(unzip.stderr ?? "").trim()}`);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const tar = spawnSync("tar", ["-xzf", archivePath, "-C", extractDir], {
|
|
168
|
+
encoding: "utf8",
|
|
169
|
+
timeout: 60_000,
|
|
170
|
+
});
|
|
171
|
+
if (tar.status !== 0) {
|
|
172
|
+
throw new Error(`tar extract failed: ${String(tar.stderr ?? "").trim()}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Ensure the pinned managed rtk binary is on disk. Downloads once into the
|
|
177
|
+
* Webpresso cache. No brew/cargo. Idempotent.
|
|
178
|
+
*/
|
|
179
|
+
export function ensureManagedRtk(options = {}) {
|
|
180
|
+
if (process.env.WP_SKIP_RTK === "1") {
|
|
181
|
+
return { ok: false, error: "WP_SKIP_RTK=1" };
|
|
182
|
+
}
|
|
183
|
+
const version = options.version ?? RTK_MANAGED_VERSION;
|
|
184
|
+
const dest = managedRtkExecutablePath(version);
|
|
185
|
+
if (existsSync(dest) && probeRtkCapabilityAt(dest)) {
|
|
186
|
+
resetManagedRtkCacheForTests();
|
|
187
|
+
cachedExecutable = dest;
|
|
188
|
+
cachedCapability = true;
|
|
189
|
+
return { ok: true, path: dest, installed: false };
|
|
190
|
+
}
|
|
191
|
+
const url = managedRtkDownloadUrl(version);
|
|
192
|
+
if (!url) {
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
error: `no managed rtk asset for ${process.platform}/${process.arch}`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const installDir = managedRtkInstallDir(version);
|
|
199
|
+
mkdirSync(installDir, { recursive: true });
|
|
200
|
+
const work = mkdtempSync(join(tmpdir(), "wp-rtk-"));
|
|
201
|
+
try {
|
|
202
|
+
const archiveName = basename(url);
|
|
203
|
+
const archivePath = join(work, archiveName);
|
|
204
|
+
const download = options.downloader ?? defaultDownloader;
|
|
205
|
+
download(url, archivePath);
|
|
206
|
+
const extractDir = join(work, "extract");
|
|
207
|
+
extractArchive(archivePath, extractDir);
|
|
208
|
+
const found = findExtractedBinary(extractDir);
|
|
209
|
+
if (!found) {
|
|
210
|
+
return { ok: false, error: `archive did not contain ${BINARY_NAME}` };
|
|
211
|
+
}
|
|
212
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
213
|
+
// Stage under a unique name so concurrent setup never clobbers a live dest
|
|
214
|
+
// mid-probe, and we only delete our own staged file on failure.
|
|
215
|
+
const staged = join(installDir, `.rtk-staging-${process.pid}-${Date.now()}-${randomBytes(6).toString("hex")}`);
|
|
216
|
+
try {
|
|
217
|
+
try {
|
|
218
|
+
renameSync(found, staged);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
const cp = spawnSync("cp", [found, staged], { encoding: "utf8" });
|
|
222
|
+
if (cp.status !== 0) {
|
|
223
|
+
return { ok: false, error: `failed to stage managed rtk at ${staged}` };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (process.platform !== "win32") {
|
|
227
|
+
chmodSync(staged, 0o755);
|
|
228
|
+
}
|
|
229
|
+
if (!probeRtkCapabilityAt(staged)) {
|
|
230
|
+
return { ok: false, error: "downloaded rtk failed capability probe (rtk gain --help)" };
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
renameSync(staged, dest);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Cross-device rename: replace dest via copy then drop stage.
|
|
237
|
+
const cp = spawnSync("cp", [staged, dest], { encoding: "utf8" });
|
|
238
|
+
if (cp.status !== 0) {
|
|
239
|
+
return { ok: false, error: `failed to place managed rtk at ${dest}` };
|
|
240
|
+
}
|
|
241
|
+
if (process.platform !== "win32") {
|
|
242
|
+
chmodSync(dest, 0o755);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
try {
|
|
248
|
+
rmSync(staged, { force: true });
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// ignore
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
resetManagedRtkCacheForTests();
|
|
255
|
+
cachedExecutable = dest;
|
|
256
|
+
cachedCapability = true;
|
|
257
|
+
return { ok: true, path: dest, installed: true };
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
261
|
+
return { ok: false, error: message };
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
try {
|
|
265
|
+
rmSync(work, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
// ignore
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Write a tiny marker file for diagnostics (optional). */
|
|
273
|
+
export function writeManagedRtkVersionMarker(path, version) {
|
|
274
|
+
writeFileSync(`${path}.version`, `${version}\n`, "utf8");
|
|
275
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import { Worker } from "node:worker_threads";
|
|
6
4
|
import { resolveAgentKitPackageRoot } from "#cli/commands/init/package-root.js";
|
|
7
5
|
import { resolveGlobalCapableVpCommand } from "#cli/global-vp.js";
|
|
8
6
|
import { pathCandidates } from "#runtime/command-exists.js";
|
|
9
7
|
import { readTrustedJsonFile } from "#shared-utils/read-json-file.js";
|
|
8
|
+
import { isManagedRtkAvailable, resolveManagedRtkExecutable } from "./managed-rtk.js";
|
|
10
9
|
const require = createRequire(import.meta.url);
|
|
11
10
|
const MANAGED_TOOL_PREFIX = {
|
|
12
11
|
oxfmt: {
|
|
@@ -33,196 +32,8 @@ const MANAGED_TOOL_PREFIX = {
|
|
|
33
32
|
wrangler: { packageName: "wrangler", binName: "wrangler" },
|
|
34
33
|
};
|
|
35
34
|
let rtkAvailable = null;
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// binary does not. Probe that capability instead of `--version`, which *any*
|
|
39
|
-
// `rtk` on PATH answers with exit 0 — trusting it would route wrapped commands
|
|
40
|
-
// (typecheck/qa/test gates) through a foreign binary that may mangle args or
|
|
41
|
-
// drop the wrapped exit code, masking a failing gate as green.
|
|
42
|
-
const RTK_CAPABILITY_ARGS = ["gain", "--help"];
|
|
43
|
-
// Measured cold cost of `rtk gain --help` is ~11ms; 3s is generous headroom.
|
|
44
|
-
// A probe that exceeds it is a broken or wrong binary, so degrade to unfiltered
|
|
45
|
-
// output rather than let a hung `rtk` stall every wrapped command (per
|
|
46
|
-
// no-timeout-as-fix: the bound surfaces the fault, it does not silence it).
|
|
47
|
-
const RTK_PROBE_TIMEOUT_MS = 3000;
|
|
48
|
-
const RTK_PROBE_ESCALATION_DELAY_MS = 250;
|
|
49
|
-
const RTK_PROBE_FORCE_KILL_OBSERVE_MS = 1000;
|
|
50
|
-
const RTK_PROBE_ADAPTER_SLACK_MS = 1000;
|
|
51
|
-
const RTK_PROBE_ADAPTER_TIMEOUT_MS = RTK_PROBE_TIMEOUT_MS +
|
|
52
|
-
RTK_PROBE_ESCALATION_DELAY_MS +
|
|
53
|
-
RTK_PROBE_FORCE_KILL_OBSERVE_MS +
|
|
54
|
-
RTK_PROBE_ADAPTER_SLACK_MS;
|
|
55
|
-
const RTK_PROBE_STATUS_INDEX = 0;
|
|
56
|
-
const RTK_PROBE_PID_INDEX = 1;
|
|
57
|
-
const RTK_PROBE_PENDING = 0;
|
|
58
|
-
const RTK_PROBE_AVAILABLE = 1;
|
|
59
|
-
const RTK_PROBE_UNAVAILABLE = 2;
|
|
60
|
-
const RTK_PROBE_CLEANUP_FAILED = 3;
|
|
61
|
-
// `resolveRunner` is intentionally synchronous because it is also used by pure
|
|
62
|
-
// command planners. The worker keeps that contract while allowing the inner
|
|
63
|
-
// probe to use the same async, owned-process-group supervisor as long-running
|
|
64
|
-
// command surfaces. The main thread only waits on shared state; it never owns a
|
|
65
|
-
// timeout-killed wrapper whose descendants can escape.
|
|
66
|
-
const RTK_PROBE_WORKER_SOURCE = String.raw `
|
|
67
|
-
(async () => {
|
|
68
|
-
const [{ spawn }, { workerData }] = await Promise.all([
|
|
69
|
-
import("node:child_process"),
|
|
70
|
-
import("node:worker_threads"),
|
|
71
|
-
]);
|
|
72
|
-
const state = new Int32Array(workerData.state);
|
|
73
|
-
let settled = false;
|
|
74
|
-
const finish = (status) => {
|
|
75
|
-
if (settled) return;
|
|
76
|
-
settled = true;
|
|
77
|
-
Atomics.store(state, workerData.statusIndex, status);
|
|
78
|
-
Atomics.notify(state, workerData.statusIndex);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const {
|
|
83
|
-
createTerminationAwareChild,
|
|
84
|
-
terminateOwnedProcessTreeWithEscalation,
|
|
85
|
-
} = await import(workerData.supervisorModuleUrl);
|
|
86
|
-
if (
|
|
87
|
-
typeof createTerminationAwareChild !== "function" ||
|
|
88
|
-
typeof terminateOwnedProcessTreeWithEscalation !== "function"
|
|
89
|
-
) {
|
|
90
|
-
finish(workerData.cleanupFailed);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const child = spawn("rtk", workerData.args, {
|
|
95
|
-
detached: process.platform !== "win32",
|
|
96
|
-
stdio: "ignore",
|
|
97
|
-
windowsHide: true,
|
|
98
|
-
});
|
|
99
|
-
if (child.pid) Atomics.store(state, workerData.pidIndex, child.pid);
|
|
100
|
-
const tracked = createTerminationAwareChild(child, {
|
|
101
|
-
kind: "owned-process-group",
|
|
102
|
-
detached: true,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
let resolving = false;
|
|
106
|
-
let timer;
|
|
107
|
-
const resolveAfterCleanup = async (status) => {
|
|
108
|
-
if (resolving) return;
|
|
109
|
-
resolving = true;
|
|
110
|
-
if (timer) clearTimeout(timer);
|
|
111
|
-
await terminateOwnedProcessTreeWithEscalation(tracked, {
|
|
112
|
-
escalationDelayMs: workerData.escalationDelayMs,
|
|
113
|
-
});
|
|
114
|
-
finish(status);
|
|
115
|
-
};
|
|
116
|
-
const settle = (status) => {
|
|
117
|
-
void resolveAfterCleanup(status).catch(() => finish(workerData.cleanupFailed));
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
timer = setTimeout(() => {
|
|
121
|
-
settle(workerData.unavailable);
|
|
122
|
-
}, workerData.timeoutMs);
|
|
123
|
-
child.once("error", () => {
|
|
124
|
-
settle(workerData.unavailable);
|
|
125
|
-
});
|
|
126
|
-
child.once("close", (code) => {
|
|
127
|
-
settle(code === 0 ? workerData.available : workerData.unavailable);
|
|
128
|
-
});
|
|
129
|
-
} catch {
|
|
130
|
-
finish(workerData.cleanupFailed);
|
|
131
|
-
}
|
|
132
|
-
})();
|
|
133
|
-
`;
|
|
134
|
-
function probeRtkAvailability() {
|
|
135
|
-
if (rtkAvailable !== null)
|
|
136
|
-
return rtkAvailable;
|
|
137
|
-
const supervisorModuleUrl = resolveProbeSupervisorModuleUrl();
|
|
138
|
-
if (supervisorModuleUrl === null) {
|
|
139
|
-
rtkAvailable = false;
|
|
140
|
-
return rtkAvailable;
|
|
141
|
-
}
|
|
142
|
-
const state = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 2));
|
|
143
|
-
let worker;
|
|
144
|
-
try {
|
|
145
|
-
worker = new Worker(RTK_PROBE_WORKER_SOURCE, {
|
|
146
|
-
eval: true,
|
|
147
|
-
workerData: {
|
|
148
|
-
args: RTK_CAPABILITY_ARGS,
|
|
149
|
-
available: RTK_PROBE_AVAILABLE,
|
|
150
|
-
unavailable: RTK_PROBE_UNAVAILABLE,
|
|
151
|
-
cleanupFailed: RTK_PROBE_CLEANUP_FAILED,
|
|
152
|
-
statusIndex: RTK_PROBE_STATUS_INDEX,
|
|
153
|
-
pidIndex: RTK_PROBE_PID_INDEX,
|
|
154
|
-
state: state.buffer,
|
|
155
|
-
supervisorModuleUrl,
|
|
156
|
-
timeoutMs: RTK_PROBE_TIMEOUT_MS,
|
|
157
|
-
escalationDelayMs: RTK_PROBE_ESCALATION_DELAY_MS,
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
worker.unref();
|
|
161
|
-
Atomics.wait(state, RTK_PROBE_STATUS_INDEX, RTK_PROBE_PENDING, RTK_PROBE_ADAPTER_TIMEOUT_MS);
|
|
162
|
-
const status = Atomics.load(state, RTK_PROBE_STATUS_INDEX);
|
|
163
|
-
if (status === RTK_PROBE_PENDING || status === RTK_PROBE_CLEANUP_FAILED) {
|
|
164
|
-
forceReclaimProbe(Atomics.load(state, RTK_PROBE_PID_INDEX));
|
|
165
|
-
}
|
|
166
|
-
rtkAvailable = status === RTK_PROBE_AVAILABLE;
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
rtkAvailable = false;
|
|
170
|
-
}
|
|
171
|
-
finally {
|
|
172
|
-
void worker?.terminate();
|
|
173
|
-
}
|
|
174
|
-
return rtkAvailable;
|
|
175
|
-
}
|
|
176
|
-
function resolveProbeSupervisorModuleUrl() {
|
|
177
|
-
const sourceExtension = import.meta.url.endsWith(".ts") ? "ts" : "js";
|
|
178
|
-
const adjacent = new URL(`../utils/process-supervisor.${sourceExtension}`, import.meta.url);
|
|
179
|
-
try {
|
|
180
|
-
if (adjacent.protocol === "file:" && existsSync(fileURLToPath(adjacent))) {
|
|
181
|
-
return adjacent.href;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
// Native runtimes can expose a virtual import.meta.url; fall back to the package root.
|
|
186
|
-
}
|
|
187
|
-
const packageRoot = resolveAgentKitPackageRoot({ moduleUrl: import.meta.url });
|
|
188
|
-
if (packageRoot === null)
|
|
189
|
-
return null;
|
|
190
|
-
for (const candidate of [
|
|
191
|
-
join(packageRoot, "dist", "esm", "utils", "process-supervisor.js"),
|
|
192
|
-
join(packageRoot, "src", "utils", "process-supervisor.ts"),
|
|
193
|
-
]) {
|
|
194
|
-
if (existsSync(candidate))
|
|
195
|
-
return pathToFileURL(candidate).href;
|
|
196
|
-
}
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
function forceReclaimProbe(pid) {
|
|
200
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
201
|
-
return;
|
|
202
|
-
const target = process.platform === "win32" ? pid : -pid;
|
|
203
|
-
try {
|
|
204
|
-
process.kill(target, "SIGKILL");
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
if (error.code !== "ESRCH")
|
|
208
|
-
throw error;
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
const deadline = Date.now() + RTK_PROBE_FORCE_KILL_OBSERVE_MS;
|
|
212
|
-
const sleeper = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
213
|
-
while (isProcessTargetAlive(target) && Date.now() < deadline) {
|
|
214
|
-
Atomics.wait(sleeper, 0, 0, 20);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
function isProcessTargetAlive(target) {
|
|
218
|
-
try {
|
|
219
|
-
process.kill(target, 0);
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
return error.code !== "ESRCH";
|
|
224
|
-
}
|
|
225
|
-
}
|
|
35
|
+
// Capability probing lives in managed-rtk (absolute binary + `gain --help`).
|
|
36
|
+
// Hermetic tests use setRtkAvailabilityProbeForTest to force wrap/degrade.
|
|
226
37
|
export function setRtkAvailabilityProbeForTest(value) {
|
|
227
38
|
rtkAvailable = value;
|
|
228
39
|
}
|
|
@@ -232,11 +43,24 @@ export function resolveOutputPolicy(outputPolicy, filterOutput) {
|
|
|
232
43
|
function withOptionalRtk(resolution, outputPolicy) {
|
|
233
44
|
if (outputPolicy !== "rtk-filtered")
|
|
234
45
|
return resolution;
|
|
235
|
-
|
|
46
|
+
// Hermetic test seam: setRtkAvailabilityProbeForTest(true) wraps with bare "rtk".
|
|
47
|
+
if (rtkAvailable === true) {
|
|
48
|
+
return {
|
|
49
|
+
...resolution,
|
|
50
|
+
command: "rtk",
|
|
51
|
+
args: [resolution.command, ...resolution.args],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (rtkAvailable === false)
|
|
55
|
+
return resolution;
|
|
56
|
+
if (!isManagedRtkAvailable())
|
|
57
|
+
return resolution;
|
|
58
|
+
const rtkBin = resolveManagedRtkExecutable();
|
|
59
|
+
if (!rtkBin)
|
|
236
60
|
return resolution;
|
|
237
61
|
return {
|
|
238
62
|
...resolution,
|
|
239
|
-
command:
|
|
63
|
+
command: rtkBin,
|
|
240
64
|
args: [resolution.command, ...resolution.args],
|
|
241
65
|
};
|
|
242
66
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate an installed package's root directory from its bare name.
|
|
3
|
+
*
|
|
4
|
+
* The obvious spelling — `require.resolve("<name>/package.json")` — is a trap.
|
|
5
|
+
* Node honours the target package's `exports` map, and a package that declares
|
|
6
|
+
* only `"."` (rulesync) makes that throw `ERR_PACKAGE_PATH_NOT_EXPORTED`. Every
|
|
7
|
+
* caller in this repo wrapped it in `try {} catch { return null }`, so the throw
|
|
8
|
+
* turned into a silently missing capability rather than a visible failure.
|
|
9
|
+
*
|
|
10
|
+
* Resolving the package's exported entry works instead, because any package
|
|
11
|
+
* meant to be imported has one. From there, walk up to the directory whose
|
|
12
|
+
* `package.json` actually declares that name — the entry may sit several levels
|
|
13
|
+
* deep (`<root>/dist/index.cjs`).
|
|
14
|
+
*
|
|
15
|
+
* Caveat worth knowing before reusing this: `resolve` runs under the CJS
|
|
16
|
+
* `require` condition. A package whose every export condition is `import`
|
|
17
|
+
* (ESM-only, as `@webpresso/agent-kit` itself is) has no entry resolvable this
|
|
18
|
+
* way at all. Such a package must export a condition-free `"./package.json"`
|
|
19
|
+
* target instead; this helper cannot rescue it.
|
|
20
|
+
*/
|
|
21
|
+
/** Minimal slice of `NodeRequire` needed here — keeps callers and tests honest. */
|
|
22
|
+
export interface ModuleResolver {
|
|
23
|
+
resolve(specifier: string): string;
|
|
24
|
+
}
|
|
25
|
+
export declare function readPackageManifest(packageRoot: string): Record<string, unknown> | null;
|
|
26
|
+
/**
|
|
27
|
+
* @param name Bare package name, scoped or unscoped (`rulesync`, `@scope/pkg`).
|
|
28
|
+
* @returns The package root directory, or `null` when it cannot be resolved.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolvePackageRoot(name: string, resolver: ModuleResolver, maxDepth?: number): string | null;
|
|
31
|
+
/** Convenience wrapper: the installed `version` of `name`, or `null`. */
|
|
32
|
+
export declare function resolvePackageVersion(name: string, resolver: ModuleResolver): string | null;
|