99 lines
3.6 KiB
JavaScript
99 lines
3.6 KiB
JavaScript
/**
|
|
* --------------------------------------------------------------------
|
|
* 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=<path>
|
|
* → writes a package.json to <path> 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}`);
|
|
}
|
|
} |