@maroonedsoftware/johnny5 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.
@@ -0,0 +1,102 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/integrations/docker/index.ts
5
+ import { existsSync } from "fs";
6
+ import { isAbsolute, resolve } from "path";
7
+ var parseComposePs = /* @__PURE__ */ __name((raw) => {
8
+ const trimmed = raw.trim();
9
+ if (!trimmed) return [];
10
+ if (trimmed.startsWith("[")) {
11
+ return JSON.parse(trimmed);
12
+ }
13
+ const services = [];
14
+ for (const line of trimmed.split("\n")) {
15
+ if (!line.trim()) continue;
16
+ try {
17
+ services.push(JSON.parse(line));
18
+ } catch {
19
+ }
20
+ }
21
+ return services;
22
+ }, "parseComposePs");
23
+ var dockerServicesUp = /* @__PURE__ */ __name((options = {}) => {
24
+ const composeFileRel = options.composeFile ?? "docker-compose.yml";
25
+ const skipIfMissing = options.skipIfMissing ?? true;
26
+ const check = {
27
+ name: "docker compose services up",
28
+ run: /* @__PURE__ */ __name(async (ctx) => {
29
+ const composeFile = isAbsolute(composeFileRel) ? composeFileRel : resolve(ctx.paths.repoRoot, composeFileRel);
30
+ if (!existsSync(composeFile)) {
31
+ return skipIfMissing ? {
32
+ ok: true,
33
+ message: `no ${composeFileRel}; skipping`
34
+ } : {
35
+ ok: false,
36
+ message: `${composeFile} is missing`
37
+ };
38
+ }
39
+ let raw;
40
+ try {
41
+ const result = await ctx.shell.run("docker", [
42
+ "compose",
43
+ "ps",
44
+ "--format",
45
+ "json"
46
+ ], {
47
+ cwd: ctx.paths.repoRoot
48
+ });
49
+ raw = String(result.stdout);
50
+ } catch (err) {
51
+ return {
52
+ ok: false,
53
+ message: `\`docker compose ps\` failed: ${err.message}`,
54
+ fixHint: "Ensure Docker is installed and running."
55
+ };
56
+ }
57
+ const services = parseComposePs(raw);
58
+ if (services.length === 0) {
59
+ return {
60
+ ok: false,
61
+ message: "no compose services running",
62
+ fixHint: "Run `docker compose up -d`."
63
+ };
64
+ }
65
+ const notRunning = services.filter((s) => s.State !== "running");
66
+ if (notRunning.length > 0) {
67
+ return {
68
+ ok: false,
69
+ message: `not running: ${notRunning.map((s) => s.Service).join(", ")}`,
70
+ fixHint: "Run `docker compose up -d`."
71
+ };
72
+ }
73
+ return {
74
+ ok: true,
75
+ message: `${services.length} service(s) running`
76
+ };
77
+ }, "run")
78
+ };
79
+ if (options.autoStart) {
80
+ check.autoFix = async (ctx) => {
81
+ const exit = await ctx.shell.runStreaming("docker", [
82
+ "compose",
83
+ "up",
84
+ "-d"
85
+ ], {
86
+ cwd: ctx.paths.repoRoot
87
+ });
88
+ return exit === 0 ? {
89
+ ok: true,
90
+ message: "compose services started"
91
+ } : {
92
+ ok: false,
93
+ message: `compose up exited ${exit}`
94
+ };
95
+ };
96
+ }
97
+ return check;
98
+ }, "dockerServicesUp");
99
+ export {
100
+ dockerServicesUp
101
+ };
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/integrations/docker/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\nimport type { Check } from '../../types.js';\n\n/** Options for `dockerServicesUp`. */\nexport interface DockerServicesOptions {\n /**\n * Path to `docker-compose.yml`. Relative paths are resolved against the\n * repo root. Defaults to `'docker-compose.yml'`.\n */\n composeFile?: string;\n /**\n * When true (default), a missing compose file is treated as a passing\n * check with a skip message. When false, it's a failure.\n */\n skipIfMissing?: boolean;\n /** When true, the check attaches an `autoFix` that runs `docker compose up -d`. */\n autoStart?: boolean;\n}\n\ninterface ComposeService {\n Service: string;\n State: string;\n}\n\nconst parseComposePs = (raw: string): ComposeService[] => {\n const trimmed = raw.trim();\n if (!trimmed) return [];\n if (trimmed.startsWith('[')) {\n return JSON.parse(trimmed) as ComposeService[];\n }\n // NDJSON\n const services: ComposeService[] = [];\n for (const line of trimmed.split('\\n')) {\n if (!line.trim()) continue;\n try {\n services.push(JSON.parse(line) as ComposeService);\n } catch {\n // ignore malformed line\n }\n }\n return services;\n};\n\n/**\n * Check that `docker compose ps` reports every service in the running state.\n * Parses both JSON-array and NDJSON output formats. When `autoStart` is set the\n * returned check exposes an `autoFix` that runs `docker compose up -d`.\n */\nexport const dockerServicesUp = (options: DockerServicesOptions = {}): Check => {\n const composeFileRel = options.composeFile ?? 'docker-compose.yml';\n const skipIfMissing = options.skipIfMissing ?? true;\n\n const check: Check = {\n name: 'docker compose services up',\n run: async ctx => {\n const composeFile = isAbsolute(composeFileRel) ? composeFileRel : resolve(ctx.paths.repoRoot, composeFileRel);\n if (!existsSync(composeFile)) {\n return skipIfMissing\n ? { ok: true, message: `no ${composeFileRel}; skipping` }\n : { ok: false, message: `${composeFile} is missing` };\n }\n let raw: string;\n try {\n const result = await ctx.shell.run('docker', ['compose', 'ps', '--format', 'json'], { cwd: ctx.paths.repoRoot });\n raw = String(result.stdout);\n } catch (err) {\n return {\n ok: false,\n message: `\\`docker compose ps\\` failed: ${(err as Error).message}`,\n fixHint: 'Ensure Docker is installed and running.',\n };\n }\n\n const services = parseComposePs(raw);\n if (services.length === 0) {\n return {\n ok: false,\n message: 'no compose services running',\n fixHint: 'Run `docker compose up -d`.',\n };\n }\n const notRunning = services.filter(s => s.State !== 'running');\n if (notRunning.length > 0) {\n return {\n ok: false,\n message: `not running: ${notRunning.map(s => s.Service).join(', ')}`,\n fixHint: 'Run `docker compose up -d`.',\n };\n }\n return { ok: true, message: `${services.length} service(s) running` };\n },\n };\n\n if (options.autoStart) {\n check.autoFix = async ctx => {\n const exit = await ctx.shell.runStreaming('docker', ['compose', 'up', '-d'], { cwd: ctx.paths.repoRoot });\n return exit === 0 ? { ok: true, message: 'compose services started' } : { ok: false, message: `compose up exited ${exit}` };\n };\n }\n\n return check;\n};\n"],"mappings":";;;;AAAA,SAASA,kBAAkB;AAC3B,SAASC,YAAYC,eAAe;AAwBpC,IAAMC,iBAAiB,wBAACC,QAAAA;AACpB,QAAMC,UAAUD,IAAIE,KAAI;AACxB,MAAI,CAACD,QAAS,QAAO,CAAA;AACrB,MAAIA,QAAQE,WAAW,GAAA,GAAM;AACzB,WAAOC,KAAKC,MAAMJ,OAAAA;EACtB;AAEA,QAAMK,WAA6B,CAAA;AACnC,aAAWC,QAAQN,QAAQO,MAAM,IAAA,GAAO;AACpC,QAAI,CAACD,KAAKL,KAAI,EAAI;AAClB,QAAI;AACAI,eAASG,KAAKL,KAAKC,MAAME,IAAAA,CAAAA;IAC7B,QAAQ;IAER;EACJ;AACA,SAAOD;AACX,GAjBuB;AAwBhB,IAAMI,mBAAmB,wBAACC,UAAiC,CAAC,MAAC;AAChE,QAAMC,iBAAiBD,QAAQE,eAAe;AAC9C,QAAMC,gBAAgBH,QAAQG,iBAAiB;AAE/C,QAAMC,QAAe;IACjBC,MAAM;IACNC,KAAK,8BAAMC,QAAAA;AACP,YAAML,cAAcM,WAAWP,cAAAA,IAAkBA,iBAAiBQ,QAAQF,IAAIG,MAAMC,UAAUV,cAAAA;AAC9F,UAAI,CAACW,WAAWV,WAAAA,GAAc;AAC1B,eAAOC,gBACD;UAAEU,IAAI;UAAMC,SAAS,MAAMb,cAAAA;QAA2B,IACtD;UAAEY,IAAI;UAAOC,SAAS,GAAGZ,WAAAA;QAAyB;MAC5D;AACA,UAAIb;AACJ,UAAI;AACA,cAAM0B,SAAS,MAAMR,IAAIS,MAAMV,IAAI,UAAU;UAAC;UAAW;UAAM;UAAY;WAAS;UAAEW,KAAKV,IAAIG,MAAMC;QAAS,CAAA;AAC9GtB,cAAM6B,OAAOH,OAAOI,MAAM;MAC9B,SAASC,KAAK;AACV,eAAO;UACHP,IAAI;UACJC,SAAS,iCAAkCM,IAAcN,OAAO;UAChEO,SAAS;QACb;MACJ;AAEA,YAAM1B,WAAWP,eAAeC,GAAAA;AAChC,UAAIM,SAAS2B,WAAW,GAAG;AACvB,eAAO;UACHT,IAAI;UACJC,SAAS;UACTO,SAAS;QACb;MACJ;AACA,YAAME,aAAa5B,SAAS6B,OAAOC,CAAAA,MAAKA,EAAEC,UAAU,SAAA;AACpD,UAAIH,WAAWD,SAAS,GAAG;AACvB,eAAO;UACHT,IAAI;UACJC,SAAS,gBAAgBS,WAAWI,IAAIF,CAAAA,MAAKA,EAAEG,OAAO,EAAEC,KAAK,IAAA,CAAA;UAC7DR,SAAS;QACb;MACJ;AACA,aAAO;QAAER,IAAI;QAAMC,SAAS,GAAGnB,SAAS2B,MAAM;MAAsB;IACxE,GApCK;EAqCT;AAEA,MAAItB,QAAQ8B,WAAW;AACnB1B,UAAM2B,UAAU,OAAMxB,QAAAA;AAClB,YAAMyB,OAAO,MAAMzB,IAAIS,MAAMiB,aAAa,UAAU;QAAC;QAAW;QAAM;SAAO;QAAEhB,KAAKV,IAAIG,MAAMC;MAAS,CAAA;AACvG,aAAOqB,SAAS,IAAI;QAAEnB,IAAI;QAAMC,SAAS;MAA2B,IAAI;QAAED,IAAI;QAAOC,SAAS,qBAAqBkB,IAAAA;MAAO;IAC9H;EACJ;AAEA,SAAO5B;AACX,GArDgC;","names":["existsSync","isAbsolute","resolve","parseComposePs","raw","trimmed","trim","startsWith","JSON","parse","services","line","split","push","dockerServicesUp","options","composeFileRel","composeFile","skipIfMissing","check","name","run","ctx","isAbsolute","resolve","paths","repoRoot","existsSync","ok","message","result","shell","cwd","String","stdout","err","fixHint","length","notRunning","filter","s","State","map","Service","join","autoStart","autoFix","exit","runStreaming"]}
@@ -0,0 +1,31 @@
1
+ import { b as Check } from '../../types-CH7ccr3j.js';
2
+ import '@maroonedsoftware/appconfig';
3
+ import 'execa';
4
+
5
+ /** Options for `envFile`. */
6
+ interface EnvFileOptions {
7
+ /** Path to the .env file. Relative paths are resolved against the repo root. */
8
+ path: string;
9
+ /** Process env vars that must be set after the file is loaded. */
10
+ required?: string[];
11
+ }
12
+ /**
13
+ * Check that an `.env` file exists at `options.path` and that every entry in
14
+ * `options.required` is present on `process.env`. Note: this check does not
15
+ * itself load the file — it assumes `buildContext` has already done so.
16
+ */
17
+ declare const envFile: (options: EnvFileOptions) => Check;
18
+ /** Options for `portsFree`. Each entry is either a bare port number or `{ port, label }`. */
19
+ interface PortsFreeOptions {
20
+ ports: Array<{
21
+ port: number;
22
+ label?: string;
23
+ } | number>;
24
+ }
25
+ /**
26
+ * Check that every port in `options.ports` is free on `127.0.0.1`. Probes by
27
+ * attempting to bind; reports the labels of any port that fails to bind.
28
+ */
29
+ declare const portsFree: (options: PortsFreeOptions) => Check;
30
+
31
+ export { type EnvFileOptions, type PortsFreeOptions, envFile, portsFree };
@@ -0,0 +1,73 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/integrations/filesystem/index.ts
5
+ import net from "net";
6
+ import { existsSync } from "fs";
7
+ import { isAbsolute, resolve } from "path";
8
+ var envFile = /* @__PURE__ */ __name((options) => ({
9
+ name: `${options.path} present`,
10
+ run: /* @__PURE__ */ __name(async (ctx) => {
11
+ const absolute = isAbsolute(options.path) ? options.path : resolve(ctx.paths.repoRoot, options.path);
12
+ if (!existsSync(absolute)) {
13
+ return {
14
+ ok: false,
15
+ message: `${absolute} is missing`,
16
+ fixHint: `Create ${options.path} (often from a .env.example).`
17
+ };
18
+ }
19
+ const missing = (options.required ?? []).filter((v) => !process.env[v]);
20
+ if (missing.length > 0) {
21
+ return {
22
+ ok: false,
23
+ message: `present, but missing: ${missing.join(", ")}`,
24
+ fixHint: `Add ${missing.join(", ")} to ${options.path}`
25
+ };
26
+ }
27
+ return {
28
+ ok: true,
29
+ message: "present"
30
+ };
31
+ }, "run")
32
+ }), "envFile");
33
+ var probePort = /* @__PURE__ */ __name((port) => new Promise((resolveDone) => {
34
+ const server = net.createServer();
35
+ server.once("error", () => resolveDone(false));
36
+ server.once("listening", () => {
37
+ server.close(() => resolveDone(true));
38
+ });
39
+ server.listen(port, "127.0.0.1");
40
+ }), "probePort");
41
+ var portsFree = /* @__PURE__ */ __name((options) => {
42
+ const specs = options.ports.map((p) => typeof p === "number" ? {
43
+ port: p,
44
+ label: String(p)
45
+ } : {
46
+ port: p.port,
47
+ label: p.label ?? String(p.port)
48
+ });
49
+ return {
50
+ name: "dev ports free",
51
+ run: /* @__PURE__ */ __name(async () => {
52
+ const occupied = [];
53
+ for (const spec of specs) {
54
+ const free = await probePort(spec.port);
55
+ if (!free) occupied.push(spec);
56
+ }
57
+ if (occupied.length === 0) return {
58
+ ok: true,
59
+ message: specs.map((s) => `:${s.port}`).join(", ")
60
+ };
61
+ return {
62
+ ok: false,
63
+ message: `in use: ${occupied.map((s) => `${s.label}:${s.port}`).join(", ")}`,
64
+ fixHint: `Stop the process holding these ports (\`lsof -i :${occupied[0]?.port}\`).`
65
+ };
66
+ }, "run")
67
+ };
68
+ }, "portsFree");
69
+ export {
70
+ envFile,
71
+ portsFree
72
+ };
73
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/integrations/filesystem/index.ts"],"sourcesContent":["import net from 'node:net';\nimport { existsSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\nimport type { Check } from '../../types.js';\n\n/** Options for `envFile`. */\nexport interface EnvFileOptions {\n /** Path to the .env file. Relative paths are resolved against the repo root. */\n path: string;\n /** Process env vars that must be set after the file is loaded. */\n required?: string[];\n}\n\n/**\n * Check that an `.env` file exists at `options.path` and that every entry in\n * `options.required` is present on `process.env`. Note: this check does not\n * itself load the file — it assumes `buildContext` has already done so.\n */\nexport const envFile = (options: EnvFileOptions): Check => ({\n name: `${options.path} present`,\n run: async ctx => {\n const absolute = isAbsolute(options.path) ? options.path : resolve(ctx.paths.repoRoot, options.path);\n if (!existsSync(absolute)) {\n return {\n ok: false,\n message: `${absolute} is missing`,\n fixHint: `Create ${options.path} (often from a .env.example).`,\n };\n }\n const missing = (options.required ?? []).filter(v => !process.env[v]);\n if (missing.length > 0) {\n return {\n ok: false,\n message: `present, but missing: ${missing.join(', ')}`,\n fixHint: `Add ${missing.join(', ')} to ${options.path}`,\n };\n }\n return { ok: true, message: 'present' };\n },\n});\n\n/** Options for `portsFree`. Each entry is either a bare port number or `{ port, label }`. */\nexport interface PortsFreeOptions {\n ports: Array<{ port: number; label?: string } | number>;\n}\n\nconst probePort = (port: number): Promise<boolean> =>\n new Promise(resolveDone => {\n const server = net.createServer();\n server.once('error', () => resolveDone(false));\n server.once('listening', () => {\n server.close(() => resolveDone(true));\n });\n server.listen(port, '127.0.0.1');\n });\n\n/**\n * Check that every port in `options.ports` is free on `127.0.0.1`. Probes by\n * attempting to bind; reports the labels of any port that fails to bind.\n */\nexport const portsFree = (options: PortsFreeOptions): Check => {\n const specs = options.ports.map(p => (typeof p === 'number' ? { port: p, label: String(p) } : { port: p.port, label: p.label ?? String(p.port) }));\n return {\n name: 'dev ports free',\n run: async () => {\n const occupied: Array<{ port: number; label: string }> = [];\n for (const spec of specs) {\n const free = await probePort(spec.port);\n if (!free) occupied.push(spec);\n }\n if (occupied.length === 0) return { ok: true, message: specs.map(s => `:${s.port}`).join(', ') };\n return {\n ok: false,\n message: `in use: ${occupied.map(s => `${s.label}:${s.port}`).join(', ')}`,\n fixHint: `Stop the process holding these ports (\\`lsof -i :${occupied[0]?.port}\\`).`,\n };\n },\n };\n};\n"],"mappings":";;;;AAAA,OAAOA,SAAS;AAChB,SAASC,kBAAkB;AAC3B,SAASC,YAAYC,eAAe;AAgB7B,IAAMC,UAAU,wBAACC,aAAoC;EACxDC,MAAM,GAAGD,QAAQE,IAAI;EACrBC,KAAK,8BAAMC,QAAAA;AACP,UAAMC,WAAWC,WAAWN,QAAQE,IAAI,IAAIF,QAAQE,OAAOK,QAAQH,IAAII,MAAMC,UAAUT,QAAQE,IAAI;AACnG,QAAI,CAACQ,WAAWL,QAAAA,GAAW;AACvB,aAAO;QACHM,IAAI;QACJC,SAAS,GAAGP,QAAAA;QACZQ,SAAS,UAAUb,QAAQE,IAAI;MACnC;IACJ;AACA,UAAMY,WAAWd,QAAQe,YAAY,CAAA,GAAIC,OAAOC,CAAAA,MAAK,CAACC,QAAQC,IAAIF,CAAAA,CAAE;AACpE,QAAIH,QAAQM,SAAS,GAAG;AACpB,aAAO;QACHT,IAAI;QACJC,SAAS,yBAAyBE,QAAQO,KAAK,IAAA,CAAA;QAC/CR,SAAS,OAAOC,QAAQO,KAAK,IAAA,CAAA,OAAYrB,QAAQE,IAAI;MACzD;IACJ;AACA,WAAO;MAAES,IAAI;MAAMC,SAAS;IAAU;EAC1C,GAlBK;AAmBT,IArBuB;AA4BvB,IAAMU,YAAY,wBAACC,SACf,IAAIC,QAAQC,CAAAA,gBAAAA;AACR,QAAMC,SAASC,IAAIC,aAAY;AAC/BF,SAAOG,KAAK,SAAS,MAAMJ,YAAY,KAAA,CAAA;AACvCC,SAAOG,KAAK,aAAa,MAAA;AACrBH,WAAOI,MAAM,MAAML,YAAY,IAAA,CAAA;EACnC,CAAA;AACAC,SAAOK,OAAOR,MAAM,WAAA;AACxB,CAAA,GARc;AAcX,IAAMS,YAAY,wBAAChC,YAAAA;AACtB,QAAMiC,QAAQjC,QAAQkC,MAAMC,IAAIC,CAAAA,MAAM,OAAOA,MAAM,WAAW;IAAEb,MAAMa;IAAGC,OAAOC,OAAOF,CAAAA;EAAG,IAAI;IAAEb,MAAMa,EAAEb;IAAMc,OAAOD,EAAEC,SAASC,OAAOF,EAAEb,IAAI;EAAE,CAAA;AAC/I,SAAO;IACHtB,MAAM;IACNE,KAAK,mCAAA;AACD,YAAMoC,WAAmD,CAAA;AACzD,iBAAWC,QAAQP,OAAO;AACtB,cAAMQ,OAAO,MAAMnB,UAAUkB,KAAKjB,IAAI;AACtC,YAAI,CAACkB,KAAMF,UAASG,KAAKF,IAAAA;MAC7B;AACA,UAAID,SAASnB,WAAW,EAAG,QAAO;QAAET,IAAI;QAAMC,SAASqB,MAAME,IAAIQ,CAAAA,MAAK,IAAIA,EAAEpB,IAAI,EAAE,EAAEF,KAAK,IAAA;MAAM;AAC/F,aAAO;QACHV,IAAI;QACJC,SAAS,WAAW2B,SAASJ,IAAIQ,CAAAA,MAAK,GAAGA,EAAEN,KAAK,IAAIM,EAAEpB,IAAI,EAAE,EAAEF,KAAK,IAAA,CAAA;QACnER,SAAS,oDAAoD0B,SAAS,CAAA,GAAIhB,IAAAA;MAC9E;IACJ,GAZK;EAaT;AACJ,GAlByB;","names":["net","existsSync","isAbsolute","resolve","envFile","options","name","path","run","ctx","absolute","isAbsolute","resolve","paths","repoRoot","existsSync","ok","message","fixHint","missing","required","filter","v","process","env","length","join","probePort","port","Promise","resolveDone","server","net","createServer","once","close","listen","portsFree","specs","ports","map","p","label","String","occupied","spec","free","push","s"]}
@@ -0,0 +1,24 @@
1
+ import { b as Check } from '../../types-CH7ccr3j.js';
2
+ import '@maroonedsoftware/appconfig';
3
+ import 'execa';
4
+
5
+ /** Options for `postgresReachable`. */
6
+ interface PostgresReachableOptions {
7
+ /**
8
+ * The AppConfig key whose string value holds the connection string.
9
+ * Defaults to `'DATABASE_URL'`.
10
+ */
11
+ configKey?: string;
12
+ /** Direct override — takes precedence over `configKey`. */
13
+ connectionString?: string;
14
+ /** Connection timeout in milliseconds. Defaults to `2000`. */
15
+ timeoutMs?: number;
16
+ }
17
+ /**
18
+ * Check that Postgres accepts a connection and responds to `select version()`.
19
+ * Lazily loads `pg` so consumers who don't need the check don't pay the import
20
+ * cost. Returns a failing result with a clear message when `pg` isn't installed.
21
+ */
22
+ declare const postgresReachable: (options?: PostgresReachableOptions) => Check;
23
+
24
+ export { type PostgresReachableOptions, postgresReachable };
@@ -0,0 +1,60 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/integrations/postgres/index.ts
5
+ var postgresReachable = /* @__PURE__ */ __name((options = {}) => ({
6
+ name: "postgres reachable",
7
+ run: /* @__PURE__ */ __name(async (ctx) => {
8
+ let pg;
9
+ try {
10
+ pg = await import("pg");
11
+ } catch {
12
+ return {
13
+ ok: false,
14
+ message: "`pg` is not installed; add it as a dependency to use this check"
15
+ };
16
+ }
17
+ const configKey = options.configKey ?? "DATABASE_URL";
18
+ let url = options.connectionString;
19
+ if (!url) {
20
+ try {
21
+ url = ctx.config.getString(configKey);
22
+ } catch {
23
+ url = process.env[configKey];
24
+ }
25
+ }
26
+ if (!url) {
27
+ return {
28
+ ok: false,
29
+ message: `${configKey} is not set`,
30
+ fixHint: `Set ${configKey} in your .env.`
31
+ };
32
+ }
33
+ const client = new pg.Client({
34
+ connectionString: url,
35
+ connectionTimeoutMillis: options.timeoutMs ?? 2e3
36
+ });
37
+ try {
38
+ await client.connect();
39
+ const result = await client.query("select version() as version");
40
+ const version = result.rows[0]?.version ?? "unknown";
41
+ const short = version.split(" ").slice(0, 2).join(" ");
42
+ return {
43
+ ok: true,
44
+ message: short
45
+ };
46
+ } catch (err) {
47
+ return {
48
+ ok: false,
49
+ message: `connection failed: ${err.message}`,
50
+ fixHint: "Start Postgres (`docker compose up -d`) or check the connection string."
51
+ };
52
+ } finally {
53
+ await client.end().catch(() => void 0);
54
+ }
55
+ }, "run")
56
+ }), "postgresReachable");
57
+ export {
58
+ postgresReachable
59
+ };
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/integrations/postgres/index.ts"],"sourcesContent":["import type { Check } from '../../types.js';\n\n/** Options for `postgresReachable`. */\nexport interface PostgresReachableOptions {\n /**\n * The AppConfig key whose string value holds the connection string.\n * Defaults to `'DATABASE_URL'`.\n */\n configKey?: string;\n /** Direct override — takes precedence over `configKey`. */\n connectionString?: string;\n /** Connection timeout in milliseconds. Defaults to `2000`. */\n timeoutMs?: number;\n}\n\n/**\n * Check that Postgres accepts a connection and responds to `select version()`.\n * Lazily loads `pg` so consumers who don't need the check don't pay the import\n * cost. Returns a failing result with a clear message when `pg` isn't installed.\n */\nexport const postgresReachable = (options: PostgresReachableOptions = {}): Check => ({\n name: 'postgres reachable',\n run: async ctx => {\n let pg: typeof import('pg');\n try {\n pg = await import('pg');\n } catch {\n return { ok: false, message: '`pg` is not installed; add it as a dependency to use this check' };\n }\n\n const configKey = options.configKey ?? 'DATABASE_URL';\n let url: string | undefined = options.connectionString;\n if (!url) {\n try {\n url = ctx.config.getString(configKey);\n } catch {\n url = process.env[configKey];\n }\n }\n if (!url) {\n return {\n ok: false,\n message: `${configKey} is not set`,\n fixHint: `Set ${configKey} in your .env.`,\n };\n }\n\n const client = new pg.Client({ connectionString: url, connectionTimeoutMillis: options.timeoutMs ?? 2000 });\n try {\n await client.connect();\n const result = await client.query<{ version: string }>('select version() as version');\n const version = result.rows[0]?.version ?? 'unknown';\n const short = version.split(' ').slice(0, 2).join(' ');\n return { ok: true, message: short };\n } catch (err) {\n return {\n ok: false,\n message: `connection failed: ${(err as Error).message}`,\n fixHint: 'Start Postgres (`docker compose up -d`) or check the connection string.',\n };\n } finally {\n await client.end().catch(() => undefined);\n }\n },\n});\n"],"mappings":";;;;AAoBO,IAAMA,oBAAoB,wBAACC,UAAoC,CAAC,OAAc;EACjFC,MAAM;EACNC,KAAK,8BAAMC,QAAAA;AACP,QAAIC;AACJ,QAAI;AACAA,WAAK,MAAM,OAAO,IAAA;IACtB,QAAQ;AACJ,aAAO;QAAEC,IAAI;QAAOC,SAAS;MAAkE;IACnG;AAEA,UAAMC,YAAYP,QAAQO,aAAa;AACvC,QAAIC,MAA0BR,QAAQS;AACtC,QAAI,CAACD,KAAK;AACN,UAAI;AACAA,cAAML,IAAIO,OAAOC,UAAUJ,SAAAA;MAC/B,QAAQ;AACJC,cAAMI,QAAQC,IAAIN,SAAAA;MACtB;IACJ;AACA,QAAI,CAACC,KAAK;AACN,aAAO;QACHH,IAAI;QACJC,SAAS,GAAGC,SAAAA;QACZO,SAAS,OAAOP,SAAAA;MACpB;IACJ;AAEA,UAAMQ,SAAS,IAAIX,GAAGY,OAAO;MAAEP,kBAAkBD;MAAKS,yBAAyBjB,QAAQkB,aAAa;IAAK,CAAA;AACzG,QAAI;AACA,YAAMH,OAAOI,QAAO;AACpB,YAAMC,SAAS,MAAML,OAAOM,MAA2B,6BAAA;AACvD,YAAMC,UAAUF,OAAOG,KAAK,CAAA,GAAID,WAAW;AAC3C,YAAME,QAAQF,QAAQG,MAAM,GAAA,EAAKC,MAAM,GAAG,CAAA,EAAGC,KAAK,GAAA;AAClD,aAAO;QAAEtB,IAAI;QAAMC,SAASkB;MAAM;IACtC,SAASI,KAAK;AACV,aAAO;QACHvB,IAAI;QACJC,SAAS,sBAAuBsB,IAActB,OAAO;QACrDQ,SAAS;MACb;IACJ,UAAA;AACI,YAAMC,OAAOc,IAAG,EAAGC,MAAM,MAAMC,MAAAA;IACnC;EACJ,GAzCK;AA0CT,IA5CiC;","names":["postgresReachable","options","name","run","ctx","pg","ok","message","configKey","url","connectionString","config","getString","process","env","fixHint","client","Client","connectionTimeoutMillis","timeoutMs","connect","result","query","version","rows","short","split","slice","join","err","end","catch","undefined"]}
@@ -0,0 +1,28 @@
1
+ import { b as Check } from '../../types-CH7ccr3j.js';
2
+ import '@maroonedsoftware/appconfig';
3
+ import 'execa';
4
+
5
+ /** Options for `redisReachable`. */
6
+ interface RedisReachableOptions {
7
+ /** Direct override for the Redis host. Takes precedence over `hostConfigKey` / env. */
8
+ host?: string;
9
+ /** Direct override for the Redis port. Takes precedence over `portConfigKey` / env. */
10
+ port?: number;
11
+ /**
12
+ * Optional AppConfig keys to read host/port from when they aren't supplied
13
+ * directly. Default: `'REDIS_HOST'`, `'REDIS_PORT'`. Falls back to the
14
+ * matching `process.env` entry when the config getter throws.
15
+ */
16
+ hostConfigKey?: string;
17
+ portConfigKey?: string;
18
+ /** Connection timeout in milliseconds. Defaults to `2000`. */
19
+ timeoutMs?: number;
20
+ }
21
+ /**
22
+ * Check that Redis is reachable and answers `PING`. Lazily loads `ioredis` so
23
+ * consumers who don't need the check don't pay the import cost. Returns a
24
+ * failing result with a clear message when `ioredis` isn't installed.
25
+ */
26
+ declare const redisReachable: (options?: RedisReachableOptions) => Check;
27
+
28
+ export { type RedisReachableOptions, redisReachable };
@@ -0,0 +1,64 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/integrations/redis/index.ts
5
+ var readConfigString = /* @__PURE__ */ __name((ctx, key) => {
6
+ try {
7
+ return ctx.config.getString(key);
8
+ } catch {
9
+ return process.env[key];
10
+ }
11
+ }, "readConfigString");
12
+ var readConfigNumber = /* @__PURE__ */ __name((ctx, key) => {
13
+ try {
14
+ return ctx.config.getNumber(key);
15
+ } catch {
16
+ const raw = process.env[key];
17
+ return raw ? Number(raw) : void 0;
18
+ }
19
+ }, "readConfigNumber");
20
+ var redisReachable = /* @__PURE__ */ __name((options = {}) => ({
21
+ name: "redis reachable",
22
+ run: /* @__PURE__ */ __name(async (ctx) => {
23
+ let RedisCtor;
24
+ try {
25
+ const mod = await import("ioredis");
26
+ RedisCtor = mod.Redis;
27
+ } catch {
28
+ return {
29
+ ok: false,
30
+ message: "`ioredis` is not installed; add it as a dependency to use this check"
31
+ };
32
+ }
33
+ const host = options.host ?? readConfigString(ctx, options.hostConfigKey ?? "REDIS_HOST") ?? "localhost";
34
+ const port = options.port ?? readConfigNumber(ctx, options.portConfigKey ?? "REDIS_PORT") ?? 6379;
35
+ const redis = new RedisCtor({
36
+ host,
37
+ port,
38
+ lazyConnect: true,
39
+ connectTimeout: options.timeoutMs ?? 2e3,
40
+ maxRetriesPerRequest: 1,
41
+ retryStrategy: /* @__PURE__ */ __name(() => null, "retryStrategy")
42
+ });
43
+ try {
44
+ await redis.connect();
45
+ const pong = await redis.ping();
46
+ return {
47
+ ok: pong === "PONG",
48
+ message: `${host}:${port} \u2192 ${pong}`
49
+ };
50
+ } catch (err) {
51
+ return {
52
+ ok: false,
53
+ message: `${host}:${port} unreachable: ${err.message}`,
54
+ fixHint: "Start Redis (`docker compose up -d`) or set the host/port options."
55
+ };
56
+ } finally {
57
+ redis.disconnect();
58
+ }
59
+ }, "run")
60
+ }), "redisReachable");
61
+ export {
62
+ redisReachable
63
+ };
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/integrations/redis/index.ts"],"sourcesContent":["import type { Check } from '../../types.js';\n\n/** Options for `redisReachable`. */\nexport interface RedisReachableOptions {\n /** Direct override for the Redis host. Takes precedence over `hostConfigKey` / env. */\n host?: string;\n /** Direct override for the Redis port. Takes precedence over `portConfigKey` / env. */\n port?: number;\n /**\n * Optional AppConfig keys to read host/port from when they aren't supplied\n * directly. Default: `'REDIS_HOST'`, `'REDIS_PORT'`. Falls back to the\n * matching `process.env` entry when the config getter throws.\n */\n hostConfigKey?: string;\n portConfigKey?: string;\n /** Connection timeout in milliseconds. Defaults to `2000`. */\n timeoutMs?: number;\n}\n\nconst readConfigString = (ctx: { config: { getString: (key: string) => string } }, key: string): string | undefined => {\n try {\n return ctx.config.getString(key);\n } catch {\n return process.env[key];\n }\n};\n\nconst readConfigNumber = (ctx: { config: { getNumber: (key: string) => number } }, key: string): number | undefined => {\n try {\n return ctx.config.getNumber(key);\n } catch {\n const raw = process.env[key];\n return raw ? Number(raw) : undefined;\n }\n};\n\n/**\n * Check that Redis is reachable and answers `PING`. Lazily loads `ioredis` so\n * consumers who don't need the check don't pay the import cost. Returns a\n * failing result with a clear message when `ioredis` isn't installed.\n */\nexport const redisReachable = (options: RedisReachableOptions = {}): Check => ({\n name: 'redis reachable',\n run: async ctx => {\n let RedisCtor: typeof import('ioredis').Redis;\n try {\n const mod = await import('ioredis');\n RedisCtor = mod.Redis;\n } catch {\n return { ok: false, message: '`ioredis` is not installed; add it as a dependency to use this check' };\n }\n\n const host = options.host ?? readConfigString(ctx, options.hostConfigKey ?? 'REDIS_HOST') ?? 'localhost';\n const port = options.port ?? readConfigNumber(ctx, options.portConfigKey ?? 'REDIS_PORT') ?? 6379;\n\n const redis = new RedisCtor({\n host,\n port,\n lazyConnect: true,\n connectTimeout: options.timeoutMs ?? 2000,\n maxRetriesPerRequest: 1,\n retryStrategy: () => null,\n });\n try {\n await redis.connect();\n const pong = await redis.ping();\n return { ok: pong === 'PONG', message: `${host}:${port} → ${pong}` };\n } catch (err) {\n return {\n ok: false,\n message: `${host}:${port} unreachable: ${(err as Error).message}`,\n fixHint: 'Start Redis (`docker compose up -d`) or set the host/port options.',\n };\n } finally {\n redis.disconnect();\n }\n },\n});\n"],"mappings":";;;;AAmBA,IAAMA,mBAAmB,wBAACC,KAAyDC,QAAAA;AAC/E,MAAI;AACA,WAAOD,IAAIE,OAAOC,UAAUF,GAAAA;EAChC,QAAQ;AACJ,WAAOG,QAAQC,IAAIJ,GAAAA;EACvB;AACJ,GANyB;AAQzB,IAAMK,mBAAmB,wBAACN,KAAyDC,QAAAA;AAC/E,MAAI;AACA,WAAOD,IAAIE,OAAOK,UAAUN,GAAAA;EAChC,QAAQ;AACJ,UAAMO,MAAMJ,QAAQC,IAAIJ,GAAAA;AACxB,WAAOO,MAAMC,OAAOD,GAAAA,IAAOE;EAC/B;AACJ,GAPyB;AAclB,IAAMC,iBAAiB,wBAACC,UAAiC,CAAC,OAAc;EAC3EC,MAAM;EACNC,KAAK,8BAAMd,QAAAA;AACP,QAAIe;AACJ,QAAI;AACA,YAAMC,MAAM,MAAM,OAAO,SAAA;AACzBD,kBAAYC,IAAIC;IACpB,QAAQ;AACJ,aAAO;QAAEC,IAAI;QAAOC,SAAS;MAAuE;IACxG;AAEA,UAAMC,OAAOR,QAAQQ,QAAQrB,iBAAiBC,KAAKY,QAAQS,iBAAiB,YAAA,KAAiB;AAC7F,UAAMC,OAAOV,QAAQU,QAAQhB,iBAAiBN,KAAKY,QAAQW,iBAAiB,YAAA,KAAiB;AAE7F,UAAMC,QAAQ,IAAIT,UAAU;MACxBK;MACAE;MACAG,aAAa;MACbC,gBAAgBd,QAAQe,aAAa;MACrCC,sBAAsB;MACtBC,eAAe,6BAAM,MAAN;IACnB,CAAA;AACA,QAAI;AACA,YAAML,MAAMM,QAAO;AACnB,YAAMC,OAAO,MAAMP,MAAMQ,KAAI;AAC7B,aAAO;QAAEd,IAAIa,SAAS;QAAQZ,SAAS,GAAGC,IAAAA,IAAQE,IAAAA,WAAUS,IAAAA;MAAO;IACvE,SAASE,KAAK;AACV,aAAO;QACHf,IAAI;QACJC,SAAS,GAAGC,IAAAA,IAAQE,IAAAA,iBAAsBW,IAAcd,OAAO;QAC/De,SAAS;MACb;IACJ,UAAA;AACIV,YAAMW,WAAU;IACpB;EACJ,GAjCK;AAkCT,IApC8B;","names":["readConfigString","ctx","key","config","getString","process","env","readConfigNumber","getNumber","raw","Number","undefined","redisReachable","options","name","run","RedisCtor","mod","Redis","ok","message","host","hostConfigKey","port","portConfigKey","redis","lazyConnect","connectTimeout","timeoutMs","maxRetriesPerRequest","retryStrategy","connect","pong","ping","err","fixHint","disconnect"]}
@@ -0,0 +1,50 @@
1
+ import { Container, ScopedContainer } from 'injectkit';
2
+ import { AppConfig } from '@maroonedsoftware/appconfig';
3
+ import { Logger } from '@maroonedsoftware/logger';
4
+ import { ServerKitModule } from '@maroonedsoftware/koa';
5
+ import { C as CliContext, d as CommandModule } from '../../types-CH7ccr3j.js';
6
+ import 'execa';
7
+
8
+ /** Options accepted by `bootstrapForCli`. */
9
+ interface BootstrapForCliOptions<ConfigT extends AppConfig = AppConfig> {
10
+ modules: ServerKitModule<ConfigT>[];
11
+ config: ConfigT;
12
+ logger?: Logger;
13
+ }
14
+ /** An InjectKit container and a `shutdown` hook that runs every module's `shutdown` in reverse order. */
15
+ interface CliContainer {
16
+ container: Container;
17
+ shutdown: () => Promise<void>;
18
+ }
19
+ /**
20
+ * Run each `module.setup(registry, config)` and build the InjectKit container.
21
+ * Deliberately does NOT call `module.start()` — CLIs don't want background work
22
+ * (HTTP listeners, job pollers) spinning up. Module `shutdown` hooks are
23
+ * invoked when the returned `shutdown` is called.
24
+ */
25
+ declare const bootstrapForCli: <ConfigT extends AppConfig = AppConfig>(options: BootstrapForCliOptions<ConfigT>) => Promise<CliContainer>;
26
+ /**
27
+ * Associate a list of ServerKit modules with a `CliContext`. The first call to
28
+ * `getOrBootstrapContainer` for that context will lazily run their `setup`
29
+ * hooks. `createCliApp` calls this automatically when `modules` is supplied.
30
+ */
31
+ declare const configureServerKitModules: <ConfigT extends AppConfig>(ctx: CliContext, modules: ServerKitModule<ConfigT>[]) => void;
32
+ /**
33
+ * Return the bootstrapped container for `ctx`, building it on the first call
34
+ * and caching the promise for subsequent calls within the same process.
35
+ * Throws if `configureServerKitModules` hasn't been called for this context.
36
+ */
37
+ declare const getOrBootstrapContainer: (ctx: CliContext) => Promise<CliContainer>;
38
+ /** `CliContext` augmented with a scoped InjectKit container, handed to `requireContainer` handlers. */
39
+ interface RequireContainerCtx extends CliContext {
40
+ container: ScopedContainer;
41
+ }
42
+ /**
43
+ * Wrap a command handler so it lazily bootstraps the ServerKit container and
44
+ * receives a fresh scoped container per invocation. The root container is NOT
45
+ * shut down between commands within the same process — call `bootstrapForCli`
46
+ * directly when explicit teardown is required.
47
+ */
48
+ declare const requireContainer: <Opts = Record<string, unknown>>(handler: (opts: Opts, ctx: RequireContainerCtx, args: string[]) => Promise<number | void>) => CommandModule<Opts>["run"];
49
+
50
+ export { type BootstrapForCliOptions, type CliContainer, type RequireContainerCtx, bootstrapForCli, configureServerKitModules, getOrBootstrapContainer, requireContainer };
@@ -0,0 +1,74 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/integrations/serverkit/index.ts
5
+ import { InjectKitRegistry } from "injectkit";
6
+ import { AppConfig } from "@maroonedsoftware/appconfig";
7
+ import { ConsoleLogger, Logger } from "@maroonedsoftware/logger";
8
+ var bootstrapForCli = /* @__PURE__ */ __name(async (options) => {
9
+ const registry = new InjectKitRegistry();
10
+ registry.register(Logger).useInstance(options.logger ?? new ConsoleLogger());
11
+ registry.register(AppConfig).useInstance(options.config);
12
+ for (const module of options.modules) {
13
+ if (module.setup) await module.setup(registry, options.config);
14
+ }
15
+ const container = registry.build();
16
+ const shutdown = /* @__PURE__ */ __name(async () => {
17
+ for (const module of [
18
+ ...options.modules
19
+ ].reverse()) {
20
+ if (!module.shutdown) continue;
21
+ try {
22
+ await module.shutdown(container);
23
+ } catch {
24
+ }
25
+ }
26
+ }, "shutdown");
27
+ return {
28
+ container,
29
+ shutdown
30
+ };
31
+ }, "bootstrapForCli");
32
+ var STATE_KEY = /* @__PURE__ */ Symbol.for("@maroonedsoftware/johnny5/serverkit/state.v1");
33
+ var getState = /* @__PURE__ */ __name(() => {
34
+ const g = globalThis;
35
+ if (!g[STATE_KEY]) {
36
+ g[STATE_KEY] = {
37
+ containerByContext: /* @__PURE__ */ new WeakMap()
38
+ };
39
+ }
40
+ return g[STATE_KEY];
41
+ }, "getState");
42
+ var configureServerKitModules = /* @__PURE__ */ __name((ctx, modules) => {
43
+ getState().containerByContext.set(ctx, {
44
+ modules
45
+ });
46
+ }, "configureServerKitModules");
47
+ var getOrBootstrapContainer = /* @__PURE__ */ __name(async (ctx) => {
48
+ const lazy = getState().containerByContext.get(ctx);
49
+ if (!lazy) throw new Error("ServerKit modules have not been configured on this CliContext \u2014 call configureServerKitModules() in createCliApp first.");
50
+ if (!lazy.promise) {
51
+ lazy.promise = bootstrapForCli({
52
+ modules: lazy.modules,
53
+ config: ctx.config
54
+ });
55
+ }
56
+ return lazy.promise;
57
+ }, "getOrBootstrapContainer");
58
+ var requireContainer = /* @__PURE__ */ __name((handler) => {
59
+ return async (opts, ctx, args) => {
60
+ const { container } = await getOrBootstrapContainer(ctx);
61
+ const scoped = container.createScopedContainer();
62
+ const enriched = Object.assign({}, ctx, {
63
+ container: scoped
64
+ });
65
+ return handler(opts, enriched, args);
66
+ };
67
+ }, "requireContainer");
68
+ export {
69
+ bootstrapForCli,
70
+ configureServerKitModules,
71
+ getOrBootstrapContainer,
72
+ requireContainer
73
+ };
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/integrations/serverkit/index.ts"],"sourcesContent":["import { InjectKitRegistry, type Container, type ScopedContainer } from 'injectkit';\nimport { AppConfig } from '@maroonedsoftware/appconfig';\nimport { ConsoleLogger, Logger } from '@maroonedsoftware/logger';\nimport type { ServerKitModule } from '@maroonedsoftware/koa';\nimport type { CliContext, CommandModule } from '../../types.js';\n\n/** Options accepted by `bootstrapForCli`. */\nexport interface BootstrapForCliOptions<ConfigT extends AppConfig = AppConfig> {\n modules: ServerKitModule<ConfigT>[];\n config: ConfigT;\n logger?: Logger;\n}\n\n/** An InjectKit container and a `shutdown` hook that runs every module's `shutdown` in reverse order. */\nexport interface CliContainer {\n container: Container;\n shutdown: () => Promise<void>;\n}\n\n/**\n * Run each `module.setup(registry, config)` and build the InjectKit container.\n * Deliberately does NOT call `module.start()` — CLIs don't want background work\n * (HTTP listeners, job pollers) spinning up. Module `shutdown` hooks are\n * invoked when the returned `shutdown` is called.\n */\nexport const bootstrapForCli = async <ConfigT extends AppConfig = AppConfig>(\n options: BootstrapForCliOptions<ConfigT>,\n): Promise<CliContainer> => {\n const registry = new InjectKitRegistry();\n\n registry.register(Logger).useInstance(options.logger ?? new ConsoleLogger());\n registry.register(AppConfig).useInstance(options.config);\n\n for (const module of options.modules) {\n if (module.setup) await module.setup(registry, options.config);\n }\n\n const container = registry.build();\n\n const shutdown = async (): Promise<void> => {\n for (const module of [...options.modules].reverse()) {\n if (!module.shutdown) continue;\n try {\n await module.shutdown(container);\n } catch {\n // Ignore individual module shutdown failures during teardown.\n }\n }\n };\n\n return { container, shutdown };\n};\n\n// Lazy, per-process bootstrap cache. Composite commands within a single\n// invocation reuse the same container; subsequent invocations bootstrap fresh.\ninterface LazyBootstrap<ConfigT extends AppConfig> {\n modules: ServerKitModule<ConfigT>[];\n promise?: Promise<CliContainer>;\n}\n\n// State must live on globalThis under a Symbol.for key so that the main johnny5\n// bundle and the /serverkit subpath bundle share it. tsup with `splitting:\n// false` builds each entry independently, so module-scoped state would be\n// duplicated — createCliApp would write to one copy and requireContainer would\n// read from another. Symbol.for makes the WeakMap process-wide regardless of\n// which bundle initialised it first.\nconst STATE_KEY = Symbol.for('@maroonedsoftware/johnny5/serverkit/state.v1');\n\ninterface Johnny5ServerkitState {\n containerByContext: WeakMap<CliContext, LazyBootstrap<AppConfig>>;\n}\n\nconst getState = (): Johnny5ServerkitState => {\n const g = globalThis as unknown as Record<symbol, Johnny5ServerkitState | undefined>;\n if (!g[STATE_KEY]) {\n g[STATE_KEY] = { containerByContext: new WeakMap() };\n }\n return g[STATE_KEY] as Johnny5ServerkitState;\n};\n\n/**\n * Associate a list of ServerKit modules with a `CliContext`. The first call to\n * `getOrBootstrapContainer` for that context will lazily run their `setup`\n * hooks. `createCliApp` calls this automatically when `modules` is supplied.\n */\nexport const configureServerKitModules = <ConfigT extends AppConfig>(ctx: CliContext, modules: ServerKitModule<ConfigT>[]): void => {\n getState().containerByContext.set(ctx, { modules: modules as ServerKitModule<AppConfig>[] });\n};\n\n/**\n * Return the bootstrapped container for `ctx`, building it on the first call\n * and caching the promise for subsequent calls within the same process.\n * Throws if `configureServerKitModules` hasn't been called for this context.\n */\nexport const getOrBootstrapContainer = async (ctx: CliContext): Promise<CliContainer> => {\n const lazy = getState().containerByContext.get(ctx);\n if (!lazy) throw new Error('ServerKit modules have not been configured on this CliContext — call configureServerKitModules() in createCliApp first.');\n if (!lazy.promise) {\n lazy.promise = bootstrapForCli({\n modules: lazy.modules,\n config: ctx.config,\n });\n }\n return lazy.promise;\n};\n\n/** `CliContext` augmented with a scoped InjectKit container, handed to `requireContainer` handlers. */\nexport interface RequireContainerCtx extends CliContext {\n container: ScopedContainer;\n}\n\n/**\n * Wrap a command handler so it lazily bootstraps the ServerKit container and\n * receives a fresh scoped container per invocation. The root container is NOT\n * shut down between commands within the same process — call `bootstrapForCli`\n * directly when explicit teardown is required.\n */\nexport const requireContainer = <Opts = Record<string, unknown>>(\n handler: (opts: Opts, ctx: RequireContainerCtx, args: string[]) => Promise<number | void>,\n): CommandModule<Opts>['run'] => {\n return async (opts, ctx, args) => {\n const { container } = await getOrBootstrapContainer(ctx);\n const scoped = container.createScopedContainer() as ScopedContainer;\n const enriched: RequireContainerCtx = Object.assign({}, ctx, { container: scoped });\n return handler(opts, enriched, args);\n };\n};\n"],"mappings":";;;;AAAA,SAASA,yBAA+D;AACxE,SAASC,iBAAiB;AAC1B,SAASC,eAAeC,cAAc;AAuB/B,IAAMC,kBAAkB,8BAC3BC,YAAAA;AAEA,QAAMC,WAAW,IAAIC,kBAAAA;AAErBD,WAASE,SAASC,MAAAA,EAAQC,YAAYL,QAAQM,UAAU,IAAIC,cAAAA,CAAAA;AAC5DN,WAASE,SAASK,SAAAA,EAAWH,YAAYL,QAAQS,MAAM;AAEvD,aAAWC,UAAUV,QAAQW,SAAS;AAClC,QAAID,OAAOE,MAAO,OAAMF,OAAOE,MAAMX,UAAUD,QAAQS,MAAM;EACjE;AAEA,QAAMI,YAAYZ,SAASa,MAAK;AAEhC,QAAMC,WAAW,mCAAA;AACb,eAAWL,UAAU;SAAIV,QAAQW;MAASK,QAAO,GAAI;AACjD,UAAI,CAACN,OAAOK,SAAU;AACtB,UAAI;AACA,cAAML,OAAOK,SAASF,SAAAA;MAC1B,QAAQ;MAER;IACJ;EACJ,GATiB;AAWjB,SAAO;IAAEA;IAAWE;EAAS;AACjC,GA1B+B;AAyC/B,IAAME,YAAYC,uBAAOC,IAAI,8CAAA;AAM7B,IAAMC,WAAW,6BAAA;AACb,QAAMC,IAAIC;AACV,MAAI,CAACD,EAAEJ,SAAAA,GAAY;AACfI,MAAEJ,SAAAA,IAAa;MAAEM,oBAAoB,oBAAIC,QAAAA;IAAU;EACvD;AACA,SAAOH,EAAEJ,SAAAA;AACb,GANiB;AAaV,IAAMQ,4BAA4B,wBAA4BC,KAAiBf,YAAAA;AAClFS,WAAAA,EAAWG,mBAAmBI,IAAID,KAAK;IAAEf;EAAiD,CAAA;AAC9F,GAFyC;AASlC,IAAMiB,0BAA0B,8BAAOF,QAAAA;AAC1C,QAAMG,OAAOT,SAAAA,EAAWG,mBAAmBO,IAAIJ,GAAAA;AAC/C,MAAI,CAACG,KAAM,OAAM,IAAIE,MAAM,8HAAA;AAC3B,MAAI,CAACF,KAAKG,SAAS;AACfH,SAAKG,UAAUjC,gBAAgB;MAC3BY,SAASkB,KAAKlB;MACdF,QAAQiB,IAAIjB;IAChB,CAAA;EACJ;AACA,SAAOoB,KAAKG;AAChB,GAVuC;AAuBhC,IAAMC,mBAAmB,wBAC5BC,YAAAA;AAEA,SAAO,OAAOC,MAAMT,KAAKU,SAAAA;AACrB,UAAM,EAAEvB,UAAS,IAAK,MAAMe,wBAAwBF,GAAAA;AACpD,UAAMW,SAASxB,UAAUyB,sBAAqB;AAC9C,UAAMC,WAAgCC,OAAOC,OAAO,CAAC,GAAGf,KAAK;MAAEb,WAAWwB;IAAO,CAAA;AACjF,WAAOH,QAAQC,MAAMI,UAAUH,IAAAA;EACnC;AACJ,GATgC;","names":["InjectKitRegistry","AppConfig","ConsoleLogger","Logger","bootstrapForCli","options","registry","InjectKitRegistry","register","Logger","useInstance","logger","ConsoleLogger","AppConfig","config","module","modules","setup","container","build","shutdown","reverse","STATE_KEY","Symbol","for","getState","g","globalThis","containerByContext","WeakMap","configureServerKitModules","ctx","set","getOrBootstrapContainer","lazy","get","Error","promise","requireContainer","handler","opts","args","scoped","createScopedContainer","enriched","Object","assign"]}
@@ -0,0 +1,26 @@
1
+ import { b as Check } from '../../types-CH7ccr3j.js';
2
+ import '@maroonedsoftware/appconfig';
3
+ import 'execa';
4
+
5
+ /** Options for `nodeVersion`. */
6
+ interface NodeVersionOptions {
7
+ /** Minimum Node major version required. The check fails when `process.versions.node` is below this. */
8
+ min: number;
9
+ }
10
+ /** Check that `process.versions.node` is at least `options.min` (compared by major version). */
11
+ declare const nodeVersion: (options: NodeVersionOptions) => Check;
12
+ /** Options for `pnpmVersion`. */
13
+ interface PnpmVersionOptions {
14
+ /**
15
+ * If supplied, the installed pnpm version must match exactly. Otherwise
16
+ * only presence on PATH is checked.
17
+ */
18
+ expected?: string;
19
+ }
20
+ /**
21
+ * Check that pnpm is on PATH and (optionally) matches a specific version. Fails
22
+ * with a corepack-flavoured fixHint when the binary is missing or mismatched.
23
+ */
24
+ declare const pnpmVersion: (options?: PnpmVersionOptions) => Check;
25
+
26
+ export { type NodeVersionOptions, type PnpmVersionOptions, nodeVersion, pnpmVersion };