mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-18 08:29:48 +08:00
build: commit vendored shared skill packages
This commit is contained in:
@@ -81,27 +81,8 @@ export async function listTextFiles(root) {
|
||||
}
|
||||
|
||||
export async function collectReleaseFiles(root) {
|
||||
const baseFiles = await listTextFiles(root);
|
||||
const fileMap = new Map(baseFiles.map((file) => [file.relPath, file.bytes]));
|
||||
const vendoredPackages = new Set();
|
||||
|
||||
for (const file of baseFiles.filter((entry) => path.posix.basename(entry.relPath) === "package.json")) {
|
||||
const packageDirRel = normalizeDirRel(path.posix.dirname(file.relPath));
|
||||
const rewritten = await rewritePackageJsonForRelease({
|
||||
root,
|
||||
packageDirRel,
|
||||
bytes: file.bytes,
|
||||
fileMap,
|
||||
vendoredPackages,
|
||||
});
|
||||
if (rewritten) {
|
||||
fileMap.set(file.relPath, rewritten);
|
||||
}
|
||||
}
|
||||
|
||||
return [...fileMap.entries()]
|
||||
.map(([relPath, bytes]) => ({ relPath, bytes }))
|
||||
.sort((left, right) => left.relPath.localeCompare(right.relPath));
|
||||
await validateSelfContainedRelease(root);
|
||||
return listTextFiles(root);
|
||||
}
|
||||
|
||||
export async function materializeReleaseFiles(files, outDir) {
|
||||
@@ -113,88 +94,37 @@ export async function materializeReleaseFiles(files, outDir) {
|
||||
}
|
||||
}
|
||||
|
||||
async function rewritePackageJsonForRelease({ root, packageDirRel, bytes, fileMap, vendoredPackages }) {
|
||||
const packageJson = JSON.parse(bytes.toString("utf8"));
|
||||
let changed = false;
|
||||
export async function validateSelfContainedRelease(root) {
|
||||
const files = await listTextFiles(root);
|
||||
for (const file of files.filter((entry) => path.posix.basename(entry.relPath) === "package.json")) {
|
||||
const packageDir = path.resolve(root, fromPosixRel(path.posix.dirname(file.relPath)));
|
||||
const packageJson = JSON.parse(file.bytes.toString("utf8"));
|
||||
for (const section of PACKAGE_DEPENDENCY_SECTIONS) {
|
||||
const dependencies = packageJson[section];
|
||||
if (!dependencies || typeof dependencies !== "object") continue;
|
||||
|
||||
for (const section of PACKAGE_DEPENDENCY_SECTIONS) {
|
||||
const dependencies = packageJson[section];
|
||||
if (!dependencies || typeof dependencies !== "object") continue;
|
||||
|
||||
for (const [name, spec] of Object.entries(dependencies)) {
|
||||
if (typeof spec !== "string" || !spec.startsWith("file:")) continue;
|
||||
|
||||
const sourceDir = path.resolve(root, fromPosixRel(packageDirRel), spec.slice(5));
|
||||
const vendorDirRel = normalizeDirRel(path.posix.join(packageDirRel, "vendor", name));
|
||||
await vendorPackageTree({
|
||||
sourceDir,
|
||||
targetDirRel: vendorDirRel,
|
||||
fileMap,
|
||||
vendoredPackages,
|
||||
});
|
||||
dependencies[name] = toFileDependencySpec(packageDirRel, vendorDirRel);
|
||||
changed = true;
|
||||
for (const [name, spec] of Object.entries(dependencies)) {
|
||||
if (typeof spec !== "string" || !spec.startsWith("file:")) continue;
|
||||
const targetDir = path.resolve(packageDir, spec.slice(5));
|
||||
if (!isWithinRoot(root, targetDir)) {
|
||||
throw new Error(
|
||||
`Release artifact is not self-contained: ${file.relPath} depends on ${name} via ${spec}`,
|
||||
);
|
||||
}
|
||||
await fs.access(targetDir).catch(() => {
|
||||
throw new Error(`Missing local dependency for release: ${file.relPath} -> ${spec}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed) return null;
|
||||
return Buffer.from(`${JSON.stringify(packageJson, null, 2)}\n`);
|
||||
}
|
||||
|
||||
async function vendorPackageTree({ sourceDir, targetDirRel, fileMap, vendoredPackages }) {
|
||||
const dedupeKey = `${path.resolve(sourceDir)}=>${targetDirRel}`;
|
||||
if (vendoredPackages.has(dedupeKey)) return;
|
||||
vendoredPackages.add(dedupeKey);
|
||||
|
||||
const files = await listTextFiles(sourceDir);
|
||||
if (files.length === 0) {
|
||||
throw new Error(`Local package has no text files: ${sourceDir}`);
|
||||
}
|
||||
|
||||
const packageJson = files.find((file) => file.relPath === "package.json");
|
||||
if (!packageJson) {
|
||||
throw new Error(`Local package is missing package.json: ${sourceDir}`);
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
if (file.relPath === "package.json") continue;
|
||||
fileMap.set(joinReleasePath(targetDirRel, file.relPath), file.bytes);
|
||||
}
|
||||
|
||||
const rewrittenPackageJson = await rewritePackageJsonForRelease({
|
||||
root: sourceDir,
|
||||
packageDirRel: ".",
|
||||
bytes: packageJson.bytes,
|
||||
fileMap: {
|
||||
set(relPath, outputBytes) {
|
||||
fileMap.set(joinReleasePath(targetDirRel, relPath), outputBytes);
|
||||
},
|
||||
},
|
||||
vendoredPackages,
|
||||
});
|
||||
|
||||
fileMap.set(
|
||||
joinReleasePath(targetDirRel, "package.json"),
|
||||
rewrittenPackageJson ?? packageJson.bytes,
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeDirRel(relPath) {
|
||||
return relPath === "." ? "." : relPath.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
function fromPosixRel(relPath) {
|
||||
return relPath === "." ? "." : relPath.split("/").join(path.sep);
|
||||
}
|
||||
|
||||
function joinReleasePath(base, relPath) {
|
||||
const joined = normalizeDirRel(path.posix.join(base === "." ? "" : base, relPath));
|
||||
return joined.replace(/^\.\//, "");
|
||||
}
|
||||
|
||||
function toFileDependencySpec(fromDirRel, targetDirRel) {
|
||||
const fromDir = fromDirRel === "." ? "" : fromDirRel;
|
||||
const relative = path.posix.relative(fromDir || ".", targetDirRel);
|
||||
const normalized = relative === "" ? "." : relative;
|
||||
return `file:${normalized.startsWith(".") ? normalized : `./${normalized}`}`;
|
||||
function isWithinRoot(root, target) {
|
||||
const resolvedRoot = path.resolve(root);
|
||||
const relative = path.relative(resolvedRoot, path.resolve(target));
|
||||
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user