UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

315 lines (313 loc) 12.4 kB
const require_runtime = require('../_virtual/_rolldown/runtime.cjs'); const require_merge = require('../merge.cjs'); const require_config = require('../config.cjs'); const require_infra = require('./infra.cjs'); const require_snapshot = require('./snapshot.cjs'); const require_cli_init = require('./init.cjs'); let node_fs = require("node:fs"); let node_path = require("node:path"); let node_crypto = require("node:crypto"); let glob = require("glob"); //#region src/cli/sync.ts const FRAMEWORK_OWNED_SYNC_FILES = new Set([ ".env.example", ".gitignore", "AGENTS.md", "biome.json", "bos.config.json", "bunfig.toml", "CONTRIBUTING.md", "package.json", ".changeset/config.json", ".changeset/README.md", ".github/renovate.json", ".github/workflows/ci.yml", ".github/workflows/release-sync.yml", ".opencode/skills/everything-dev/SKILL.md", "ui/package.json", "ui/postcss.config.mjs", "ui/rsbuild.config.ts", "ui/tsconfig.json", "ui/src/app.ts", "ui/src/globals.d.ts", "ui/src/hydrate.tsx", "ui/src/lib/api.ts", "ui/src/lib/auth.ts", "ui/src/router.server.tsx", "ui/src/router.tsx", "ui/src/routes/__root.tsx", "api/package.json", "api/plugin.dev.ts", "api/rspack.config.js", "api/tsconfig.contract.json", "api/tsconfig.json", "api/src/lib/auth.ts" ]); function computeLocalHash(projectDir, filePath) { const fullPath = (0, node_path.join)(projectDir, filePath); if (!(0, node_fs.existsSync)(fullPath)) return null; try { const content = (0, node_fs.readFileSync)(fullPath); return (0, node_crypto.createHash)("sha256").update(content).digest("hex").substring(0, 16); } catch { return null; } } function backupFiles(projectDir, filePaths) { const filesToBackup = filePaths.filter((f) => (0, node_fs.existsSync)((0, node_path.join)(projectDir, f))); if (filesToBackup.length === 0) return null; const backupDir = (0, node_path.join)(projectDir, ".bos", "sync-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")); for (const filePath of filesToBackup) { const src = (0, node_path.join)(projectDir, filePath); const dest = (0, node_path.join)(backupDir, filePath); (0, node_fs.mkdirSync)((0, node_path.dirname)(dest), { recursive: true }); (0, node_fs.copyFileSync)(src, dest); } return backupDir; } function mergeStringMaps(local, template) { if (!local && !template) return void 0; const merged = { ...local ?? {} }; for (const [name, value] of Object.entries(template ?? {})) merged[name] = value; return Object.keys(merged).length > 0 ? merged : void 0; } function mergeWorkspacePackages(local, template) { const localPackages = Array.isArray(local) ? local : []; const templatePackages = Array.isArray(template) ? template : []; if (localPackages.length === 0 && templatePackages.length === 0) return void 0; const ordered = /* @__PURE__ */ new Set(); for (const entry of templatePackages) if (typeof entry === "string" && entry.length > 0) ordered.add(entry); for (const entry of localPackages) if (typeof entry === "string" && entry.length > 0) ordered.add(entry); if ([...ordered].some((e) => e.startsWith("plugins/") && e !== "plugins/*")) { for (const entry of [...ordered]) if (entry.startsWith("plugins/") && entry !== "plugins/*") ordered.delete(entry); ordered.add("plugins/*"); } return ordered.size > 0 ? [...ordered] : void 0; } function mergePackageJson(filePath, local, template) { const merged = { ...local, ...template }; if (filePath === "package.json") { for (const key of [ "name", "private", "version" ]) if (key in local) merged[key] = local[key]; } else if ("version" in local) merged.version = local.version; for (const depField of [ "dependencies", "devDependencies", "peerDependencies", "overrides" ]) { const localDeps = local[depField]; const templateDeps = template[depField]; const mergedDeps = mergeStringMaps(localDeps, templateDeps); if (mergedDeps) merged[depField] = mergedDeps; else delete merged[depField]; } if (local.scripts && typeof local.scripts === "object" || template.scripts && typeof template.scripts === "object") { const mergedScripts = mergeStringMaps(local.scripts, template.scripts); if (mergedScripts) merged.scripts = mergedScripts; else delete merged.scripts; } if (local.workspaces && typeof local.workspaces === "object" || template.workspaces && typeof template.workspaces === "object") { const localWorkspaces = local.workspaces ?? {}; const templateWorkspaces = template.workspaces ?? {}; const mergedWorkspaces = { ...localWorkspaces, ...templateWorkspaces }; const mergedPackages = mergeWorkspacePackages(localWorkspaces.packages, templateWorkspaces.packages); if (mergedPackages) mergedWorkspaces.packages = mergedPackages; else delete mergedWorkspaces.packages; const mergedCatalog = mergeStringMaps(localWorkspaces.catalog, templateWorkspaces.catalog); if (mergedCatalog) mergedWorkspaces.catalog = mergedCatalog; else delete mergedWorkspaces.catalog; if (Object.keys(mergedWorkspaces).length > 0) merged.workspaces = mergedWorkspaces; else delete merged.workspaces; } return merged; } function toDestPath(filePath) { return require_cli_init.sourcePathToDestinationPath(filePath); } function toSourcePath(sourceDir, destPath) { if ((0, node_fs.existsSync)((0, node_path.join)(sourceDir, destPath))) return destPath; if (destPath.startsWith(".github/")) { const templatePath = destPath.replace(/^\.github\//, ".github/templates/"); if ((0, node_fs.existsSync)((0, node_path.join)(sourceDir, templatePath))) return templatePath; } return null; } function writeSyncedFile(sourceDir, projectDir, filePath) { const src = (0, node_path.join)(sourceDir, filePath); const destPath = filePath.startsWith(".github/templates/") ? filePath.replace(/^\.github\/templates\//, ".github/") : filePath; const dest = (0, node_path.join)(projectDir, destPath); (0, node_fs.mkdirSync)((0, node_path.dirname)(dest), { recursive: true }); if (filePath.endsWith("bos.config.json")) { const localContent = (0, node_fs.existsSync)(dest) ? (0, node_fs.readFileSync)(dest, "utf-8") : null; const templateContent = (0, node_fs.readFileSync)(src, "utf-8"); if (localContent) { const merged = require_merge.mergeBosConfigWithTemplate(JSON.parse(localContent), JSON.parse(templateContent)); (0, node_fs.writeFileSync)(dest, `${JSON.stringify(merged, null, 2)}\n`); return; } } if (filePath.endsWith("package.json")) { const localContent = (0, node_fs.existsSync)(dest) ? (0, node_fs.readFileSync)(dest, "utf-8") : null; const templateContent = (0, node_fs.readFileSync)(src, "utf-8"); if (localContent) { const merged = mergePackageJson(destPath, JSON.parse(localContent), JSON.parse(templateContent)); (0, node_fs.writeFileSync)(dest, `${JSON.stringify(merged, null, 2)}\n`); return; } } (0, node_fs.writeFileSync)(dest, (0, node_fs.readFileSync)(src)); } async function getSelectedChildPlugins(projectDir, localConfig) { if (!localConfig.plugins || typeof localConfig.plugins !== "object") return []; const pluginDirs = new Set((await (0, glob.glob)("plugins/*/package.json", { cwd: projectDir, nodir: true, dot: false, absolute: false })).map((file) => file.match(/^plugins\/([^/]+)\/package\.json$/)?.[1]).filter((key) => Boolean(key))); const selected = []; for (const [pluginKey, rawEntry] of Object.entries(localConfig.plugins)) { if (typeof rawEntry === "string") { if (!rawEntry.startsWith("local:")) { selected.push(pluginKey); continue; } if ((0, node_fs.existsSync)((0, node_path.join)(projectDir, rawEntry.slice(6).trim())) || pluginDirs.has(pluginKey)) selected.push(pluginKey); continue; } if (!rawEntry || typeof rawEntry !== "object") { selected.push(pluginKey); continue; } const entry = rawEntry; const development = typeof entry.development === "string" ? entry.development : void 0; if (!development?.startsWith("local:")) { selected.push(pluginKey); continue; } if ((0, node_fs.existsSync)((0, node_path.join)(projectDir, development.slice(6).trim())) || pluginDirs.has(pluginKey)) selected.push(pluginKey); } return selected; } async function syncTemplate(projectDir, options) { const localConfig = JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(projectDir, "bos.config.json"), "utf-8")); let extendsRef; if (typeof localConfig.extends === "string") extendsRef = localConfig.extends; else if (require_merge.isPlainObject(localConfig.extends)) extendsRef = require_merge.resolveExtendsRef(localConfig.extends, "production"); if (!extendsRef?.startsWith("bos://")) return { status: "error", updated: [], skipped: [], added: [], error: "No extends field found in bos.config.json — cannot determine parent" }; const extendsMatch = extendsRef.match(/^bos:\/\/([^/]+)\/(.+)$/); if (!extendsMatch) return { status: "error", updated: [], skipped: [], added: [], error: `Invalid extends reference: ${extendsRef}` }; const extendsAccount = extendsMatch[1]; const extendsGateway = extendsMatch[2]; const { sourceDir, cleanup } = await require_cli_init.resolveSourceDir({ extendsAccount, extendsGateway }); try { const childPlugins = await getSelectedChildPlugins(projectDir, localConfig); const withUi = (0, node_fs.existsSync)((0, node_path.join)(projectDir, "ui", "package.json")); const withApi = (0, node_fs.existsSync)((0, node_path.join)(projectDir, "api", "package.json")); const withHost = (0, node_fs.existsSync)((0, node_path.join)(projectDir, "host", "package.json")); const filteredFiles = /* @__PURE__ */ new Set(); const destToSource = /* @__PURE__ */ new Map(); for (const destPath of FRAMEWORK_OWNED_SYNC_FILES) { if (destPath.startsWith("ui/") && !withUi) continue; if (destPath.startsWith("api/") && !withApi) continue; if (destPath.startsWith("host/") && !withHost) continue; const sourcePath = toSourcePath(sourceDir, destPath); if (!sourcePath) continue; filteredFiles.add(sourcePath); destToSource.set(destPath, sourcePath); } const updated = []; const skipped = []; const added = []; for (const [destPath, filePath] of destToSource.entries()) { const localHash = computeLocalHash(projectDir, destPath); const sourceContent = (0, node_fs.readFileSync)((0, node_path.join)(sourceDir, filePath)); const sourceHash = (0, node_crypto.createHash)("sha256").update(sourceContent).digest("hex").substring(0, 16); if (localHash === null) { added.push(destPath); continue; } if (localHash !== sourceHash) updated.push(destPath); } if (options.dryRun) return { status: "dry-run", updated, skipped, added }; const filesToWrite = [...updated, ...added]; if (filesToWrite.length > 0) { backupFiles(projectDir, filesToWrite); for (const destPath of filesToWrite) writeSyncedFile(sourceDir, projectDir, destToSource.get(destPath) ?? destPath); } const newSnapshotFiles = {}; for (const filePath of filteredFiles) { const content = (0, node_fs.readFileSync)((0, node_path.join)(sourceDir, filePath)); newSnapshotFiles[toDestPath(filePath)] = (0, node_crypto.createHash)("sha256").update(content).digest("hex").substring(0, 16); } await require_snapshot.writeSnapshot(projectDir, { parentRef: `bos://${extendsAccount}/${extendsGateway}`, files: newSnapshotFiles }); const account = localConfig.account || extendsAccount; const domain = localConfig.domain || extendsGateway; const overrides = []; if (withUi) overrides.push("ui"); if (withApi) overrides.push("api"); if (withHost) overrides.push("host"); if (childPlugins.length > 0) overrides.push("plugins"); await require_cli_init.personalizeConfig(projectDir, { extendsAccount, extendsGateway, account, domain, overrides, plugins: childPlugins, workspaceOpts: { sourceDir }, mode: "sync", existingConfig: localConfig }); const syncedConfig = await require_config.loadConfig({ cwd: projectDir }); if (syncedConfig?.runtime) require_infra.writeGeneratedInfra(projectDir, syncedConfig.runtime); if (!options.noInstall) { await require_cli_init.runBunInstall(projectDir); await require_cli_init.runTypesGen(projectDir); } return { status: "synced", updated, skipped, added }; } finally { await cleanup(); } } //#endregion exports.syncTemplate = syncTemplate; //# sourceMappingURL=sync.cjs.map