/** * -------------------------------------------------------------------- * docmd : the zero-config documentation engine. * * @package @docmd/core (and ecosystem) * @website https://docmd.io * @repository https://github.com/docmd-io/docmd * @license MIT * @copyright Copyright (c) 2025-present docmd.io * * [docmd-source] - Please do not remove this header. * -------------------------------------------------------------------- * * Resolves the external (non-@docmd) runtime dependencies of @docmd/core * and its transitive deps, then either: * * node tools/print-okf-runtime-deps.js * → prints `name@range` lines to stdout (human readable / CI log) * * node tools/print-okf-runtime-deps.js --emit-pkg= * → writes a package.json to listing all external deps under * `dependencies`. Used by the Docker builder stage so the * production stage can do a single `npm install --prefix /usr/local` * instead of maintaining a hand-rolled list in the Dockerfile. * * The script is the single source of truth for what external deps the * Docker image needs. Run it after adding a new dep to any @docmd/* * package — the next Docker build will pick up the change automatically * because the builder stage regenerates the package.json fresh. */ const fs = require("fs"); const path = require("path"); const root = process.cwd(); const emitArg = process.argv.find((a) => a.startsWith("--emit-pkg=")); const emitPath = emitArg ? emitArg.slice("--emit-pkg=".length) : null; function findPackageDir(name, dir = path.join(root, "packages")) { for (const entry of fs.readdirSync(dir, {withFileTypes: true})) { if (!entry.isDirectory()) continue; const full = path.join(dir, entry.name); const pj = path.join(full, "package.json"); if (fs.existsSync(pj)) { const pkg = JSON.parse(fs.readFileSync(pj, "utf8")); if (pkg.name === name) return full; } const r = findPackageDir(name, full); if (r) return r; } return null; } function collectDeps(name, seen = new Set()) { if (seen.has(name)) return seen; seen.add(name); const dir = findPackageDir(name); if (!dir) return seen; const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); for (const dep of Object.keys(pkg.dependencies || {})) { if (dep.startsWith("@docmd/")) collectDeps(dep, seen); } return seen; } const internal = collectDeps("@docmd/core"); const external = new Map(); for (const name of internal) { const dir = findPackageDir(name); if (!dir) continue; const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")); for (const [dep, range] of Object.entries(pkg.dependencies || {})) { if (dep.startsWith("@docmd/")) continue; if (dep.startsWith("@mgks/")) continue; if (!external.has(dep)) external.set(dep, range); } } const lines = [...external].sort(([a], [b]) => a.localeCompare(b)); if (emitPath) { // Write a package.json the production stage can `npm install` directly. // `name` and `version` are required by npm but otherwise arbitrary. const pkg = { name: "docmd-runtime-deps", version: "0.0.0", private: true, description: "Auto-generated by tools/print-okf-runtime-deps.js — do not edit.", dependencies: Object.fromEntries(lines) }; fs.mkdirSync(path.dirname(emitPath), { recursive: true }); fs.writeFileSync(emitPath, JSON.stringify(pkg, null, 2) + "\n"); console.log(`[generate-runtime-pkg] wrote ${lines.length} deps → ${emitPath}`); } else { for (const [dep, range] of lines) { console.log(`${dep}@${range}`); } }