okf-catalog 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +87 -0
- package/dist/bundle/cat-file.js +94 -0
- package/dist/bundle/cat-file.js.map +1 -0
- package/dist/bundle/contract.js +60 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/frontmatter.js +106 -0
- package/dist/bundle/frontmatter.js.map +1 -0
- package/dist/bundle/git-tree.js +109 -0
- package/dist/bundle/git-tree.js.map +1 -0
- package/dist/bundle/index-file.js +90 -0
- package/dist/bundle/index-file.js.map +1 -0
- package/dist/bundle/links.js +57 -0
- package/dist/bundle/links.js.map +1 -0
- package/dist/bundle/load.js +363 -0
- package/dist/bundle/load.js.map +1 -0
- package/dist/bundle/manifest.js +95 -0
- package/dist/bundle/manifest.js.map +1 -0
- package/dist/bundle/markdown.js +210 -0
- package/dist/bundle/markdown.js.map +1 -0
- package/dist/bundle/model.js +7 -0
- package/dist/bundle/model.js.map +1 -0
- package/dist/bundle/page.js +374 -0
- package/dist/bundle/page.js.map +1 -0
- package/dist/bundle/paths.js +36 -0
- package/dist/bundle/paths.js.map +1 -0
- package/dist/bundle/reserved.js +56 -0
- package/dist/bundle/reserved.js.map +1 -0
- package/dist/bundle/timestamp.js +49 -0
- package/dist/bundle/timestamp.js.map +1 -0
- package/dist/catalog/model.js +32 -0
- package/dist/catalog/model.js.map +1 -0
- package/dist/catalog/nearest.js +56 -0
- package/dist/catalog/nearest.js.map +1 -0
- package/dist/catalog/outputs.js +438 -0
- package/dist/catalog/outputs.js.map +1 -0
- package/dist/catalog/provenance.js +37 -0
- package/dist/catalog/provenance.js.map +1 -0
- package/dist/catalog/runtime.js +2 -0
- package/dist/catalog/runtime.js.map +1 -0
- package/dist/catalog/text.js +140 -0
- package/dist/catalog/text.js.map +1 -0
- package/dist/cli.js +81 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.js +155 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/pack.js +182 -0
- package/dist/commands/pack.js.map +1 -0
- package/dist/commands/serve.js +452 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/config/company-config.js +272 -0
- package/dist/config/company-config.js.map +1 -0
- package/dist/derive/derived-document.js +42 -0
- package/dist/derive/derived-document.js.map +1 -0
- package/dist/engine/qmd-render.js +54 -0
- package/dist/engine/qmd-render.js.map +1 -0
- package/dist/engine/qmd.js +241 -0
- package/dist/engine/qmd.js.map +1 -0
- package/dist/fs/cache-dir.js +140 -0
- package/dist/fs/cache-dir.js.map +1 -0
- package/dist/fs/company-lock.js +162 -0
- package/dist/fs/company-lock.js.map +1 -0
- package/dist/fs/walk.js +168 -0
- package/dist/fs/walk.js.map +1 -0
- package/dist/log.js +65 -0
- package/dist/log.js.map +1 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.js +45 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools.js +249 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/report/report.js +43 -0
- package/dist/report/report.js.map +1 -0
- package/dist/search/engine.js +2 -0
- package/dist/search/engine.js.map +1 -0
- package/dist/search/query.js +91 -0
- package/dist/search/query.js.map +1 -0
- package/dist/search/search.js +224 -0
- package/dist/search/search.js.map +1 -0
- package/dist/search/snippet.js +95 -0
- package/dist/search/snippet.js.map +1 -0
- package/dist/serve/poller.js +119 -0
- package/dist/serve/poller.js.map +1 -0
- package/dist/serve/runtime.js +390 -0
- package/dist/serve/runtime.js.map +1 -0
- package/dist/source/git-runner.js +232 -0
- package/dist/source/git-runner.js.map +1 -0
- package/dist/source/git.js +457 -0
- package/dist/source/git.js.map +1 -0
- package/dist/source/local.js +20 -0
- package/dist/source/local.js.map +1 -0
- package/dist/source/source.js +2 -0
- package/dist/source/source.js.map +1 -0
- package/npm-shrinkwrap.json +5482 -0
- package/package.json +80 -0
- package/plugin/claude-code/.claude-plugin/plugin.json +15 -0
- package/plugin/claude-code/.mcp.json +10 -0
- package/plugin/claude-code/skills/okf-catalog/SKILL.md +15 -0
- package/recipes/publish/README.md +32 -0
- package/recipes/publish/checkers.lock +435 -0
- package/recipes/publish/checkers.txt +5 -0
- package/recipes/publish/pack.sh +60 -0
- package/recipes/publish/publish.yml +88 -0
- package/recipes/publish/push.sh +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../../src/commands/pack.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n realpathSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { buildManifest, MANIFEST_NAME } from \"../bundle/manifest.js\";\nimport type { BundleFile, LoadOptions, Status } from \"../bundle/model.js\";\nimport { findCollision } from \"../bundle/paths.js\";\nimport { reservedKind } from \"../bundle/reserved.js\";\nimport { readCompanyConfig } from \"../config/company-config.js\";\nimport { cacheOverlapsBundle } from \"../fs/cache-dir.js\";\nimport { walkBundle } from \"../fs/walk.js\";\nimport { renderReport } from \"../report/report.js\";\nimport { type CommandIo, clockFrom, loadForCheck } from \"./check.js\";\n\nexport const PACK_USAGE = `usage: okf-catalog pack --config <path> --from <bundle folder> --out <folder> [options]\n\nWrites the bundle a server will serve: the admitted pages, the reserved files and the attachments as they are,\nan index.md for every folder of pages that lacks one, and a manifest covering every file. The output folder\nmust be new or empty and must not lie inside the source folder (nor the reverse). Nothing is written when the\nloader refuses a file; the report says why and the exit code is 1.\n\noptions:\n --config <path> the company configuration (admission, caps, types, spec text)\n --from <folder> the bundle folder to pack (a checkout's bundle folder, never a repository root)\n --out <folder> where to write; new or empty\n --admit <status> admit this status (stable, deprecated); may be repeated; default from the configuration\n --commit <sha> the source commit to record in the manifest; the zero commit without it\n --help print this text\n\nexit codes: 0 packed; 1 the loader refused a file; 2 usage, configuration or output-folder problem\n`;\n\nconst ZERO_COMMIT = \"0\".repeat(40);\nconst COMMIT = /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/;\nconst STATUSES = new Set<Status>([\"stable\", \"deprecated\"]);\n\n/** `YYYY-MM-DDTHH:mm:ssZ`, the form the fixtures carry, so a pack under OKF_CATALOG_NOW reproduces them. */\nconst publishedAt = (now: Date): string => now.toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n\nfunction usage(io: CommandIo, message: string): number {\n io.stderr(`${message}\\n${PACK_USAGE}`);\n return 2;\n}\n\n/** The real path of a folder that may not exist yet: its nearest existing ancestor's, plus the rest. */\nfunction realPathOf(path: string): string {\n const absolute = resolve(path);\n if (existsSync(absolute)) return realpathSync(absolute);\n return join(realPathOf(dirname(absolute)), basename(absolute));\n}\n\nexport function runPack(argv: string[], io: CommandIo): number {\n let parsed: ReturnType<typeof parseArgs>;\n try {\n parsed = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\" },\n from: { type: \"string\" },\n out: { type: \"string\" },\n admit: { type: \"string\", multiple: true },\n commit: { type: \"string\" },\n },\n allowPositionals: false,\n strict: true,\n });\n } catch (error) {\n return usage(io, (error as Error).message);\n }\n const {\n config: configPath,\n from,\n out,\n commit,\n } = parsed.values as {\n config?: string;\n from?: string;\n out?: string;\n commit?: string;\n };\n const admitFlags = (parsed.values.admit as string[] | undefined) ?? [];\n if (configPath === undefined || from === undefined || out === undefined)\n return usage(io, \"--config, --from and --out are required\");\n if (commit !== undefined && !COMMIT.test(commit))\n return usage(io, \"--commit must be a full commit hash in lower-case hex\");\n const admit: Status[] = [];\n for (const flag of admitFlags) {\n if (!STATUSES.has(flag as Status))\n return usage(io, `--admit must be stable or deprecated, got ${flag}`);\n admit.push(flag as Status);\n }\n\n const read = readCompanyConfig(resolve(configPath), homedir());\n if (!read.ok) {\n io.stderr(`the configuration is not usable: ${read.problems.join(\"; \")}\\n`);\n return 2;\n }\n const config = read.config;\n\n let now: Date;\n try {\n now = clockFrom(io.env);\n } catch (error) {\n io.stderr(`${(error as Error).message}\\n`);\n return 2;\n }\n\n const fromPath = resolve(from);\n const outPath = resolve(out);\n if (!existsSync(fromPath) || !lstatSync(fromPath).isDirectory()) {\n io.stderr(`the bundle folder ${from} does not exist or is not a folder\\n`);\n return 2;\n }\n const outStat = lstatSync(outPath, { throwIfNoEntry: false });\n if (outStat?.isSymbolicLink()) {\n io.stderr(`the output folder ${out} is a link; pack writes a fresh folder only\\n`);\n return 2;\n }\n if (existsSync(outPath)) {\n if (!lstatSync(outPath).isDirectory() || readdirSync(outPath).length > 0) {\n io.stderr(\n `the output folder ${out} exists and is not empty; pack writes a fresh folder only\\n`,\n );\n return 2;\n }\n }\n if (cacheOverlapsBundle(realPathOf(outPath), realpathSync(fromPath))) {\n io.stderr(\n `the output folder ${out} lies inside the bundle folder, or the bundle inside it; choose a folder apart\\n`,\n );\n return 2;\n }\n\n let walked: ReturnType<typeof walkBundle>;\n try {\n walked = walkBundle(fromPath, config.caps);\n } catch (error) {\n io.stderr(`${(error as Error).message}\\n`);\n return 2;\n }\n const options: LoadOptions = {\n admit: admit.length > 0 ? admit : config.serve.admit,\n dev: false,\n integrity: \"none\",\n specText: config.specText,\n caps: config.caps,\n ...(config.types === undefined ? {} : { types: config.types }),\n walkRefusals: walked.refusals,\n hiddenPaths: walked.hidden,\n hiddenFolders: walked.hiddenFolders,\n ...(walked.fatal === undefined ? {} : { walkFatal: walked.fatal }),\n };\n const { catalog, report } = loadForCheck(config.company, walked.files, options, now);\n if (report.fatal !== undefined || report.refusals.length > 0) {\n io.stderr(renderReport(report));\n return 1;\n }\n\n // What travels: admitted pages, reserved files and attachments as walked, and an index where a folder of pages lacks one.\n const byPath = new Map(walked.files.map((f) => [f.path, f] as const));\n const output = new Map<string, Uint8Array>();\n const put = (path: string, bytes: Uint8Array): void => {\n output.set(path.normalize(\"NFC\"), bytes);\n };\n for (const path of catalog.pages.keys()) {\n const file = byPath.get(path);\n if (file !== undefined) put(path, file.bytes);\n }\n for (const file of walked.files) {\n if (file.path === MANIFEST_NAME) continue;\n if (reservedKind(file.path) !== undefined || !file.path.endsWith(\".md\"))\n put(file.path, file.bytes);\n }\n for (const [folder, entry] of catalog.folders) {\n if (entry.indexSource === \"generated\" && entry.index !== undefined) {\n put(folder === \"\" ? \"index.md\" : `${folder}/index.md`, Buffer.from(entry.index.text, \"utf8\"));\n }\n }\n const files: BundleFile[] = [...output.entries()]\n .map(([path, bytes]) => ({ path, bytes }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n // Two names a case-folding file system or the server's key would treat as one cannot be published together.\n const collision = findCollision(files.map((f) => f.path));\n if (collision !== undefined) {\n io.stderr(\n `${collision[1]} and ${collision[0]} collide under case folding or normalisation; rename one of them\\n`,\n );\n return 1;\n }\n const manifest = buildManifest(files, {\n commit: commit ?? ZERO_COMMIT,\n publishedAt: publishedAt(now),\n });\n\n mkdirSync(outPath, { recursive: true });\n for (const file of files) {\n const target = join(outPath, file.path);\n mkdirSync(dirname(target), { recursive: true });\n writeFileSync(target, file.bytes);\n }\n writeFileSync(join(outPath, MANIFEST_NAME), `${JSON.stringify(manifest, null, 2)}\\n`);\n io.stdout(renderReport(report));\n return 0;\n}\n"]}
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { accessSync, constants, rmSync } from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { delimiter, join, resolve } from "node:path";
|
|
5
|
+
import { Writable } from "node:stream";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { RESULT_BUDGET } from "../catalog/outputs.js";
|
|
8
|
+
import { discoverConfigPath, readCompanyConfig } from "../config/company-config.js";
|
|
9
|
+
import { cacheOverlapsBundle, cacheRoot, companyDir, ensureCache } from "../fs/cache-dir.js";
|
|
10
|
+
import { acquireLock, makePrivateDir, processAlive, readLockOwner, sweepPrivate, } from "../fs/company-lock.js";
|
|
11
|
+
import { createLog } from "../log.js";
|
|
12
|
+
import { createServerFactory } from "../mcp/server.js";
|
|
13
|
+
import { serveOverStdio } from "../mcp/stdio.js";
|
|
14
|
+
import { createPoller } from "../serve/poller.js";
|
|
15
|
+
import { createRuntime } from "../serve/runtime.js";
|
|
16
|
+
import { createGitSource } from "../source/git.js";
|
|
17
|
+
import { createGitRunner, redactCredentials } from "../source/git-runner.js";
|
|
18
|
+
import { createLocalSource } from "../source/local.js";
|
|
19
|
+
import { clockFrom } from "./check.js";
|
|
20
|
+
export const SERVE_USAGE = `usage: okf-catalog serve [options]
|
|
21
|
+
|
|
22
|
+
Serves the company's bundle to an MCP client over stdio. stdout carries the protocol and nothing else; the log
|
|
23
|
+
is JSON lines on stderr. The bundle is loaded when the first client completes its handshake.
|
|
24
|
+
|
|
25
|
+
options:
|
|
26
|
+
--config <path> the company configuration (else OKF_CATALOG_CONFIG, else ./okf-catalog.yaml)
|
|
27
|
+
--log-level <level> error, warn, info (default) or debug
|
|
28
|
+
|
|
29
|
+
exit codes: 0 the client closed the connection; 2 usage error or unsupported host
|
|
30
|
+
`;
|
|
31
|
+
const LEVELS = new Set(["error", "warn", "info", "debug"]);
|
|
32
|
+
/** How long a signal waits for in-flight calls before the process ends anyway (the lock dies with it). */
|
|
33
|
+
const SHUTDOWN_DEADLINE_MS = 5_000;
|
|
34
|
+
/**
|
|
35
|
+
* Reserves stdout for the protocol (decision D36). Returns a stream bound to the original stdout writer for the
|
|
36
|
+
* transport; from here on anything else that writes to `process.stdout` lands on stderr instead.
|
|
37
|
+
*/
|
|
38
|
+
function reserveStdout() {
|
|
39
|
+
const original = process.stdout;
|
|
40
|
+
const write = original.write.bind(original);
|
|
41
|
+
const protocol = new Writable({
|
|
42
|
+
write(chunk, encoding, callback) {
|
|
43
|
+
write(chunk, encoding, callback);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
// A failure of the real stdout (the client stopped reading: EPIPE) reaches the transport through its own
|
|
47
|
+
// stream, so it is reported, never an unhandled 'error' event that ends the process.
|
|
48
|
+
original.on("error", (error) => protocol.destroy(error));
|
|
49
|
+
original.write = (chunk, ...rest) => process.stderr.write(chunk, ...rest);
|
|
50
|
+
return protocol;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The minimum git this server runs (D50): `--end-of-options`, `protocol.allow` and `maintenance.auto` are older.
|
|
54
|
+
* Two of the fixed settings are best effort below newer versions and silently ignored before them:
|
|
55
|
+
* `transfer.credentialsInUrl` (2.37) and `credential.interactive` (2.47); the configuration grammar and the
|
|
56
|
+
* detached process (no terminal) cover what they would.
|
|
57
|
+
*/
|
|
58
|
+
const MIN_GIT = [2, 30];
|
|
59
|
+
/** `git` on PATH, resolved once; never read from a variable of its own. */
|
|
60
|
+
async function gitBinary(cacheRoot) {
|
|
61
|
+
void cacheRoot;
|
|
62
|
+
for (const folder of (process.env.PATH ?? "").split(delimiter)) {
|
|
63
|
+
if (folder.length === 0)
|
|
64
|
+
continue;
|
|
65
|
+
const candidate = resolve(folder, "git");
|
|
66
|
+
try {
|
|
67
|
+
accessSync(candidate, constants.X_OK);
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// not here
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw new Error("git was not found on PATH; install git 2.30 or later to serve a repository source");
|
|
75
|
+
}
|
|
76
|
+
async function requireGitVersion(runner, cwd) {
|
|
77
|
+
const result = await runner.run(["--version"], { cwd, timeoutMs: 30_000 });
|
|
78
|
+
const match = /git version (\d+)\.(\d+)/.exec(result.stdout.toString("utf8"));
|
|
79
|
+
const [major, minor] = match === null ? [0, 0] : [Number(match[1]), Number(match[2])];
|
|
80
|
+
if (major > MIN_GIT[0] || (major === MIN_GIT[0] && minor >= MIN_GIT[1]))
|
|
81
|
+
return;
|
|
82
|
+
throw new Error(`git ${MIN_GIT[0]}.${MIN_GIT[1]} or later is required to serve a repository source; this is ${match?.[0] ?? "an unknown version"}`);
|
|
83
|
+
}
|
|
84
|
+
/** Stream failures that mean the client is gone; anything else (a malformed line) is only logged. */
|
|
85
|
+
const STREAM_FAILURES = new Set(["EPIPE", "ERR_STREAM_DESTROYED", "ECONNRESET", "EIO"]);
|
|
86
|
+
/** Every console method becomes a log record, so a dependency's message never reaches the protocol channel. */
|
|
87
|
+
function redirectConsole(log) {
|
|
88
|
+
const methods = ["log", "info", "debug", "dir", "table", "trace", "warn", "error"];
|
|
89
|
+
for (const method of methods) {
|
|
90
|
+
console[method] = (...args) => {
|
|
91
|
+
const text = args
|
|
92
|
+
.map((a) => typeof a === "string"
|
|
93
|
+
? a
|
|
94
|
+
: (() => {
|
|
95
|
+
try {
|
|
96
|
+
return JSON.stringify(a) ?? String(a);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return String(a);
|
|
100
|
+
}
|
|
101
|
+
})())
|
|
102
|
+
.join(" ");
|
|
103
|
+
if (method === "warn" || method === "error")
|
|
104
|
+
log.warn("console", { method, text });
|
|
105
|
+
else
|
|
106
|
+
log.debug("console", { method, text });
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function refusingRuntime(problem, log) {
|
|
111
|
+
log.error("serve.refusing", { problem });
|
|
112
|
+
const reject = () => Promise.reject(new Error(problem));
|
|
113
|
+
return {
|
|
114
|
+
ready: reject,
|
|
115
|
+
lease: reject,
|
|
116
|
+
refresh: reject,
|
|
117
|
+
status: () => ({ lock: "exclusive", loaded: false, refusing: problem }),
|
|
118
|
+
shutdown: async () => undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function version() {
|
|
122
|
+
const require = createRequire(import.meta.url);
|
|
123
|
+
return require("../../package.json").version;
|
|
124
|
+
}
|
|
125
|
+
/** The serve command. Resolves with the exit code once the connection has closed, or at once on a usage error. */
|
|
126
|
+
export async function runServe(argv) {
|
|
127
|
+
const protocolOut = reserveStdout();
|
|
128
|
+
let parsed;
|
|
129
|
+
try {
|
|
130
|
+
parsed = parseArgs({
|
|
131
|
+
args: argv,
|
|
132
|
+
options: {
|
|
133
|
+
config: { type: "string" },
|
|
134
|
+
"log-level": { type: "string", default: "info" },
|
|
135
|
+
},
|
|
136
|
+
allowPositionals: false,
|
|
137
|
+
strict: true,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
process.stderr.write(`${error.message}\n${SERVE_USAGE}`);
|
|
142
|
+
return 2;
|
|
143
|
+
}
|
|
144
|
+
const levelFlag = String(parsed.values["log-level"]);
|
|
145
|
+
if (!LEVELS.has(levelFlag)) {
|
|
146
|
+
process.stderr.write(`--log-level must be error, warn, info or debug\n${SERVE_USAGE}`);
|
|
147
|
+
return 2;
|
|
148
|
+
}
|
|
149
|
+
if (process.platform === "win32") {
|
|
150
|
+
process.stderr.write("Windows is not a version 0 host: the cache folder's ownership and mode checks assume POSIX\n");
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
const log = createLog(process.stderr, levelFlag);
|
|
154
|
+
redirectConsole(log);
|
|
155
|
+
let clock;
|
|
156
|
+
try {
|
|
157
|
+
const fixed = process.env.OKF_CATALOG_NOW;
|
|
158
|
+
if (fixed !== undefined && fixed.length > 0) {
|
|
159
|
+
const at = clockFrom(process.env);
|
|
160
|
+
clock = () => at;
|
|
161
|
+
}
|
|
162
|
+
else
|
|
163
|
+
clock = () => new Date();
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
process.stderr.write(`${error.message}\n`);
|
|
167
|
+
return 2;
|
|
168
|
+
}
|
|
169
|
+
const home = homedir();
|
|
170
|
+
const found = discoverConfigPath(parsed.values.config === undefined ? undefined : String(parsed.values.config), process.env, process.cwd(), home);
|
|
171
|
+
let runtime;
|
|
172
|
+
let options = {
|
|
173
|
+
company: "unknown",
|
|
174
|
+
source: "unknown",
|
|
175
|
+
dev: false,
|
|
176
|
+
limitDefault: 8,
|
|
177
|
+
resultBudget: RESULT_BUDGET,
|
|
178
|
+
};
|
|
179
|
+
let lockHandle;
|
|
180
|
+
let privateWork;
|
|
181
|
+
let poller;
|
|
182
|
+
let closing;
|
|
183
|
+
/** Ends any git in flight and refuses new ones; set once a repository source's runner exists (bite 5 review M2). */
|
|
184
|
+
let abortTransport = () => undefined;
|
|
185
|
+
if ("error" in found) {
|
|
186
|
+
runtime = refusingRuntime(found.error, log);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// A test-only setting (like OKF_CATALOG_NOW): `file` repositories, for the suite's local bare repositories.
|
|
190
|
+
const fileRepositories = process.env.OKF_CATALOG_GIT_PROTOCOLS === "file";
|
|
191
|
+
const read = readCompanyConfig(found.path, home, { allowFileRepositories: fileRepositories });
|
|
192
|
+
if (!read.ok) {
|
|
193
|
+
runtime = refusingRuntime(`the configuration at ${found.path} is not usable: ${read.problems.join("; ")}`, log);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const config = read.config;
|
|
197
|
+
const configured = config.source;
|
|
198
|
+
const root = cacheRoot({ env: process.env, platform: process.platform, home });
|
|
199
|
+
const dir = companyDir(root.root, config.company);
|
|
200
|
+
const described = configured.kind === "local"
|
|
201
|
+
? configured.configured
|
|
202
|
+
: redactCredentials(configured.repository);
|
|
203
|
+
// A repository source is built inside prepare(), once the lock has decided the work folder (D47).
|
|
204
|
+
let gitSource;
|
|
205
|
+
let gitRunner;
|
|
206
|
+
const placeholder = {
|
|
207
|
+
kind: "git",
|
|
208
|
+
load: async () => {
|
|
209
|
+
throw new Error("the repository source is not prepared");
|
|
210
|
+
},
|
|
211
|
+
describe: () => described,
|
|
212
|
+
};
|
|
213
|
+
const source = configured.kind === "local"
|
|
214
|
+
? createLocalSource({ path: configured.path, configured: configured.configured }, config.caps)
|
|
215
|
+
: placeholder;
|
|
216
|
+
options = {
|
|
217
|
+
company: config.company,
|
|
218
|
+
source: described,
|
|
219
|
+
dev: config.serve.dev,
|
|
220
|
+
limitDefault: config.serve.limitDefault,
|
|
221
|
+
resultBudget: RESULT_BUDGET,
|
|
222
|
+
};
|
|
223
|
+
let lockKind = "exclusive";
|
|
224
|
+
const serving = createRuntime({
|
|
225
|
+
company: config.company,
|
|
226
|
+
source,
|
|
227
|
+
prepare: async () => {
|
|
228
|
+
if (configured.kind === "local" && cacheOverlapsBundle(dir, configured.path)) {
|
|
229
|
+
const described = new Error("the cache folder lies inside the bundle folder, or the bundle inside the cache folder; set XDG_CACHE_HOME to a folder outside the bundle");
|
|
230
|
+
described.detail = `cache ${dir}; bundle ${configured.path}`;
|
|
231
|
+
throw described;
|
|
232
|
+
}
|
|
233
|
+
const ensured = ensureCache(dir, root.root, {
|
|
234
|
+
uid: process.getuid?.() ?? 0,
|
|
235
|
+
platform: process.platform,
|
|
236
|
+
});
|
|
237
|
+
if (!ensured.ok) {
|
|
238
|
+
const described = new Error(ensured.problem);
|
|
239
|
+
described.detail = ensured.detail;
|
|
240
|
+
throw described;
|
|
241
|
+
}
|
|
242
|
+
sweepPrivate(dir, processAlive);
|
|
243
|
+
let lock;
|
|
244
|
+
try {
|
|
245
|
+
lock = lockHandle ?? acquireLock(dir, clock());
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
// A lock database that cannot be opened: the fix is named, SQLite's words and the path go to the log.
|
|
249
|
+
const described = new Error("the company lock database in the cache folder is unusable; remove it and start again (the log names it)");
|
|
250
|
+
described.detail = `${join(dir, "lock.sqlite")}: ${error.message}`;
|
|
251
|
+
throw described;
|
|
252
|
+
}
|
|
253
|
+
let work = dir;
|
|
254
|
+
if (lock.kind === "exclusive")
|
|
255
|
+
lockHandle = lock;
|
|
256
|
+
else {
|
|
257
|
+
work = makePrivateDir(dir, process.pid);
|
|
258
|
+
privateWork = work;
|
|
259
|
+
lockKind = "private";
|
|
260
|
+
}
|
|
261
|
+
let result;
|
|
262
|
+
// Imported here, after stdout is reserved, so nothing the engine's modules do at load can reach the channel.
|
|
263
|
+
const { QmdEngine } = await import("../engine/qmd.js");
|
|
264
|
+
const engine = await QmdEngine.open({ company: config.company, dir: work });
|
|
265
|
+
if (engine.resetOnOpen !== undefined)
|
|
266
|
+
log.warn("engine.reset", { detail: engine.resetOnOpen });
|
|
267
|
+
result = {
|
|
268
|
+
engine,
|
|
269
|
+
lock: lockKind,
|
|
270
|
+
...(engine.resetOnOpen === undefined ? {} : { resetOnOpen: engine.resetOnOpen }),
|
|
271
|
+
};
|
|
272
|
+
if (configured.kind === "git") {
|
|
273
|
+
try {
|
|
274
|
+
await prepareGit();
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
await engine.close().catch(() => undefined);
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
async function prepareGit() {
|
|
283
|
+
if (configured.kind !== "git")
|
|
284
|
+
return;
|
|
285
|
+
const binary = await gitBinary(root.root);
|
|
286
|
+
gitRunner?.abort();
|
|
287
|
+
const runner = createGitRunner({
|
|
288
|
+
binary,
|
|
289
|
+
allowProtocols: `https:ssh${process.env.OKF_CATALOG_GIT_PROTOCOLS === "file" ? ":file" : ""}`,
|
|
290
|
+
cacheRoot: root.root,
|
|
291
|
+
env: process.env,
|
|
292
|
+
});
|
|
293
|
+
gitRunner = runner;
|
|
294
|
+
await requireGitVersion(runner, work);
|
|
295
|
+
gitSource = createGitSource({
|
|
296
|
+
repository: configured.repository,
|
|
297
|
+
branch: configured.branch,
|
|
298
|
+
bundlePath: configured.bundlePath,
|
|
299
|
+
workDir: join(work, "source"),
|
|
300
|
+
caps: config.caps,
|
|
301
|
+
runner,
|
|
302
|
+
clock,
|
|
303
|
+
log,
|
|
304
|
+
});
|
|
305
|
+
result = { ...result, source: gitSource };
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
load: {
|
|
309
|
+
admit: config.serve.admit,
|
|
310
|
+
dev: config.serve.dev,
|
|
311
|
+
integrity: config.integrity,
|
|
312
|
+
specText: config.specText,
|
|
313
|
+
caps: config.caps,
|
|
314
|
+
...(config.types === undefined ? {} : { types: config.types }),
|
|
315
|
+
},
|
|
316
|
+
clock,
|
|
317
|
+
log,
|
|
318
|
+
extra: () => ({
|
|
319
|
+
lockOwner: lockKind === "private" ? (readLockOwner(dir) ?? null) : null,
|
|
320
|
+
poller: poller?.state() ??
|
|
321
|
+
(configured.kind === "git" ? { intervalMs: config.serve.pullIntervalMs } : null),
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
// The poller starts once the first load has run, whatever its outcome, and ticks at once when the load
|
|
325
|
+
// answered from the tree on disk; it never starts before the handshake, since start() runs on it.
|
|
326
|
+
let pollerStarted = false;
|
|
327
|
+
const startPoller = () => {
|
|
328
|
+
if (pollerStarted || configured.kind !== "git")
|
|
329
|
+
return;
|
|
330
|
+
pollerStarted = true;
|
|
331
|
+
// The poller exists once the first load settles, whatever its outcome, with the source resolved lazily,
|
|
332
|
+
// so a failed prepare() (git missing, the cache unusable) is retried by its ticks (bite 5 review M4).
|
|
333
|
+
void serving
|
|
334
|
+
.ready()
|
|
335
|
+
.catch(() => undefined)
|
|
336
|
+
.then(() => {
|
|
337
|
+
if (closing !== undefined)
|
|
338
|
+
return;
|
|
339
|
+
poller = createPoller({
|
|
340
|
+
runtime: serving,
|
|
341
|
+
source: () => gitSource,
|
|
342
|
+
intervalMs: config.serve.pullIntervalMs,
|
|
343
|
+
log,
|
|
344
|
+
clock,
|
|
345
|
+
immediate: gitSource?.startedFromDisk() ?? false,
|
|
346
|
+
});
|
|
347
|
+
poller.start();
|
|
348
|
+
});
|
|
349
|
+
};
|
|
350
|
+
abortTransport = () => gitRunner?.abort();
|
|
351
|
+
runtime = {
|
|
352
|
+
...serving,
|
|
353
|
+
start: () => {
|
|
354
|
+
serving.start();
|
|
355
|
+
startPoller();
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
log.info("serve.start", {
|
|
359
|
+
company: config.company,
|
|
360
|
+
source: described,
|
|
361
|
+
dev: config.serve.dev,
|
|
362
|
+
node: process.versions.node,
|
|
363
|
+
configRule: found.rule,
|
|
364
|
+
...(root.note === undefined ? {} : { note: root.note }),
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const factory = createServerFactory(runtime, options, clock, log, version());
|
|
369
|
+
let onTransportFailure = () => undefined;
|
|
370
|
+
const handle = serveOverStdio(factory, { stdin: process.stdin, stdout: protocolOut }, (error) => {
|
|
371
|
+
log.error("transport.error", { error: error.message });
|
|
372
|
+
onTransportFailure(error);
|
|
373
|
+
}, () => runtime.start?.());
|
|
374
|
+
let finished;
|
|
375
|
+
const done = new Promise((resolve) => {
|
|
376
|
+
finished = resolve;
|
|
377
|
+
});
|
|
378
|
+
let signals = 0;
|
|
379
|
+
const cleanup = () => {
|
|
380
|
+
lockHandle?.close();
|
|
381
|
+
if (privateWork !== undefined)
|
|
382
|
+
rmSync(privateWork, { recursive: true, force: true });
|
|
383
|
+
};
|
|
384
|
+
const shutdown = (reason) => {
|
|
385
|
+
if (closing !== undefined)
|
|
386
|
+
return closing;
|
|
387
|
+
closing = (async () => {
|
|
388
|
+
log.info("serve.shutdown", { reason });
|
|
389
|
+
try {
|
|
390
|
+
await poller?.stop();
|
|
391
|
+
abortTransport();
|
|
392
|
+
await runtime.shutdown();
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
log.error("transport.error", { error: error.message });
|
|
396
|
+
}
|
|
397
|
+
cleanup();
|
|
398
|
+
await handle.close().catch(() => undefined);
|
|
399
|
+
finished?.();
|
|
400
|
+
})();
|
|
401
|
+
return closing;
|
|
402
|
+
};
|
|
403
|
+
/** A signal ends the process: the drain gets a deadline, and a second signal ends it at once. */
|
|
404
|
+
/** A shutdown with a deadline: the drain gets its time, then the process ends anyway, git included. */
|
|
405
|
+
const shutdownAndExit = (reason) => {
|
|
406
|
+
const deadline = setTimeout(() => {
|
|
407
|
+
log.error("serve.shutdown", { reason: `${reason}: the drain did not finish in time` });
|
|
408
|
+
abortTransport();
|
|
409
|
+
cleanup();
|
|
410
|
+
process.exit(0);
|
|
411
|
+
}, SHUTDOWN_DEADLINE_MS);
|
|
412
|
+
deadline.unref();
|
|
413
|
+
void shutdown(reason).then(() => {
|
|
414
|
+
clearTimeout(deadline);
|
|
415
|
+
process.exit(0);
|
|
416
|
+
});
|
|
417
|
+
};
|
|
418
|
+
const onSignal = (signal) => {
|
|
419
|
+
signals += 1;
|
|
420
|
+
if (signals > 1) {
|
|
421
|
+
abortTransport();
|
|
422
|
+
cleanup();
|
|
423
|
+
process.exit(130);
|
|
424
|
+
}
|
|
425
|
+
shutdownAndExit(signal);
|
|
426
|
+
};
|
|
427
|
+
onTransportFailure = (error) => {
|
|
428
|
+
const code = error.code ?? "";
|
|
429
|
+
if (STREAM_FAILURES.has(code)) {
|
|
430
|
+
void shutdown(`the client stopped reading (${code})`).then(() => process.exit(0));
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
process.stdin.on("end", () => shutdownAndExit("stdin ended"));
|
|
434
|
+
process.stdin.on("close", () => shutdownAndExit("stdin closed"));
|
|
435
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
436
|
+
process.on(signal, () => onSignal(signal));
|
|
437
|
+
}
|
|
438
|
+
process.on("exit", () => {
|
|
439
|
+
// Synchronous last resort: a crash must not leave a private copy behind.
|
|
440
|
+
if (privateWork !== undefined) {
|
|
441
|
+
try {
|
|
442
|
+
rmSync(privateWork, { recursive: true, force: true });
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// nothing else to do at exit
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
await done;
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants, rmSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { delimiter, join, resolve } from \"node:path\";\nimport { Writable } from \"node:stream\";\nimport { parseArgs } from \"node:util\";\nimport { RESULT_BUDGET } from \"../catalog/outputs.js\";\nimport type { Runtime, ToolOptions } from \"../catalog/runtime.js\";\nimport { discoverConfigPath, readCompanyConfig } from \"../config/company-config.js\";\nimport { cacheOverlapsBundle, cacheRoot, companyDir, ensureCache } from \"../fs/cache-dir.js\";\nimport {\n acquireLock,\n type Lock,\n makePrivateDir,\n processAlive,\n readLockOwner,\n sweepPrivate,\n} from \"../fs/company-lock.js\";\nimport { createLog, type Level, type Log } from \"../log.js\";\nimport { createServerFactory } from \"../mcp/server.js\";\nimport { serveOverStdio } from \"../mcp/stdio.js\";\nimport { createPoller, type Poller } from \"../serve/poller.js\";\nimport { createRuntime, type PrepareResult } from \"../serve/runtime.js\";\nimport { createGitSource, type GitSource } from \"../source/git.js\";\nimport { createGitRunner, type GitRunner, redactCredentials } from \"../source/git-runner.js\";\nimport { createLocalSource } from \"../source/local.js\";\nimport type { Source } from \"../source/source.js\";\nimport { clockFrom } from \"./check.js\";\n\nexport const SERVE_USAGE = `usage: okf-catalog serve [options]\n\nServes the company's bundle to an MCP client over stdio. stdout carries the protocol and nothing else; the log\nis JSON lines on stderr. The bundle is loaded when the first client completes its handshake.\n\noptions:\n --config <path> the company configuration (else OKF_CATALOG_CONFIG, else ./okf-catalog.yaml)\n --log-level <level> error, warn, info (default) or debug\n\nexit codes: 0 the client closed the connection; 2 usage error or unsupported host\n`;\n\nconst LEVELS = new Set<Level>([\"error\", \"warn\", \"info\", \"debug\"]);\n/** How long a signal waits for in-flight calls before the process ends anyway (the lock dies with it). */\nconst SHUTDOWN_DEADLINE_MS = 5_000;\n\n/**\n * Reserves stdout for the protocol (decision D36). Returns a stream bound to the original stdout writer for the\n * transport; from here on anything else that writes to `process.stdout` lands on stderr instead.\n */\nfunction reserveStdout(): Writable {\n const original = process.stdout;\n const write = original.write.bind(original);\n const protocol = new Writable({\n write(chunk, encoding, callback) {\n write(chunk, encoding, callback);\n },\n });\n // A failure of the real stdout (the client stopped reading: EPIPE) reaches the transport through its own\n // stream, so it is reported, never an unhandled 'error' event that ends the process.\n original.on(\"error\", (error) => protocol.destroy(error));\n type Writer = (chunk: unknown, ...rest: unknown[]) => boolean;\n (original as unknown as { write: Writer }).write = (chunk, ...rest) =>\n (process.stderr.write as unknown as Writer)(chunk, ...rest);\n return protocol;\n}\n\n/**\n * The minimum git this server runs (D50): `--end-of-options`, `protocol.allow` and `maintenance.auto` are older.\n * Two of the fixed settings are best effort below newer versions and silently ignored before them:\n * `transfer.credentialsInUrl` (2.37) and `credential.interactive` (2.47); the configuration grammar and the\n * detached process (no terminal) cover what they would.\n */\nconst MIN_GIT: [number, number] = [2, 30];\n\n/** `git` on PATH, resolved once; never read from a variable of its own. */\nasync function gitBinary(cacheRoot: string): Promise<string> {\n void cacheRoot;\n for (const folder of (process.env.PATH ?? \"\").split(delimiter)) {\n if (folder.length === 0) continue;\n const candidate = resolve(folder, \"git\");\n try {\n accessSync(candidate, constants.X_OK);\n return candidate;\n } catch {\n // not here\n }\n }\n throw new Error(\n \"git was not found on PATH; install git 2.30 or later to serve a repository source\",\n );\n}\n\nasync function requireGitVersion(runner: GitRunner, cwd: string): Promise<void> {\n const result = await runner.run([\"--version\"], { cwd, timeoutMs: 30_000 });\n const match = /git version (\\d+)\\.(\\d+)/.exec(result.stdout.toString(\"utf8\"));\n const [major, minor] = match === null ? [0, 0] : [Number(match[1]), Number(match[2])];\n if (major > MIN_GIT[0] || (major === MIN_GIT[0] && minor >= MIN_GIT[1])) return;\n throw new Error(\n `git ${MIN_GIT[0]}.${MIN_GIT[1]} or later is required to serve a repository source; this is ${match?.[0] ?? \"an unknown version\"}`,\n );\n}\n\n/** Stream failures that mean the client is gone; anything else (a malformed line) is only logged. */\nconst STREAM_FAILURES = new Set([\"EPIPE\", \"ERR_STREAM_DESTROYED\", \"ECONNRESET\", \"EIO\"]);\n\n/** Every console method becomes a log record, so a dependency's message never reaches the protocol channel. */\nfunction redirectConsole(log: Log): void {\n const methods = [\"log\", \"info\", \"debug\", \"dir\", \"table\", \"trace\", \"warn\", \"error\"] as const;\n for (const method of methods) {\n (console as unknown as Record<string, unknown>)[method] = (...args: unknown[]) => {\n const text = args\n .map((a) =>\n typeof a === \"string\"\n ? a\n : ((): string => {\n try {\n return JSON.stringify(a) ?? String(a);\n } catch {\n return String(a);\n }\n })(),\n )\n .join(\" \");\n if (method === \"warn\" || method === \"error\") log.warn(\"console\", { method, text });\n else log.debug(\"console\", { method, text });\n };\n }\n}\n\nfunction refusingRuntime(problem: string, log: Log): Runtime {\n log.error(\"serve.refusing\", { problem });\n const reject = (): Promise<never> => Promise.reject(new Error(problem));\n return {\n ready: reject,\n lease: reject,\n refresh: reject,\n status: () => ({ lock: \"exclusive\", loaded: false, refusing: problem }),\n shutdown: async () => undefined,\n };\n}\n\nfunction version(): string {\n const require = createRequire(import.meta.url);\n return (require(\"../../package.json\") as { version: string }).version;\n}\n\n/** The serve command. Resolves with the exit code once the connection has closed, or at once on a usage error. */\nexport async function runServe(argv: string[]): Promise<number> {\n const protocolOut = reserveStdout();\n let parsed: ReturnType<typeof parseArgs>;\n try {\n parsed = parseArgs({\n args: argv,\n options: {\n config: { type: \"string\" },\n \"log-level\": { type: \"string\", default: \"info\" },\n },\n allowPositionals: false,\n strict: true,\n });\n } catch (error) {\n process.stderr.write(`${(error as Error).message}\\n${SERVE_USAGE}`);\n return 2;\n }\n const levelFlag = String(parsed.values[\"log-level\"]);\n if (!LEVELS.has(levelFlag as Level)) {\n process.stderr.write(`--log-level must be error, warn, info or debug\\n${SERVE_USAGE}`);\n return 2;\n }\n if (process.platform === \"win32\") {\n process.stderr.write(\n \"Windows is not a version 0 host: the cache folder's ownership and mode checks assume POSIX\\n\",\n );\n return 2;\n }\n const log = createLog(process.stderr, levelFlag as Level);\n redirectConsole(log);\n let clock: () => Date;\n try {\n const fixed = process.env.OKF_CATALOG_NOW;\n if (fixed !== undefined && fixed.length > 0) {\n const at = clockFrom(process.env);\n clock = () => at;\n } else clock = () => new Date();\n } catch (error) {\n process.stderr.write(`${(error as Error).message}\\n`);\n return 2;\n }\n\n const home = homedir();\n const found = discoverConfigPath(\n parsed.values.config === undefined ? undefined : String(parsed.values.config),\n process.env,\n process.cwd(),\n home,\n );\n let runtime: Runtime;\n let options: ToolOptions = {\n company: \"unknown\",\n source: \"unknown\",\n dev: false,\n limitDefault: 8,\n resultBudget: RESULT_BUDGET,\n };\n let lockHandle: Extract<Lock, { kind: \"exclusive\" }> | undefined;\n let privateWork: string | undefined;\n let poller: Poller | undefined;\n let closing: Promise<void> | undefined;\n /** Ends any git in flight and refuses new ones; set once a repository source's runner exists (bite 5 review M2). */\n let abortTransport: () => void = () => undefined;\n\n if (\"error\" in found) {\n runtime = refusingRuntime(found.error, log);\n } else {\n // A test-only setting (like OKF_CATALOG_NOW): `file` repositories, for the suite's local bare repositories.\n const fileRepositories = process.env.OKF_CATALOG_GIT_PROTOCOLS === \"file\";\n const read = readCompanyConfig(found.path, home, { allowFileRepositories: fileRepositories });\n if (!read.ok) {\n runtime = refusingRuntime(\n `the configuration at ${found.path} is not usable: ${read.problems.join(\"; \")}`,\n log,\n );\n } else {\n const config = read.config;\n const configured = config.source;\n const root = cacheRoot({ env: process.env, platform: process.platform, home });\n const dir = companyDir(root.root, config.company);\n const described =\n configured.kind === \"local\"\n ? configured.configured\n : redactCredentials(configured.repository);\n // A repository source is built inside prepare(), once the lock has decided the work folder (D47).\n let gitSource: GitSource | undefined;\n let gitRunner: GitRunner | undefined;\n const placeholder: Source = {\n kind: \"git\",\n load: async () => {\n throw new Error(\"the repository source is not prepared\");\n },\n describe: () => described,\n };\n const source: Source =\n configured.kind === \"local\"\n ? createLocalSource(\n { path: configured.path, configured: configured.configured },\n config.caps,\n )\n : placeholder;\n options = {\n company: config.company,\n source: described,\n dev: config.serve.dev,\n limitDefault: config.serve.limitDefault,\n resultBudget: RESULT_BUDGET,\n };\n let lockKind: \"exclusive\" | \"private\" = \"exclusive\";\n const serving = createRuntime({\n company: config.company,\n source,\n prepare: async () => {\n if (configured.kind === \"local\" && cacheOverlapsBundle(dir, configured.path)) {\n const described = new Error(\n \"the cache folder lies inside the bundle folder, or the bundle inside the cache folder; set XDG_CACHE_HOME to a folder outside the bundle\",\n ) as Error & { detail?: string };\n described.detail = `cache ${dir}; bundle ${configured.path}`;\n throw described;\n }\n const ensured = ensureCache(dir, root.root, {\n uid: process.getuid?.() ?? 0,\n platform: process.platform,\n });\n if (!ensured.ok) {\n const described = new Error(ensured.problem) as Error & { detail?: string };\n described.detail = ensured.detail;\n throw described;\n }\n sweepPrivate(dir, processAlive);\n let lock: Lock;\n try {\n lock = lockHandle ?? acquireLock(dir, clock());\n } catch (error) {\n // A lock database that cannot be opened: the fix is named, SQLite's words and the path go to the log.\n const described = new Error(\n \"the company lock database in the cache folder is unusable; remove it and start again (the log names it)\",\n ) as Error & { detail?: string };\n described.detail = `${join(dir, \"lock.sqlite\")}: ${(error as Error).message}`;\n throw described;\n }\n let work = dir;\n if (lock.kind === \"exclusive\") lockHandle = lock;\n else {\n work = makePrivateDir(dir, process.pid);\n privateWork = work;\n lockKind = \"private\";\n }\n let result: PrepareResult;\n // Imported here, after stdout is reserved, so nothing the engine's modules do at load can reach the channel.\n const { QmdEngine } = await import(\"../engine/qmd.js\");\n const engine = await QmdEngine.open({ company: config.company, dir: work });\n if (engine.resetOnOpen !== undefined)\n log.warn(\"engine.reset\", { detail: engine.resetOnOpen });\n result = {\n engine,\n lock: lockKind,\n ...(engine.resetOnOpen === undefined ? {} : { resetOnOpen: engine.resetOnOpen }),\n };\n if (configured.kind === \"git\") {\n try {\n await prepareGit();\n } catch (error) {\n await engine.close().catch(() => undefined);\n throw error;\n }\n }\n return result;\n async function prepareGit(): Promise<void> {\n if (configured.kind !== \"git\") return;\n const binary = await gitBinary(root.root);\n gitRunner?.abort();\n const runner = createGitRunner({\n binary,\n allowProtocols: `https:ssh${process.env.OKF_CATALOG_GIT_PROTOCOLS === \"file\" ? \":file\" : \"\"}`,\n cacheRoot: root.root,\n env: process.env,\n });\n gitRunner = runner;\n await requireGitVersion(runner, work);\n gitSource = createGitSource({\n repository: configured.repository,\n branch: configured.branch,\n bundlePath: configured.bundlePath,\n workDir: join(work, \"source\"),\n caps: config.caps,\n runner,\n clock,\n log,\n });\n result = { ...result, source: gitSource };\n }\n },\n load: {\n admit: config.serve.admit,\n dev: config.serve.dev,\n integrity: config.integrity,\n specText: config.specText,\n caps: config.caps,\n ...(config.types === undefined ? {} : { types: config.types }),\n },\n clock,\n log,\n extra: () => ({\n lockOwner: lockKind === \"private\" ? (readLockOwner(dir) ?? null) : null,\n poller:\n poller?.state() ??\n (configured.kind === \"git\" ? { intervalMs: config.serve.pullIntervalMs } : null),\n }),\n });\n // The poller starts once the first load has run, whatever its outcome, and ticks at once when the load\n // answered from the tree on disk; it never starts before the handshake, since start() runs on it.\n let pollerStarted = false;\n const startPoller = (): void => {\n if (pollerStarted || configured.kind !== \"git\") return;\n pollerStarted = true;\n // The poller exists once the first load settles, whatever its outcome, with the source resolved lazily,\n // so a failed prepare() (git missing, the cache unusable) is retried by its ticks (bite 5 review M4).\n void serving\n .ready()\n .catch(() => undefined)\n .then(() => {\n if (closing !== undefined) return;\n poller = createPoller({\n runtime: serving,\n source: () => gitSource,\n intervalMs: config.serve.pullIntervalMs,\n log,\n clock,\n immediate: gitSource?.startedFromDisk() ?? false,\n });\n poller.start();\n });\n };\n abortTransport = () => gitRunner?.abort();\n runtime = {\n ...serving,\n start: () => {\n serving.start();\n startPoller();\n },\n };\n log.info(\"serve.start\", {\n company: config.company,\n source: described,\n dev: config.serve.dev,\n node: process.versions.node,\n configRule: found.rule,\n ...(root.note === undefined ? {} : { note: root.note }),\n });\n }\n }\n\n const factory = createServerFactory(runtime, options, clock, log, version());\n let onTransportFailure: (error: Error) => void = () => undefined;\n const handle = serveOverStdio(\n factory,\n { stdin: process.stdin, stdout: protocolOut },\n (error) => {\n log.error(\"transport.error\", { error: error.message });\n onTransportFailure(error);\n },\n () => runtime.start?.(),\n );\n\n let finished: (() => void) | undefined;\n const done = new Promise<void>((resolve) => {\n finished = resolve;\n });\n let signals = 0;\n const cleanup = (): void => {\n lockHandle?.close();\n if (privateWork !== undefined) rmSync(privateWork, { recursive: true, force: true });\n };\n const shutdown = (reason: string): Promise<void> => {\n if (closing !== undefined) return closing;\n closing = (async () => {\n log.info(\"serve.shutdown\", { reason });\n try {\n await poller?.stop();\n abortTransport();\n await runtime.shutdown();\n } catch (error) {\n log.error(\"transport.error\", { error: (error as Error).message });\n }\n cleanup();\n await handle.close().catch(() => undefined);\n finished?.();\n })();\n return closing;\n };\n /** A signal ends the process: the drain gets a deadline, and a second signal ends it at once. */\n /** A shutdown with a deadline: the drain gets its time, then the process ends anyway, git included. */\n const shutdownAndExit = (reason: string): void => {\n const deadline = setTimeout(() => {\n log.error(\"serve.shutdown\", { reason: `${reason}: the drain did not finish in time` });\n abortTransport();\n cleanup();\n process.exit(0);\n }, SHUTDOWN_DEADLINE_MS);\n deadline.unref();\n void shutdown(reason).then(() => {\n clearTimeout(deadline);\n process.exit(0);\n });\n };\n const onSignal = (signal: string): void => {\n signals += 1;\n if (signals > 1) {\n abortTransport();\n cleanup();\n process.exit(130);\n }\n shutdownAndExit(signal);\n };\n onTransportFailure = (error) => {\n const code = (error as NodeJS.ErrnoException).code ?? \"\";\n if (STREAM_FAILURES.has(code)) {\n void shutdown(`the client stopped reading (${code})`).then(() => process.exit(0));\n }\n };\n process.stdin.on(\"end\", () => shutdownAndExit(\"stdin ended\"));\n process.stdin.on(\"close\", () => shutdownAndExit(\"stdin closed\"));\n for (const signal of [\"SIGINT\", \"SIGTERM\", \"SIGHUP\"] as const) {\n process.on(signal, () => onSignal(signal));\n }\n process.on(\"exit\", () => {\n // Synchronous last resort: a crash must not leave a private copy behind.\n if (privateWork !== undefined) {\n try {\n rmSync(privateWork, { recursive: true, force: true });\n } catch {\n // nothing else to do at exit\n }\n }\n });\n await done;\n return 0;\n}\n"]}
|