chore: shrinkwrap coding agent release deps
This commit is contained in:
10
package.json
10
package.json
@@ -12,21 +12,23 @@
|
||||
"scripts": {
|
||||
"clean": "npm run clean --workspaces",
|
||||
"build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build",
|
||||
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && tsgo --noEmit && npm run check:browser-smoke",
|
||||
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && npm run check:shrinkwrap && tsgo --noEmit && npm run check:browser-smoke",
|
||||
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
|
||||
"check:pinned-deps": "node scripts/check-pinned-deps.mjs",
|
||||
"check:shrinkwrap": "node scripts/generate-coding-agent-shrinkwrap.mjs --check",
|
||||
"check:ts-imports": "node scripts/check-ts-relative-imports.mjs",
|
||||
"profile:tui": "node scripts/profile-coding-agent-node.mjs --mode tui",
|
||||
"profile:rpc": "node scripts/profile-coding-agent-node.mjs --mode rpc",
|
||||
"test": "npm run test --workspaces --if-present",
|
||||
"version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
|
||||
"version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
|
||||
"version:major": "npm version major -ws --no-git-tag-version && node scripts/sync-versions.js && shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install",
|
||||
"version:patch": "npm version patch -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only",
|
||||
"version:minor": "npm version minor -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only",
|
||||
"version:major": "npm version major -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only",
|
||||
"version:set": "npm version -ws",
|
||||
"prepublishOnly": "npm run clean && npm run build && npm run check",
|
||||
"publish": "npm run prepublishOnly && npm publish -ws --access public",
|
||||
"publish:dry": "npm run prepublishOnly && npm publish -ws --access public --dry-run",
|
||||
"release:local": "node scripts/local-release.mjs",
|
||||
"shrinkwrap:coding-agent": "node scripts/generate-coding-agent-shrinkwrap.mjs",
|
||||
"release:patch": "node scripts/release.mjs patch",
|
||||
"release:minor": "node scripts/release.mjs minor",
|
||||
"release:major": "node scripts/release.mjs major",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
- Changed source syntax to avoid TypeScript constructs that require JavaScript emit, keeping core sources compatible with Node.js strip-only TypeScript checks.
|
||||
- Removed web UI workspace references from the CLI package and dropped the package-level development watch script.
|
||||
- Published npm installs now include an `npm-shrinkwrap.json` to lock transitive dependencies for the CLI package.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
1803
packages/coding-agent/npm-shrinkwrap.json
generated
Normal file
1803
packages/coding-agent/npm-shrinkwrap.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,8 @@
|
||||
"dist",
|
||||
"docs",
|
||||
"examples",
|
||||
"CHANGELOG.md"
|
||||
"CHANGELOG.md",
|
||||
"npm-shrinkwrap.json"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "shx rm -rf dist",
|
||||
@@ -34,7 +35,8 @@
|
||||
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
|
||||
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
|
||||
"test": "vitest --run",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
"shrinkwrap": "node ../../scripts/generate-coding-agent-shrinkwrap.mjs",
|
||||
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
||||
},
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-agent-core": "^0.75.3",
|
||||
|
||||
@@ -65,7 +65,7 @@ if [[ "$SKIP_DEPS" == "false" ]]; then
|
||||
# We need all platform bindings for bun cross-compilation
|
||||
# Use --force to bypass platform checks (os/cpu restrictions in package.json)
|
||||
# Install all in one command to avoid npm removing packages from previous installs
|
||||
npm install --no-save --force --ignore-scripts \
|
||||
npm install --no-save --package-lock=false --force --ignore-scripts \
|
||||
@mariozechner/clipboard-darwin-arm64@0.3.2 \
|
||||
@mariozechner/clipboard-darwin-x64@0.3.2 \
|
||||
@mariozechner/clipboard-linux-x64-gnu@0.3.2 \
|
||||
|
||||
340
scripts/generate-coding-agent-shrinkwrap.mjs
Normal file
340
scripts/generate-coding-agent-shrinkwrap.mjs
Normal file
@@ -0,0 +1,340 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, join, posix, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = resolve(scriptDir, "..");
|
||||
const codingAgentDir = join(repoRoot, "packages/coding-agent");
|
||||
const rootLockfilePath = join(repoRoot, "package-lock.json");
|
||||
const shrinkwrapPath = join(codingAgentDir, "npm-shrinkwrap.json");
|
||||
const internalPackagePrefix = "@earendil-works/pi-";
|
||||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
const checkOnly = args.has("--check");
|
||||
|
||||
for (const arg of args) {
|
||||
if (arg !== "--check") {
|
||||
console.error(`Unknown argument: ${arg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function readJson(path) {
|
||||
return JSON.parse(readFileSync(path, "utf8"));
|
||||
}
|
||||
|
||||
function packageDependencies(entry) {
|
||||
return {
|
||||
...(entry.dependencies ?? {}),
|
||||
...(entry.optionalDependencies ?? {}),
|
||||
};
|
||||
}
|
||||
|
||||
function sortedObject(object) {
|
||||
return Object.fromEntries(Object.entries(object).sort(([a], [b]) => a.localeCompare(b)));
|
||||
}
|
||||
|
||||
function sortedPackageEntry(entry) {
|
||||
const fieldOrder = [
|
||||
"name",
|
||||
"version",
|
||||
"resolved",
|
||||
"integrity",
|
||||
"license",
|
||||
"dependencies",
|
||||
"optionalDependencies",
|
||||
"peerDependencies",
|
||||
"peerDependenciesMeta",
|
||||
"bin",
|
||||
"engines",
|
||||
"os",
|
||||
"cpu",
|
||||
"libc",
|
||||
"optional",
|
||||
"hasInstallScript",
|
||||
"deprecated",
|
||||
"funding",
|
||||
];
|
||||
const sorted = {};
|
||||
|
||||
for (const field of fieldOrder) {
|
||||
if (entry[field] !== undefined) {
|
||||
sorted[field] = entry[field];
|
||||
}
|
||||
}
|
||||
for (const [field, value] of Object.entries(entry).sort(([a], [b]) => a.localeCompare(b))) {
|
||||
if (sorted[field] === undefined) {
|
||||
sorted[field] = value;
|
||||
}
|
||||
}
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function copyLockEntry(entry) {
|
||||
const copied = { ...entry };
|
||||
delete copied.dev;
|
||||
delete copied.devOptional;
|
||||
delete copied.extraneous;
|
||||
delete copied.link;
|
||||
return sortedPackageEntry(copied);
|
||||
}
|
||||
|
||||
function copyPackageJsonEntry(packageJson, options) {
|
||||
const entry = options.includeName
|
||||
? { name: packageJson.name, version: packageJson.version }
|
||||
: { version: packageJson.version };
|
||||
|
||||
for (const field of [
|
||||
"license",
|
||||
"dependencies",
|
||||
"optionalDependencies",
|
||||
"peerDependencies",
|
||||
"peerDependenciesMeta",
|
||||
"bin",
|
||||
"engines",
|
||||
"os",
|
||||
"cpu",
|
||||
"libc",
|
||||
]) {
|
||||
if (packageJson[field] !== undefined) {
|
||||
entry[field] = packageJson[field];
|
||||
}
|
||||
}
|
||||
|
||||
return sortedPackageEntry(entry);
|
||||
}
|
||||
|
||||
function packageNameFromLockPath(lockPath) {
|
||||
const marker = "node_modules/";
|
||||
const index = lockPath.lastIndexOf(marker);
|
||||
if (index === -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parts = lockPath.slice(index + marker.length).split("/");
|
||||
if (parts[0]?.startsWith("@")) {
|
||||
return `${parts[0]}/${parts[1]}`;
|
||||
}
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
function registryTarballUrl(packageName, version) {
|
||||
const tarballName = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
|
||||
return `https://registry.npmjs.org/${packageName}/-/${tarballName}-${version}.tgz`;
|
||||
}
|
||||
|
||||
function getInternalWorkspaces(lockPackages) {
|
||||
const workspaces = new Map();
|
||||
|
||||
for (const [lockPath, entry] of Object.entries(lockPackages)) {
|
||||
if (!lockPath.startsWith("packages/") || lockPath.includes("/node_modules/") || !entry.name || !entry.version) {
|
||||
continue;
|
||||
}
|
||||
if (!entry.name.startsWith(internalPackagePrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
workspaces.set(entry.name, {
|
||||
lockPath,
|
||||
packageJson: readJson(join(repoRoot, lockPath, "package.json")),
|
||||
});
|
||||
}
|
||||
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
function resolveExternalDependency(lockPackages, packageName, fromLockPath) {
|
||||
const candidateDirs = [];
|
||||
let current = fromLockPath;
|
||||
|
||||
while (current) {
|
||||
candidateDirs.push(current);
|
||||
const parent = posix.dirname(current);
|
||||
if (parent === "." || parent === current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
candidateDirs.push("");
|
||||
|
||||
const tried = new Set();
|
||||
for (const directory of candidateDirs) {
|
||||
const candidate = directory ? `${directory}/node_modules/${packageName}` : `node_modules/${packageName}`;
|
||||
if (tried.has(candidate)) {
|
||||
continue;
|
||||
}
|
||||
tried.add(candidate);
|
||||
|
||||
const entry = lockPackages[candidate];
|
||||
if (entry && !entry.link) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
const suffix = `node_modules/${packageName}`;
|
||||
const matches = Object.entries(lockPackages)
|
||||
.filter(([lockPath, entry]) => !entry.link && (lockPath === suffix || lockPath.endsWith(`/${suffix}`)))
|
||||
.map(([lockPath]) => lockPath);
|
||||
|
||||
if (matches.length === 1) {
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Cannot resolve ${packageName} from ${fromLockPath || "root"}. ` +
|
||||
(matches.length > 1 ? `Matches: ${matches.join(", ")}` : "No matching lockfile entry found."),
|
||||
);
|
||||
}
|
||||
|
||||
function addInternalWorkspace(shrinkwrapPackages, addedPaths, queue, name, workspace) {
|
||||
const packageJson = workspace.packageJson;
|
||||
const outputPath = `node_modules/${name}`;
|
||||
const entry = copyPackageJsonEntry(packageJson, { includeName: false });
|
||||
entry.resolved = registryTarballUrl(name, packageJson.version);
|
||||
|
||||
shrinkwrapPackages[outputPath] = sortedPackageEntry(entry);
|
||||
addedPaths.add(outputPath);
|
||||
|
||||
for (const dependencyName of Object.keys(packageDependencies(packageJson))) {
|
||||
queue.push({ name: dependencyName, from: outputPath });
|
||||
}
|
||||
}
|
||||
|
||||
function addExternalPackage(lockPackages, shrinkwrapPackages, addedPaths, queue, name, from) {
|
||||
const lockPath = resolveExternalDependency(lockPackages, name, from);
|
||||
if (addedPaths.has(lockPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = lockPackages[lockPath];
|
||||
shrinkwrapPackages[lockPath] = copyLockEntry(entry);
|
||||
addedPaths.add(lockPath);
|
||||
|
||||
for (const dependencyName of Object.keys(packageDependencies(entry))) {
|
||||
queue.push({ name: dependencyName, from: lockPath });
|
||||
}
|
||||
}
|
||||
|
||||
function validateShrinkwrap(shrinkwrap, internalNames) {
|
||||
const errors = [];
|
||||
const includedPaths = new Set(Object.keys(shrinkwrap.packages));
|
||||
const includedPackageNames = new Set();
|
||||
|
||||
for (const [lockPath, entry] of Object.entries(shrinkwrap.packages)) {
|
||||
const packageName = packageNameFromLockPath(lockPath);
|
||||
if (packageName) {
|
||||
includedPackageNames.add(packageName);
|
||||
}
|
||||
if (entry.link) {
|
||||
errors.push(`${lockPath} is a link entry`);
|
||||
}
|
||||
if (typeof entry.resolved === "string" && /^(file:|link:|workspace:|\.\.?\/|\/)/.test(entry.resolved)) {
|
||||
errors.push(`${lockPath} has a local resolved value: ${entry.resolved}`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of internalNames) {
|
||||
if (!includedPackageNames.has(name)) {
|
||||
errors.push(`internal dependency ${name} is missing`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [lockPath, entry] of Object.entries(shrinkwrap.packages)) {
|
||||
for (const dependencyName of Object.keys(packageDependencies(entry))) {
|
||||
const dependencyIncluded = [...includedPaths].some(
|
||||
(candidate) => candidate === `node_modules/${dependencyName}` || candidate.endsWith(`/node_modules/${dependencyName}`),
|
||||
);
|
||||
if (!dependencyIncluded) {
|
||||
errors.push(`${lockPath || "root"} dependency ${dependencyName} is missing`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const platformPackageCount = Object.values(shrinkwrap.packages).filter((entry) => entry.os || entry.cpu || entry.libc).length;
|
||||
if (platformPackageCount === 0) {
|
||||
errors.push("no platform-specific optional dependency entries found");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Generated shrinkwrap failed validation:\n${errors.map((error) => ` - ${error}`).join("\n")}`);
|
||||
}
|
||||
}
|
||||
|
||||
function generateShrinkwrap() {
|
||||
const rootLock = readJson(rootLockfilePath);
|
||||
if (rootLock.lockfileVersion !== 3 || !rootLock.packages) {
|
||||
throw new Error("package-lock.json must be lockfileVersion 3 and contain a packages map");
|
||||
}
|
||||
|
||||
const lockPackages = rootLock.packages;
|
||||
const codingAgentPackage = readJson(join(codingAgentDir, "package.json"));
|
||||
const internalWorkspaces = getInternalWorkspaces(lockPackages);
|
||||
const shrinkwrapPackages = {
|
||||
"": copyPackageJsonEntry(codingAgentPackage, { includeName: true }),
|
||||
};
|
||||
const addedPaths = new Set([""]);
|
||||
const internalNames = new Set();
|
||||
const queue = Object.keys(packageDependencies(codingAgentPackage)).map((name) => ({ name, from: "" }));
|
||||
|
||||
while (queue.length > 0) {
|
||||
const item = queue.shift();
|
||||
if (!item) {
|
||||
break;
|
||||
}
|
||||
|
||||
const workspace = internalWorkspaces.get(item.name);
|
||||
if (workspace) {
|
||||
const outputPath = `node_modules/${item.name}`;
|
||||
internalNames.add(item.name);
|
||||
if (!addedPaths.has(outputPath)) {
|
||||
addInternalWorkspace(shrinkwrapPackages, addedPaths, queue, item.name, workspace);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
addExternalPackage(lockPackages, shrinkwrapPackages, addedPaths, queue, item.name, item.from);
|
||||
}
|
||||
|
||||
const shrinkwrap = {
|
||||
name: codingAgentPackage.name,
|
||||
version: codingAgentPackage.version,
|
||||
lockfileVersion: 3,
|
||||
requires: true,
|
||||
packages: sortedObject(shrinkwrapPackages),
|
||||
};
|
||||
|
||||
validateShrinkwrap(shrinkwrap, internalNames);
|
||||
return shrinkwrap;
|
||||
}
|
||||
|
||||
try {
|
||||
const shrinkwrap = generateShrinkwrap();
|
||||
const content = `${JSON.stringify(shrinkwrap, null, "\t")}\n`;
|
||||
|
||||
if (checkOnly) {
|
||||
if (!existsSync(shrinkwrapPath)) {
|
||||
console.error("packages/coding-agent/npm-shrinkwrap.json is missing.");
|
||||
console.error("Run: npm run shrinkwrap:coding-agent");
|
||||
process.exit(1);
|
||||
}
|
||||
const current = readFileSync(shrinkwrapPath, "utf8");
|
||||
if (current !== content) {
|
||||
console.error("packages/coding-agent/npm-shrinkwrap.json is out of date.");
|
||||
console.error("Run: npm run shrinkwrap:coding-agent");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("packages/coding-agent/npm-shrinkwrap.json is up to date.");
|
||||
} else {
|
||||
writeFileSync(shrinkwrapPath, content);
|
||||
const packageCount = Object.keys(shrinkwrap.packages).length - 1;
|
||||
const platformPackageCount = Object.values(shrinkwrap.packages).filter((entry) => entry.os || entry.cpu || entry.libc).length;
|
||||
console.log(
|
||||
`Wrote packages/coding-agent/npm-shrinkwrap.json (${packageCount} packages, ${platformPackageCount} platform-specific).`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -10,10 +10,11 @@
|
||||
* 1. Check for uncommitted changes
|
||||
* 2. Bump version via npm run version:xxx or set an explicit version
|
||||
* 3. Update CHANGELOG.md files: [Unreleased] -> [version] - date
|
||||
* 4. Commit and tag
|
||||
* 5. Publish to npm
|
||||
* 6. Add new [Unreleased] section to changelogs
|
||||
* 7. Commit
|
||||
* 4. Generate the coding-agent npm-shrinkwrap.json
|
||||
* 5. Commit and tag
|
||||
* 6. Publish to npm
|
||||
* 7. Add new [Unreleased] section to changelogs
|
||||
* 8. Commit
|
||||
*/
|
||||
|
||||
import { execSync } from "child_process";
|
||||
@@ -90,9 +91,7 @@ function bumpOrSetVersion(target) {
|
||||
}
|
||||
|
||||
console.log(`Setting explicit version (${target})...`);
|
||||
run(
|
||||
`npm version ${target} -ws --no-git-tag-version && node scripts/sync-versions.js && npx shx rm -rf node_modules packages/*/node_modules package-lock.json && npm install`,
|
||||
);
|
||||
run(`npm version ${target} -ws --no-git-tag-version && node scripts/sync-versions.js && npm install --package-lock-only`);
|
||||
return getVersion();
|
||||
}
|
||||
|
||||
@@ -164,30 +163,35 @@ console.log("Updating CHANGELOG.md files...");
|
||||
updateChangelogsForRelease(version);
|
||||
console.log();
|
||||
|
||||
// 4. Commit and tag
|
||||
// 4. Generate publish shrinkwrap
|
||||
console.log("Generating coding-agent shrinkwrap...");
|
||||
run("npm run shrinkwrap:coding-agent");
|
||||
console.log();
|
||||
|
||||
// 5. Commit and tag
|
||||
console.log("Committing and tagging...");
|
||||
stageChangedFiles();
|
||||
run(`git commit -m "Release v${version}"`);
|
||||
run(`git tag v${version}`);
|
||||
console.log();
|
||||
|
||||
// 5. Publish
|
||||
// 6. Publish
|
||||
console.log("Publishing to npm...");
|
||||
run("npm run publish");
|
||||
console.log();
|
||||
|
||||
// 6. Add new [Unreleased] sections
|
||||
// 7. Add new [Unreleased] sections
|
||||
console.log("Adding [Unreleased] sections for next cycle...");
|
||||
addUnreleasedSection();
|
||||
console.log();
|
||||
|
||||
// 7. Commit
|
||||
// 8. Commit
|
||||
console.log("Committing changelog updates...");
|
||||
stageChangedFiles();
|
||||
run(`git commit -m "Add [Unreleased] section for next cycle"`);
|
||||
console.log();
|
||||
|
||||
// 8. Push
|
||||
// 9. Push
|
||||
console.log("Pushing to remote...");
|
||||
run("git push origin main");
|
||||
run(`git push origin v${version}`);
|
||||
|
||||
Reference in New Issue
Block a user