#!/usr/bin/env node import { readdir, readFile, realpath } from "node:fs/promises"; import { dirname, relative, resolve, sep } from "node:path"; const rootArgument = process.argv[2]; if (!rootArgument) throw new Error("usage: build_legacy_project_manifest.mjs "); const root = await realpath(rootArgument); const projectFiles = await findProjectFiles(root); const seenIds = new Set(); const manifest = []; for (const projectFile of projectFiles) { const metadata = JSON.parse(await readFile(projectFile, "utf8")); if (typeof metadata.id !== "string" || metadata.id.trim() === "") { throw new Error(`project metadata has no id: ${projectFile}`); } if (seenIds.has(metadata.id)) throw new Error(`duplicate project id: ${metadata.id}`); seenIds.add(metadata.id); if (typeof metadata.name !== "string" || metadata.name.trim() === "") { throw new Error(`project metadata has no name: ${projectFile}`); } const projectRoot = dirname(projectFile); const sourceRelativePath = relative(root, projectRoot).split(sep).join("/"); const physicalFolderPath = dirname(sourceRelativePath) === "." ? [] : dirname(sourceRelativePath).split("/"); if (metadata.folderPath !== undefined && ( !Array.isArray(metadata.folderPath) || metadata.folderPath.some((part) => typeof part !== "string") || JSON.stringify(metadata.folderPath) !== JSON.stringify(physicalFolderPath) )) { process.stderr.write(`[legacy-manifest] stale metadata folderPath; using physical path: ${projectFile}\n`); } manifest.push({ legacyId: metadata.id, name: metadata.name, folderPath: physicalFolderPath, sourceRelativePath, }); } manifest.sort((left, right) => left.sourceRelativePath.localeCompare(right.sourceRelativePath, "zh-CN")); process.stdout.write(`${JSON.stringify(manifest, null, 2)}\n`); async function findProjectFiles(directory) { const entries = await readdir(directory, { withFileTypes: true }); const projectMetadata = entries.find((entry) => entry.isFile() && entry.name === "project.json"); if (projectMetadata !== undefined) return [resolve(directory, projectMetadata.name)]; const found = []; for (const entry of entries) { if (entry.name === ".trash") continue; const path = resolve(directory, entry.name); if (entry.isSymbolicLink()) { process.stderr.write(`[legacy-manifest] skip untracked symbolic link: ${path}\n`); continue; } if (entry.isDirectory()) found.push(...await findProjectFiles(path)); } return found; }