vouchington-tooling 0.1.8 → 0.2.0
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/README.md +22 -5
- package/dist/cli/commands/link-skill.d.mts +5 -0
- package/dist/cli/commands/link-skill.mjs +6 -0
- package/dist/cli/index.mjs +3 -0
- package/dist/cli/parse.d.mts +5 -0
- package/dist/cli/parse.mjs +25 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +2 -0
- package/dist/gha-post-review/github.d.mts +2 -0
- package/dist/gha-post-review/github.mjs +47 -5
- package/dist/gha-post-review/post.mjs +6 -1
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -0
- package/dist/pnpm-install/index.d.mts +2 -1
- package/dist/pnpm-install/index.mjs +2 -1
- package/dist/pnpm-install/install-operations.d.mts +4 -0
- package/dist/pnpm-install/install-operations.mjs +38 -0
- package/dist/pnpm-install/metadata-legacy.d.mts +4 -0
- package/dist/pnpm-install/metadata-legacy.mjs +81 -0
- package/dist/pnpm-install/metadata.d.mts +16 -3
- package/dist/pnpm-install/metadata.mjs +103 -36
- package/dist/pnpm-install/pending-builds.d.mts +17 -0
- package/dist/pnpm-install/pending-builds.mjs +87 -0
- package/dist/pnpm-install/pnpm-install-fake-pnpm.test-helpers.d.mts +1 -0
- package/dist/pnpm-install/pnpm-install-fake-pnpm.test-helpers.mjs +52 -0
- package/dist/pnpm-install/pnpm-install-fixture.test-helpers.mjs +6 -40
- package/dist/pnpm-install/runner.mjs +72 -56
- package/dist/pnpm-install/transition.d.mts +23 -0
- package/dist/pnpm-install/transition.mjs +36 -0
- package/dist/skill-discovery/index.d.mts +12 -0
- package/dist/skill-discovery/index.mjs +64 -0
- package/dist/skill-discovery/manifest.d.mts +15 -0
- package/dist/skill-discovery/manifest.mjs +85 -0
- package/dist/skill-discovery/target-directory.d.mts +10 -0
- package/dist/skill-discovery/target-directory.mjs +126 -0
- package/package.json +6 -1
- package/scripts/build.mjs +16 -4
- package/skills/agent-workflow/SKILL.md +5 -0
- package/skills/agent-workflow/references/evidence-sweep.md +8 -0
- package/skills/agent-workflow/references/implementation-and-review.md +9 -0
- package/skills/agent-workflow/references/implementation.md +8 -0
- package/skills/agent-workflow/references/review.md +7 -0
- package/skills/backend-vitest-test-authoring/SKILL.md +15 -0
- package/skills/backend-vitest-test-authoring/references/integration-boundaries.md +10 -0
- package/skills/dotnet-test-authoring/SKILL.md +16 -0
- package/skills/manifest.json +158 -0
- package/skills/nextjs-vitest-test-authoring/SKILL.md +15 -0
- package/skills/nextjs-vitest-test-authoring/references/framework-boundaries.md +9 -0
- package/skills/planning/SKILL.md +3 -0
- package/skills/planning/references/impact-discovery.md +9 -0
- package/skills/playwright-authoring/SKILL.md +15 -0
- package/skills/playwright-authoring/references/browser-reliability.md +9 -0
- package/skills/postgres-node-performance-tuning/SKILL.md +15 -0
- package/skills/postgres-node-performance-tuning/references/performance-patterns.md +14 -0
- package/skills/postgres-partitioning-uuid-v7/SKILL.md +16 -0
- package/skills/postgres-partitioning-uuid-v7/references/partition-lifecycle.md +14 -0
- package/skills/storybook-authoring/SKILL.md +15 -0
- package/skills/storybook-authoring/references/component-coverage.md +9 -0
- package/skills/swift-test-authoring/SKILL.md +15 -0
- package/skills/swift-test-authoring/references/network-test-doubles.md +9 -0
- package/skills/test-authoring/SKILL.md +17 -0
- package/skills/test-authoring/references/core-practice.md +10 -0
- package/skills/vitest-test-authoring/SKILL.md +15 -0
- package/skills/vitest-test-authoring/references/mock-boundaries.md +9 -0
|
@@ -2,15 +2,28 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { listWorkspaces, reportGlibcVersionRuntime } from './support.mjs';
|
|
5
|
+
import {} from './transition.mjs';
|
|
6
|
+
const componentNames = [
|
|
7
|
+
'lockfile',
|
|
8
|
+
'npm-config',
|
|
9
|
+
'pnpm',
|
|
10
|
+
'pnpmfiles',
|
|
11
|
+
'runtime',
|
|
12
|
+
'workspace-config',
|
|
13
|
+
'workspace-manifests',
|
|
14
|
+
];
|
|
5
15
|
function persistentMetadataStampPath() {
|
|
6
16
|
return path.join(process.cwd(), 'node_modules', '.pnpm-install-metadata-health.json');
|
|
7
17
|
}
|
|
8
|
-
function fail(message) {
|
|
9
|
-
throw new Error(message);
|
|
10
|
-
}
|
|
11
18
|
function addFingerprintInput(hash, label, value) {
|
|
12
19
|
hash.update(`${label.length}:${label}${value.length}:${value}`);
|
|
13
20
|
}
|
|
21
|
+
function componentHash(inputs) {
|
|
22
|
+
const hash = createHash('sha256');
|
|
23
|
+
for (const [label, value] of inputs)
|
|
24
|
+
addFingerprintInput(hash, label, value);
|
|
25
|
+
return hash.digest('hex');
|
|
26
|
+
}
|
|
14
27
|
async function optionalFile(pathname) {
|
|
15
28
|
try {
|
|
16
29
|
return await readFile(pathname, 'utf8');
|
|
@@ -34,58 +47,112 @@ function runtimePlatformIdentity() {
|
|
|
34
47
|
platform: process.platform,
|
|
35
48
|
});
|
|
36
49
|
}
|
|
37
|
-
export async function
|
|
50
|
+
export async function persistentMetadataFingerprintV4(runCapture) {
|
|
38
51
|
const workspaces = (await listWorkspaces(runCapture)).toSorted((left, right) => left.path.localeCompare(right.path));
|
|
39
52
|
const pnpmVersion = await runCapture(['--version']);
|
|
40
53
|
if (pnpmVersion.code !== 0)
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'pnpm-
|
|
49
|
-
'.npmrc',
|
|
50
|
-
'.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
throw new Error(`pnpm --version failed: ${pnpmVersion.errorOutput?.trim() || pnpmVersion.output.trim() || 'unknown error'}`);
|
|
55
|
+
const files = new Map(await Promise.all(['pnpm-lock.yaml', 'pnpm-workspace.yaml', '.npmrc', '.pnpmfile.cjs', '.pnpmfile.mjs'].map(async (filename) => [filename, await optionalFile(path.join(process.cwd(), filename))])));
|
|
56
|
+
const manifests = await Promise.all(workspaces.map(async (workspace) => {
|
|
57
|
+
const filename = path.relative(process.cwd(), path.join(workspace.path, 'package.json'));
|
|
58
|
+
return [filename, await readFile(path.join(workspace.path, 'package.json'), 'utf8')];
|
|
59
|
+
}));
|
|
60
|
+
return {
|
|
61
|
+
lockfile: componentHash([['pnpm-lock.yaml', files.get('pnpm-lock.yaml')]]),
|
|
62
|
+
'npm-config': componentHash([['.npmrc', files.get('.npmrc')]]),
|
|
63
|
+
pnpm: componentHash([['version', pnpmVersion.output.trim()]]),
|
|
64
|
+
pnpmfiles: componentHash([
|
|
65
|
+
['.pnpmfile.cjs', files.get('.pnpmfile.cjs')],
|
|
66
|
+
['.pnpmfile.mjs', files.get('.pnpmfile.mjs')],
|
|
67
|
+
]),
|
|
68
|
+
runtime: componentHash([['identity', runtimePlatformIdentity()]]),
|
|
69
|
+
'workspace-config': componentHash([['pnpm-workspace.yaml', files.get('pnpm-workspace.yaml')]]),
|
|
70
|
+
'workspace-manifests': componentHash(manifests),
|
|
71
|
+
};
|
|
57
72
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const stamp = JSON.parse(await readFile(persistentMetadataStampPath(), 'utf8'));
|
|
61
|
-
return stamp.version === 3 && stamp.fingerprint === fingerprint;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
73
|
+
function validStamp(value) {
|
|
74
|
+
if (typeof value !== 'object' || value === null)
|
|
64
75
|
return false;
|
|
65
|
-
|
|
76
|
+
const stamp = value;
|
|
77
|
+
return (stamp.version === 4 &&
|
|
78
|
+
typeof stamp.lastInvocationInstallScripts === 'boolean' &&
|
|
79
|
+
typeof stamp.scriptsEnabledInstallSucceeded === 'boolean' &&
|
|
80
|
+
typeof stamp.provenance === 'object' &&
|
|
81
|
+
stamp.provenance !== null &&
|
|
82
|
+
componentNames.every((name) => typeof stamp.provenance?.[name] === 'string') &&
|
|
83
|
+
(stamp.pendingDependencyBuilds === undefined ||
|
|
84
|
+
(Array.isArray(stamp.pendingDependencyBuilds) &&
|
|
85
|
+
stamp.pendingDependencyBuilds.every((id) => typeof id === 'string'))));
|
|
66
86
|
}
|
|
67
|
-
|
|
87
|
+
async function readPersistentMetadataState() {
|
|
68
88
|
try {
|
|
69
|
-
await
|
|
70
|
-
return
|
|
89
|
+
const parsed = JSON.parse(await readFile(persistentMetadataStampPath(), 'utf8'));
|
|
90
|
+
return validStamp(parsed)
|
|
91
|
+
? { kind: 'stamp', stamp: parsed }
|
|
92
|
+
: { kind: 'unsafe' };
|
|
71
93
|
}
|
|
72
94
|
catch (error) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
throw error;
|
|
95
|
+
return error.code === 'ENOENT'
|
|
96
|
+
? { kind: 'missing' }
|
|
97
|
+
: { kind: 'unsafe' };
|
|
77
98
|
}
|
|
78
99
|
}
|
|
79
|
-
|
|
100
|
+
async function readPersistentMetadataStamp() {
|
|
101
|
+
const state = await readPersistentMetadataState();
|
|
102
|
+
return state.kind === 'stamp' ? state.stamp : undefined;
|
|
103
|
+
}
|
|
104
|
+
export async function persistentMetadataStatusV4(provenance) {
|
|
105
|
+
const state = await readPersistentMetadataState();
|
|
106
|
+
if (state.kind === 'missing')
|
|
107
|
+
return { kind: 'absent' };
|
|
108
|
+
if (state.kind === 'unsafe')
|
|
109
|
+
return { kind: 'unsafe' };
|
|
110
|
+
const { stamp } = state;
|
|
111
|
+
const changed = componentNames.filter((name) => stamp.provenance[name] !== provenance[name]);
|
|
112
|
+
return changed.length > 0
|
|
113
|
+
? { kind: 'changed', components: changed }
|
|
114
|
+
: {
|
|
115
|
+
kind: 'matching',
|
|
116
|
+
lastInvocationInstallScripts: stamp.lastInvocationInstallScripts,
|
|
117
|
+
pendingDependencyBuilds: stamp.pendingDependencyBuilds ?? [],
|
|
118
|
+
scriptsEnabledInstallSucceeded: stamp.scriptsEnabledInstallSucceeded,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export async function writePersistentMetadataStampV4(provenance, installScripts, resetScriptsEnabledCapability, pendingDependencyBuilds) {
|
|
122
|
+
const existing = await readPersistentMetadataStamp();
|
|
123
|
+
const existingMatches = existing && componentNames.every((name) => existing.provenance[name] === provenance[name]);
|
|
124
|
+
const stamp = {
|
|
125
|
+
lastInvocationInstallScripts: installScripts,
|
|
126
|
+
pendingDependencyBuilds: (pendingDependencyBuilds ??
|
|
127
|
+
(existingMatches ? existing.pendingDependencyBuilds : undefined) ??
|
|
128
|
+
[]).toSorted(),
|
|
129
|
+
provenance,
|
|
130
|
+
scriptsEnabledInstallSucceeded: installScripts ||
|
|
131
|
+
(!resetScriptsEnabledCapability &&
|
|
132
|
+
Boolean(existingMatches && existing.scriptsEnabledInstallSucceeded)),
|
|
133
|
+
version: 4,
|
|
134
|
+
};
|
|
80
135
|
const stampPath = persistentMetadataStampPath();
|
|
81
136
|
const directory = path.dirname(stampPath);
|
|
82
137
|
const temporary = `${stampPath}.${process.pid}.tmp`;
|
|
83
138
|
await mkdir(directory, { recursive: true });
|
|
84
139
|
try {
|
|
85
|
-
await writeFile(temporary, `${JSON.stringify(
|
|
140
|
+
await writeFile(temporary, `${JSON.stringify(stamp)}\n`);
|
|
86
141
|
await rename(temporary, stampPath);
|
|
87
142
|
}
|
|
88
143
|
finally {
|
|
89
144
|
await rm(temporary, { force: true });
|
|
90
145
|
}
|
|
91
146
|
}
|
|
147
|
+
export async function persistentDependencyTreeIsCold() {
|
|
148
|
+
try {
|
|
149
|
+
await stat(path.dirname(persistentMetadataStampPath()));
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (error.code === 'ENOENT')
|
|
154
|
+
return true;
|
|
155
|
+
/* v8 ignore next -- non-ENOENT stat failures are host-specific */
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type PendingBuilds = {
|
|
2
|
+
kind: 'known';
|
|
3
|
+
ids: Set<string>;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'unknown';
|
|
6
|
+
};
|
|
7
|
+
export type PendingBuildDelta = {
|
|
8
|
+
kind: 'unknown';
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'known';
|
|
11
|
+
dependencyIds: string[];
|
|
12
|
+
workspaceIds: string[];
|
|
13
|
+
};
|
|
14
|
+
export declare function pendingBuilds(): Promise<PendingBuilds>;
|
|
15
|
+
export declare function validDependencyBuildIds(ids: [string, ...string[]]): Promise<[string, ...string[]] | undefined>;
|
|
16
|
+
export declare function clearPendingDependencyBuilds(ids: string[]): Promise<boolean>;
|
|
17
|
+
export declare function pendingBuildDelta(before: PendingBuilds, after: PendingBuilds): Promise<PendingBuildDelta>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse, stringify } from 'yaml';
|
|
4
|
+
export async function pendingBuilds() {
|
|
5
|
+
try {
|
|
6
|
+
const value = parse(await readFile(path.join(process.cwd(), 'node_modules', '.modules.yaml'), 'utf8'));
|
|
7
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
8
|
+
return { kind: 'unknown' };
|
|
9
|
+
const pending = value.pendingBuilds ?? [];
|
|
10
|
+
if (!Array.isArray(pending) || !pending.every((id) => typeof id === 'string'))
|
|
11
|
+
return { kind: 'unknown' };
|
|
12
|
+
return { kind: 'known', ids: new Set(pending) };
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return { kind: 'unknown' };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async function lockfileDependencyIds() {
|
|
19
|
+
try {
|
|
20
|
+
const lockfile = parse(await readFile(path.join(process.cwd(), 'pnpm-lock.yaml'), 'utf8'));
|
|
21
|
+
if (typeof lockfile !== 'object' || lockfile === null || !('packages' in lockfile))
|
|
22
|
+
return undefined;
|
|
23
|
+
const packages = lockfile.packages;
|
|
24
|
+
if (typeof packages !== 'object' || packages === null || Array.isArray(packages))
|
|
25
|
+
return undefined;
|
|
26
|
+
return new Set(Object.keys(packages));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function validDependencyBuildIds(ids) {
|
|
33
|
+
const packages = await lockfileDependencyIds();
|
|
34
|
+
if (!packages || !ids.every((id) => packages.has(id)))
|
|
35
|
+
return undefined;
|
|
36
|
+
const [first, ...remaining] = ids.toSorted();
|
|
37
|
+
return [first, ...remaining];
|
|
38
|
+
}
|
|
39
|
+
export async function clearPendingDependencyBuilds(ids) {
|
|
40
|
+
const filename = path.join(process.cwd(), 'node_modules', '.modules.yaml');
|
|
41
|
+
try {
|
|
42
|
+
const value = parse(await readFile(filename, 'utf8'));
|
|
43
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
44
|
+
return false;
|
|
45
|
+
const record = value;
|
|
46
|
+
if (!Array.isArray(record.pendingBuilds) ||
|
|
47
|
+
!record.pendingBuilds.every((id) => typeof id === 'string'))
|
|
48
|
+
return false;
|
|
49
|
+
const ignored = record.ignoredBuilds ?? [];
|
|
50
|
+
if (!Array.isArray(ignored) ||
|
|
51
|
+
!ignored.every((id) => typeof id === 'string') ||
|
|
52
|
+
ids.some((id) => ignored.includes(id)))
|
|
53
|
+
return false;
|
|
54
|
+
record.pendingBuilds = record.pendingBuilds.filter((id) => !ids.includes(id));
|
|
55
|
+
const temporary = `${filename}.${process.pid}.tmp`;
|
|
56
|
+
try {
|
|
57
|
+
await writeFile(temporary, stringify(record));
|
|
58
|
+
await rename(temporary, filename);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
await rm(temporary, { force: true });
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function pendingBuildDelta(before, after) {
|
|
70
|
+
if (before.kind !== 'known' || after.kind !== 'known')
|
|
71
|
+
return { kind: 'unknown' };
|
|
72
|
+
const packages = await lockfileDependencyIds();
|
|
73
|
+
if (!packages)
|
|
74
|
+
return { kind: 'unknown' };
|
|
75
|
+
const dependencyIds = [];
|
|
76
|
+
const workspaceIds = [];
|
|
77
|
+
for (const id of after.ids) {
|
|
78
|
+
if (before.ids.has(id))
|
|
79
|
+
continue;
|
|
80
|
+
(packages.has(id) ? dependencyIds : workspaceIds).push(id);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
kind: 'known',
|
|
84
|
+
dependencyIds: dependencyIds.toSorted(),
|
|
85
|
+
workspaceIds: workspaceIds.toSorted(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fakePnpmScript(): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export function fakePnpmScript() {
|
|
2
|
+
return `#!/usr/bin/env bash
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
if [ "\${1:-}" = m ]; then
|
|
5
|
+
if [ -n "\${PNPM_LIST_WARNING:-}" ]; then printf '%s\\n' "$PNPM_LIST_WARNING" >&2; fi
|
|
6
|
+
printf '%s\\n' "$PNPM_WORKSPACES_JSON"
|
|
7
|
+
exit 0
|
|
8
|
+
fi
|
|
9
|
+
if [ "\${1:-}" = --version ]; then
|
|
10
|
+
printf '%s\\n' "\${PNPM_VERSION:-11.0.0}"
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
printf '%s\\n' "$*" >> "$PNPM_LOG"
|
|
14
|
+
calls=0
|
|
15
|
+
if [ -f "$PNPM_CALLS" ]; then calls="$(cat "$PNPM_CALLS")"; fi
|
|
16
|
+
calls=$((calls + 1))
|
|
17
|
+
printf '%s' "$calls" > "$PNPM_CALLS"
|
|
18
|
+
print_release_age_violation() {
|
|
19
|
+
printf '%s\\n' '✗ Lockfile failed supply-chain policy check (1 entries in 0.1s)'
|
|
20
|
+
printf '%s\\n' '[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] 1 lockfile entries failed verification:'
|
|
21
|
+
printf '%s\\n' ' undici@8.10.0 was published at 2026-08-03T15:06:33.000Z, within the minimumReleaseAge cutoff (2026-08-02T04:48:10.357Z)'
|
|
22
|
+
}
|
|
23
|
+
if [ "\${PNPM_FAIL_CALL:-0}" = "$calls" ]; then
|
|
24
|
+
if [ -n "\${PNPM_FAIL_RELEASE_AGE:-}" ]; then print_release_age_violation; fi
|
|
25
|
+
exit "\${PNPM_FAIL_CODE:-1}"
|
|
26
|
+
fi
|
|
27
|
+
if [ "\${PNPM_FAIL_RELEASE_AGE_CALL:-0}" = "$calls" ]; then
|
|
28
|
+
print_release_age_violation
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
if [ -n "\${PNPM_SLEEP_SECONDS:-}" ]; then sleep "$PNPM_SLEEP_SECONDS"; fi
|
|
32
|
+
if [ -n "\${PNPM_PENDING_BUILDS:-}" ]; then
|
|
33
|
+
mkdir -p "$PNPM_NODE_MODULES"
|
|
34
|
+
printf 'pendingBuilds: [%s]\\n' "$PNPM_PENDING_BUILDS" > "$PNPM_NODE_MODULES/.modules.yaml"
|
|
35
|
+
fi
|
|
36
|
+
case " $* " in
|
|
37
|
+
*' rebuild '*)
|
|
38
|
+
if [ "\${PNPM_REBUILD_INVALID_LEDGER:-0}" = 1 ]; then
|
|
39
|
+
printf 'pendingBuilds: invalid\n' > "$PNPM_NODE_MODULES/.modules.yaml"
|
|
40
|
+
fi
|
|
41
|
+
if [ "\${PNPM_REBUILD_BREAK_LINK:-0}" = 1 ]; then rm -f "$PNPM_DEPENDENCY_LINK"; fi
|
|
42
|
+
;;
|
|
43
|
+
*' --force '*)
|
|
44
|
+
if [ "\${PNPM_REPAIR_LINK:-0}" = 1 ]; then
|
|
45
|
+
mkdir -p "$(dirname "$PNPM_DEPENDENCY_LINK")"
|
|
46
|
+
rm -f "$PNPM_DEPENDENCY_LINK"
|
|
47
|
+
ln -s "$PNPM_DEPENDENCY" "$PNPM_DEPENDENCY_LINK"
|
|
48
|
+
fi
|
|
49
|
+
;;
|
|
50
|
+
esac
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
@@ -4,6 +4,7 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { runPnpmInstallCli } from '../cli/commands/pnpm-install.mjs';
|
|
7
|
+
import { fakePnpmScript } from './pnpm-install-fake-pnpm.test-helpers.mjs';
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
async function writeJson(path, value) {
|
|
9
10
|
await mkdir(dirname(path), { recursive: true });
|
|
@@ -20,6 +21,7 @@ export async function makeFixture() {
|
|
|
20
21
|
await Promise.all([
|
|
21
22
|
writeJson(join(root, 'package.json'), { name: 'fixture-root', private: true }),
|
|
22
23
|
writeFile(join(root, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\nminimumReleaseAge: 2880\n'),
|
|
24
|
+
writeFile(join(root, 'pnpm-lock.yaml'), 'lockfileVersion: 9\npackages: {}\n'),
|
|
23
25
|
writeJson(join(consumer, 'package.json'), {
|
|
24
26
|
name: '@fixture/consumer',
|
|
25
27
|
dependencies: { '@fixture/dependency': 'workspace:^' },
|
|
@@ -29,46 +31,7 @@ export async function makeFixture() {
|
|
|
29
31
|
mkdir(pnpmBin),
|
|
30
32
|
]);
|
|
31
33
|
await symlink(dependency, dependencyLink, 'dir');
|
|
32
|
-
await writeFile(join(pnpmBin, 'pnpm'),
|
|
33
|
-
set -euo pipefail
|
|
34
|
-
if [ "\${1:-}" = m ]; then
|
|
35
|
-
if [ -n "\${PNPM_LIST_WARNING:-}" ]; then printf '%s\\n' "$PNPM_LIST_WARNING" >&2; fi
|
|
36
|
-
printf '%s\\n' "$PNPM_WORKSPACES_JSON"
|
|
37
|
-
exit 0
|
|
38
|
-
fi
|
|
39
|
-
if [ "\${1:-}" = --version ]; then
|
|
40
|
-
printf '%s\\n' "\${PNPM_VERSION:-11.0.0}"
|
|
41
|
-
exit 0
|
|
42
|
-
fi
|
|
43
|
-
printf '%s\\n' "$*" >> "$PNPM_LOG"
|
|
44
|
-
calls=0
|
|
45
|
-
if [ -f "$PNPM_CALLS" ]; then calls="$(cat "$PNPM_CALLS")"; fi
|
|
46
|
-
calls=$((calls + 1))
|
|
47
|
-
printf '%s' "$calls" > "$PNPM_CALLS"
|
|
48
|
-
print_release_age_violation() {
|
|
49
|
-
printf '%s\\n' '✗ Lockfile failed supply-chain policy check (1 entries in 0.1s)'
|
|
50
|
-
printf '%s\\n' '[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] 1 lockfile entries failed verification:'
|
|
51
|
-
printf '%s\\n' ' undici@8.10.0 was published at 2026-08-03T15:06:33.000Z, within the minimumReleaseAge cutoff (2026-08-02T04:48:10.357Z)'
|
|
52
|
-
}
|
|
53
|
-
if [ "\${PNPM_FAIL_CALL:-0}" = "$calls" ]; then
|
|
54
|
-
if [ -n "\${PNPM_FAIL_RELEASE_AGE:-}" ]; then print_release_age_violation; fi
|
|
55
|
-
exit "\${PNPM_FAIL_CODE:-1}"
|
|
56
|
-
fi
|
|
57
|
-
if [ "\${PNPM_FAIL_RELEASE_AGE_CALL:-0}" = "$calls" ]; then
|
|
58
|
-
print_release_age_violation
|
|
59
|
-
exit 1
|
|
60
|
-
fi
|
|
61
|
-
if [ -n "\${PNPM_SLEEP_SECONDS:-}" ]; then sleep "$PNPM_SLEEP_SECONDS"; fi
|
|
62
|
-
case " $* " in
|
|
63
|
-
*' --force '*)
|
|
64
|
-
if [ "\${PNPM_REPAIR_LINK:-0}" = 1 ]; then
|
|
65
|
-
mkdir -p "$(dirname "$PNPM_DEPENDENCY_LINK")"
|
|
66
|
-
rm -f "$PNPM_DEPENDENCY_LINK"
|
|
67
|
-
ln -s "$PNPM_DEPENDENCY" "$PNPM_DEPENDENCY_LINK"
|
|
68
|
-
fi
|
|
69
|
-
;;
|
|
70
|
-
esac
|
|
71
|
-
`);
|
|
34
|
+
await writeFile(join(pnpmBin, 'pnpm'), fakePnpmScript());
|
|
72
35
|
await execFileAsync('chmod', ['+x', join(pnpmBin, 'pnpm')]);
|
|
73
36
|
const workspaces = [
|
|
74
37
|
{ name: 'fixture-root', path: root },
|
|
@@ -83,7 +46,10 @@ esac
|
|
|
83
46
|
PNPM_DEPENDENCY: dependency,
|
|
84
47
|
PNPM_DEPENDENCY_LINK: dependencyLink,
|
|
85
48
|
PNPM_LOG: pnpmLog,
|
|
49
|
+
PNPM_NODE_MODULES: join(root, 'node_modules'),
|
|
50
|
+
PNPM_PENDING_BUILDS: '',
|
|
86
51
|
PNPM_REPAIR_LINK: '0',
|
|
52
|
+
PNPM_REBUILD_BREAK_LINK: '0',
|
|
87
53
|
PNPM_WORKSPACES_JSON: JSON.stringify(workspaces),
|
|
88
54
|
};
|
|
89
55
|
return {
|
|
@@ -1,83 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { persistentDependencyTreeIsCold, persistentMetadataFingerprint, persistentMetadataMatches, writePersistentMetadataStamp, } from './metadata.mjs';
|
|
3
|
-
import { nativeBinariesMatchRuntime } from './native-health.mjs';
|
|
1
|
+
import { persistentDependencyTreeIsCold, persistentMetadataFingerprintV4, persistentMetadataStatusV4, writePersistentMetadataStampV4, } from './metadata.mjs';
|
|
4
2
|
import { runPnpm } from './exec.mjs';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { nativeBinariesMatchRuntime } from './native-health.mjs';
|
|
4
|
+
import { clearPendingDependencyBuilds, pendingBuildDelta, pendingBuilds, validDependencyBuildIds, } from './pending-builds.mjs';
|
|
5
|
+
import { install, reconcileOrFail, withScriptPolicy } from './install-operations.mjs';
|
|
7
6
|
import { baseInstallArgs, findWorkspaceLinkMismatches, logWorkspaceLinkMismatches, } from './support.mjs';
|
|
7
|
+
import { persistentInstallTransition, persistentProvenanceDiagnostic } from './transition.mjs';
|
|
8
8
|
// oxfmt-ignore
|
|
9
9
|
const fail = (message) => { throw new Error(message); };
|
|
10
|
-
async function install(args, options, label) {
|
|
11
|
-
for (let attempt = 1; attempt <= options.maxAttempts; attempt += 1) {
|
|
12
|
-
const attemptResult = await runPnpm(args, options);
|
|
13
|
-
if (attemptResult.code === 0)
|
|
14
|
-
return;
|
|
15
|
-
if (attemptResult.code === INSTALL_TERMINATION_FAILED)
|
|
16
|
-
fail(`${label} could not terminate safely`);
|
|
17
|
-
const combinedOutput = `${attemptResult.output}\n${attemptResult.errorOutput ?? ''}`;
|
|
18
|
-
if (isReleaseAgeViolation(combinedOutput))
|
|
19
|
-
fail(formatReleaseAgeFailure(label, combinedOutput));
|
|
20
|
-
if (attempt < options.maxAttempts) {
|
|
21
|
-
console.warn(`${label} failed (attempt ${attempt}/${options.maxAttempts}); retrying`);
|
|
22
|
-
await scheduler.wait(5000);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
fail(`${label} failed after ${options.maxAttempts} attempt${options.maxAttempts === 1 ? '' : 's'}`);
|
|
26
|
-
}
|
|
27
|
-
function withScriptPolicy(args, installScripts) {
|
|
28
|
-
return installScripts ? args : [...args, '--ignore-scripts'];
|
|
29
|
-
}
|
|
30
|
-
async function reconcileAndFindMismatches(options, runCapture) {
|
|
31
|
-
const forced = ['install', '--frozen-lockfile', '--force', ...baseInstallArgs.slice(2)];
|
|
32
|
-
// oxfmt-ignore
|
|
33
|
-
await install([...forced, '--ignore-scripts', '--ignore-pnpmfile'], options, 'script-free reconciliation');
|
|
34
|
-
// oxfmt-ignore
|
|
35
|
-
await install(withScriptPolicy(forced, options.installScripts), options, 'strict persistent reconciliation');
|
|
36
|
-
return findWorkspaceLinkMismatches(runCapture);
|
|
37
|
-
}
|
|
38
|
-
async function reconcileOrFail(options, runCapture) {
|
|
39
|
-
const remaining = await reconcileAndFindMismatches(options, runCapture);
|
|
40
|
-
if (remaining.length > 0) {
|
|
41
|
-
logWorkspaceLinkMismatches(remaining);
|
|
42
|
-
fail('persistent reconciliation completed with invalid workspace links');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
10
|
async function persistent(options) {
|
|
46
11
|
if (options.ephemeralWorkspaces.trim())
|
|
47
12
|
fail('ephemeral-workspaces is only valid for ephemeral runners');
|
|
48
13
|
const runCapture = (args) => runPnpm(args, options, true);
|
|
49
|
-
const fingerprint = await
|
|
50
|
-
const
|
|
14
|
+
const fingerprint = await persistentMetadataFingerprintV4(runCapture);
|
|
15
|
+
const provenance = await persistentMetadataStatusV4(fingerprint);
|
|
51
16
|
const nativesMatch = await nativeBinariesMatchRuntime();
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
17
|
+
const provisionalTransition = persistentInstallTransition(provenance, options.installScripts);
|
|
18
|
+
let transition = nativesMatch
|
|
19
|
+
? provisionalTransition
|
|
20
|
+
: { action: 'reconcile', reason: 'native-health-mismatch' };
|
|
21
|
+
if (transition.action === 'upgrade-dependencies') {
|
|
22
|
+
const ids = await validDependencyBuildIds(transition.pendingDependencyBuilds);
|
|
23
|
+
if (!ids)
|
|
24
|
+
transition = { action: 'upgrade-scripts', reason: 'invalid-pending-dependency-builds' };
|
|
25
|
+
else
|
|
26
|
+
transition = { ...transition, pendingDependencyBuilds: ids };
|
|
27
|
+
}
|
|
28
|
+
const provenanceOk = provenance.kind === 'matching' && transition.action !== 'reconcile' && nativesMatch;
|
|
55
29
|
const cold = !provenanceOk && (await persistentDependencyTreeIsCold());
|
|
56
30
|
if (!provenanceOk && !cold) {
|
|
57
|
-
|
|
31
|
+
const finalTransition = provenance.kind === 'absent'
|
|
32
|
+
? { action: 'reconcile', reason: 'missing-stamp-populated-tree' }
|
|
33
|
+
: transition;
|
|
34
|
+
console.warn(persistentProvenanceDiagnostic(provenance, options.installScripts, nativesMatch, finalTransition));
|
|
35
|
+
console.warn(provenance.kind === 'matching' && !nativesMatch
|
|
58
36
|
? 'persistent optional native binaries do not match this runtime; reconciling'
|
|
59
37
|
: 'persistent dependency metadata provenance is missing or changed; reconciling');
|
|
60
38
|
await reconcileOrFail(options, runCapture);
|
|
61
|
-
await
|
|
39
|
+
await writePersistentMetadataStampV4(fingerprint, options.installScripts, true, []);
|
|
62
40
|
return 'persistent metadata reconciled';
|
|
63
41
|
}
|
|
64
|
-
if (
|
|
42
|
+
if (provenance.kind === 'absent')
|
|
65
43
|
console.warn('persistent dependency tree is absent; installing cold');
|
|
66
|
-
|
|
44
|
+
const pendingBefore = transition.action === 'ordinary' && !options.installScripts ? await pendingBuilds() : undefined;
|
|
45
|
+
await install(withScriptPolicy([...baseInstallArgs], transition.action.startsWith('upgrade-') ? false : options.installScripts), options, 'ordinary persistent install');
|
|
67
46
|
const stale = await findWorkspaceLinkMismatches(runCapture);
|
|
68
47
|
if (stale.length === 0) {
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
48
|
+
if (transition.action.startsWith('upgrade-')) {
|
|
49
|
+
const pendingDependencyBuilds = transition.action === 'upgrade-dependencies'
|
|
50
|
+
? transition.pendingDependencyBuilds
|
|
51
|
+
: undefined;
|
|
52
|
+
const rebuild = pendingDependencyBuilds === undefined
|
|
53
|
+
? ['rebuild', '--pending', '--recursive']
|
|
54
|
+
: ['rebuild', '--recursive', '--', ...pendingDependencyBuilds];
|
|
55
|
+
await install(rebuild, options, 'pending scripts rebuild');
|
|
56
|
+
const rebuiltStale = await findWorkspaceLinkMismatches(runCapture);
|
|
57
|
+
if (rebuiltStale.length > 0) {
|
|
58
|
+
logWorkspaceLinkMismatches(rebuiltStale);
|
|
59
|
+
await reconcileOrFail(options, runCapture);
|
|
60
|
+
console.warn(persistentProvenanceDiagnostic(provenance, options.installScripts, nativesMatch, {
|
|
61
|
+
action: 'reconcile',
|
|
62
|
+
reason: 'workspace-links-stale-after-rebuild',
|
|
63
|
+
}));
|
|
64
|
+
await writePersistentMetadataStampV4(fingerprint, options.installScripts, true, []);
|
|
65
|
+
return 'persistent reconciled';
|
|
66
|
+
}
|
|
67
|
+
if (pendingDependencyBuilds !== undefined &&
|
|
68
|
+
!(await clearPendingDependencyBuilds(pendingDependencyBuilds)))
|
|
69
|
+
fail('dependency rebuild completed but pending build ledger could not be updated safely');
|
|
70
|
+
}
|
|
71
|
+
console.warn(persistentProvenanceDiagnostic(provenance, options.installScripts, nativesMatch, transition));
|
|
72
|
+
const pendingAfter = pendingBefore ? await pendingBuilds() : undefined;
|
|
73
|
+
const delta = pendingBefore && pendingAfter
|
|
74
|
+
? await pendingBuildDelta(pendingBefore, pendingAfter)
|
|
75
|
+
: undefined;
|
|
76
|
+
const unsafePending = delta?.kind === 'unknown' || Boolean(delta?.workspaceIds.length);
|
|
77
|
+
const pendingDependencyBuilds = transition.action.startsWith('upgrade-') || unsafePending
|
|
78
|
+
? []
|
|
79
|
+
: [
|
|
80
|
+
...new Set([
|
|
81
|
+
...(provenance.kind === 'matching' ? provenance.pendingDependencyBuilds : []),
|
|
82
|
+
...(delta?.kind === 'known' ? delta.dependencyIds : []),
|
|
83
|
+
]),
|
|
84
|
+
].toSorted();
|
|
85
|
+
await writePersistentMetadataStampV4(fingerprint, options.installScripts, unsafePending, pendingDependencyBuilds);
|
|
86
|
+
return provenance.kind === 'absent' ? 'persistent cold' : 'persistent ordinary';
|
|
72
87
|
}
|
|
73
88
|
logWorkspaceLinkMismatches(stale);
|
|
89
|
+
console.warn(persistentProvenanceDiagnostic(provenance, options.installScripts, nativesMatch, {
|
|
90
|
+
action: 'reconcile',
|
|
91
|
+
reason: 'workspace-links-stale',
|
|
92
|
+
}));
|
|
74
93
|
await reconcileOrFail(options, runCapture);
|
|
75
|
-
await
|
|
94
|
+
await writePersistentMetadataStampV4(fingerprint, options.installScripts, true, []);
|
|
76
95
|
return 'persistent reconciled';
|
|
77
96
|
}
|
|
78
|
-
// A path selector's `...` (dependency closure) suffix is silently ignored by pnpm unless the
|
|
79
|
-
// path is brace-wrapped, e.g. `{./web}...` — `./web...` installs only `web` itself. Reject the
|
|
80
|
-
// unbraced form outright rather than let it resolve to a smaller-than-intended scope.
|
|
81
97
|
function isUnbracedPathClosureSelector(selector) {
|
|
82
98
|
return /^\.{0,2}\//.test(selector) && selector.endsWith('...') && !selector.startsWith('{');
|
|
83
99
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ProvenanceStatus = {
|
|
2
|
+
kind: 'absent';
|
|
3
|
+
} | {
|
|
4
|
+
kind: 'changed';
|
|
5
|
+
components: string[];
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'matching';
|
|
8
|
+
lastInvocationInstallScripts: boolean;
|
|
9
|
+
pendingDependencyBuilds: string[];
|
|
10
|
+
scriptsEnabledInstallSucceeded: boolean;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'unsafe';
|
|
13
|
+
};
|
|
14
|
+
export type PersistentInstallTransition = {
|
|
15
|
+
action: 'ordinary' | 'reconcile' | 'upgrade-scripts';
|
|
16
|
+
reason: string;
|
|
17
|
+
} | {
|
|
18
|
+
action: 'upgrade-dependencies';
|
|
19
|
+
pendingDependencyBuilds: [string, ...string[]];
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function persistentInstallTransition(provenance: ProvenanceStatus, installScripts: boolean): PersistentInstallTransition;
|
|
23
|
+
export declare function persistentProvenanceDiagnostic(provenance: ProvenanceStatus, installScripts: boolean, nativeBinariesMatchRuntime: boolean, transition: PersistentInstallTransition): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The matching rows are deliberately explicit: a script-disabled invocation can never erase
|
|
2
|
+
// evidence that this structural tree has already completed a scripts-enabled install.
|
|
3
|
+
export function persistentInstallTransition(provenance, installScripts) {
|
|
4
|
+
if (provenance.kind === 'matching') {
|
|
5
|
+
if (installScripts && !provenance.scriptsEnabledInstallSucceeded)
|
|
6
|
+
return { action: 'upgrade-scripts', reason: 'pending-scripts-rebuild' };
|
|
7
|
+
const [firstPendingDependencyBuild, ...remainingPendingDependencyBuilds] = provenance.pendingDependencyBuilds;
|
|
8
|
+
if (installScripts && firstPendingDependencyBuild !== undefined)
|
|
9
|
+
return {
|
|
10
|
+
action: 'upgrade-dependencies',
|
|
11
|
+
pendingDependencyBuilds: [firstPendingDependencyBuild, ...remainingPendingDependencyBuilds],
|
|
12
|
+
reason: 'pending-dependency-rebuild',
|
|
13
|
+
};
|
|
14
|
+
return { action: 'ordinary', reason: 'matching-structural-provenance' };
|
|
15
|
+
}
|
|
16
|
+
if (provenance.kind === 'absent')
|
|
17
|
+
return { action: 'ordinary', reason: 'missing-stamp' };
|
|
18
|
+
return {
|
|
19
|
+
action: 'reconcile',
|
|
20
|
+
reason: provenance.kind === 'changed' ? 'structural-provenance-changed' : 'unsafe-stamp',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function persistentProvenanceDiagnostic(provenance, installScripts, nativeBinariesMatchRuntime, transition) {
|
|
24
|
+
return JSON.stringify({
|
|
25
|
+
action: transition.action,
|
|
26
|
+
changedComponents: provenance.kind === 'changed' ? provenance.components : [],
|
|
27
|
+
event: 'pnpm-install-persistent-provenance',
|
|
28
|
+
installScripts,
|
|
29
|
+
scriptsEnabledInstallSucceeded: provenance.kind === 'matching' ? provenance.scriptsEnabledInstallSucceeded : false,
|
|
30
|
+
lastInvocationInstallScripts: provenance.kind === 'matching' ? provenance.lastInvocationInstallScripts : null,
|
|
31
|
+
pendingDependencyBuildCount: provenance.kind === 'matching' ? provenance.pendingDependencyBuilds.length : 0,
|
|
32
|
+
nativeBinariesMatchRuntime,
|
|
33
|
+
reason: transition.reason,
|
|
34
|
+
state: provenance.kind,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { readSkillManifest, type SkillManifest, type SkillManifestEntry } from './manifest.mts';
|
|
2
|
+
export type LinkSkillOptions = {
|
|
3
|
+
name: string;
|
|
4
|
+
sourceRoot: string;
|
|
5
|
+
targetRoot: string;
|
|
6
|
+
};
|
|
7
|
+
export type LinkSkillResult = {
|
|
8
|
+
created: boolean;
|
|
9
|
+
path: string;
|
|
10
|
+
source: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function linkSkill(options: LinkSkillOptions): Promise<LinkSkillResult>;
|