@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.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +506 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/docker/index.d.ts +27 -0
- package/dist/integrations/docker/index.js +102 -0
- package/dist/integrations/docker/index.js.map +1 -0
- package/dist/integrations/filesystem/index.d.ts +31 -0
- package/dist/integrations/filesystem/index.js +73 -0
- package/dist/integrations/filesystem/index.js.map +1 -0
- package/dist/integrations/postgres/index.d.ts +24 -0
- package/dist/integrations/postgres/index.js +60 -0
- package/dist/integrations/postgres/index.js.map +1 -0
- package/dist/integrations/redis/index.d.ts +28 -0
- package/dist/integrations/redis/index.js +64 -0
- package/dist/integrations/redis/index.js.map +1 -0
- package/dist/integrations/serverkit/index.d.ts +50 -0
- package/dist/integrations/serverkit/index.js +74 -0
- package/dist/integrations/serverkit/index.js.map +1 -0
- package/dist/integrations/versions/index.d.ts +26 -0
- package/dist/integrations/versions/index.js +56 -0
- package/dist/integrations/versions/index.js.map +1 -0
- package/dist/types-CH7ccr3j.d.ts +119 -0
- package/package.json +113 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/integrations/serverkit/index.ts
|
|
13
|
+
var serverkit_exports = {};
|
|
14
|
+
__export(serverkit_exports, {
|
|
15
|
+
bootstrapForCli: () => bootstrapForCli,
|
|
16
|
+
configureServerKitModules: () => configureServerKitModules,
|
|
17
|
+
getOrBootstrapContainer: () => getOrBootstrapContainer,
|
|
18
|
+
requireContainer: () => requireContainer
|
|
19
|
+
});
|
|
20
|
+
import { InjectKitRegistry } from "injectkit";
|
|
21
|
+
import { AppConfig } from "@maroonedsoftware/appconfig";
|
|
22
|
+
import { ConsoleLogger, Logger } from "@maroonedsoftware/logger";
|
|
23
|
+
var bootstrapForCli, STATE_KEY, getState, configureServerKitModules, getOrBootstrapContainer, requireContainer;
|
|
24
|
+
var init_serverkit = __esm({
|
|
25
|
+
"src/integrations/serverkit/index.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
bootstrapForCli = /* @__PURE__ */ __name(async (options) => {
|
|
28
|
+
const registry = new InjectKitRegistry();
|
|
29
|
+
registry.register(Logger).useInstance(options.logger ?? new ConsoleLogger());
|
|
30
|
+
registry.register(AppConfig).useInstance(options.config);
|
|
31
|
+
for (const module of options.modules) {
|
|
32
|
+
if (module.setup) await module.setup(registry, options.config);
|
|
33
|
+
}
|
|
34
|
+
const container = registry.build();
|
|
35
|
+
const shutdown = /* @__PURE__ */ __name(async () => {
|
|
36
|
+
for (const module of [
|
|
37
|
+
...options.modules
|
|
38
|
+
].reverse()) {
|
|
39
|
+
if (!module.shutdown) continue;
|
|
40
|
+
try {
|
|
41
|
+
await module.shutdown(container);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}, "shutdown");
|
|
46
|
+
return {
|
|
47
|
+
container,
|
|
48
|
+
shutdown
|
|
49
|
+
};
|
|
50
|
+
}, "bootstrapForCli");
|
|
51
|
+
STATE_KEY = /* @__PURE__ */ Symbol.for("@maroonedsoftware/johnny5/serverkit/state.v1");
|
|
52
|
+
getState = /* @__PURE__ */ __name(() => {
|
|
53
|
+
const g = globalThis;
|
|
54
|
+
if (!g[STATE_KEY]) {
|
|
55
|
+
g[STATE_KEY] = {
|
|
56
|
+
containerByContext: /* @__PURE__ */ new WeakMap()
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return g[STATE_KEY];
|
|
60
|
+
}, "getState");
|
|
61
|
+
configureServerKitModules = /* @__PURE__ */ __name((ctx, modules) => {
|
|
62
|
+
getState().containerByContext.set(ctx, {
|
|
63
|
+
modules
|
|
64
|
+
});
|
|
65
|
+
}, "configureServerKitModules");
|
|
66
|
+
getOrBootstrapContainer = /* @__PURE__ */ __name(async (ctx) => {
|
|
67
|
+
const lazy = getState().containerByContext.get(ctx);
|
|
68
|
+
if (!lazy) throw new Error("ServerKit modules have not been configured on this CliContext \u2014 call configureServerKitModules() in createCliApp first.");
|
|
69
|
+
if (!lazy.promise) {
|
|
70
|
+
lazy.promise = bootstrapForCli({
|
|
71
|
+
modules: lazy.modules,
|
|
72
|
+
config: ctx.config
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return lazy.promise;
|
|
76
|
+
}, "getOrBootstrapContainer");
|
|
77
|
+
requireContainer = /* @__PURE__ */ __name((handler) => {
|
|
78
|
+
return async (opts, ctx, args) => {
|
|
79
|
+
const { container } = await getOrBootstrapContainer(ctx);
|
|
80
|
+
const scoped = container.createScopedContainer();
|
|
81
|
+
const enriched = Object.assign({}, ctx, {
|
|
82
|
+
container: scoped
|
|
83
|
+
});
|
|
84
|
+
return handler(opts, enriched, args);
|
|
85
|
+
};
|
|
86
|
+
}, "requireContainer");
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// src/app.ts
|
|
91
|
+
import { Command } from "commander";
|
|
92
|
+
|
|
93
|
+
// src/commander/register.ts
|
|
94
|
+
var applyOption = /* @__PURE__ */ __name((cmd, spec) => {
|
|
95
|
+
if (spec.required) {
|
|
96
|
+
cmd.requiredOption(spec.flags, spec.description, spec.default);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (spec.default !== void 0) {
|
|
100
|
+
cmd.option(spec.flags, spec.description, spec.default);
|
|
101
|
+
} else {
|
|
102
|
+
cmd.option(spec.flags, spec.description);
|
|
103
|
+
}
|
|
104
|
+
}, "applyOption");
|
|
105
|
+
var findOrCreateGroup = /* @__PURE__ */ __name((parent, name) => {
|
|
106
|
+
const existing = parent.commands.find((c) => c.name() === name);
|
|
107
|
+
if (existing) return existing;
|
|
108
|
+
return parent.command(name).description(`${name} commands`);
|
|
109
|
+
}, "findOrCreateGroup");
|
|
110
|
+
var deriveOptionKey = /* @__PURE__ */ __name((flags) => {
|
|
111
|
+
const tokens = flags.split(/[ ,]+/);
|
|
112
|
+
const long = tokens.find((t) => t.startsWith("--"));
|
|
113
|
+
const target = long ?? tokens.find((t) => t.startsWith("-"));
|
|
114
|
+
if (!target) return flags;
|
|
115
|
+
const stripped = target.replace(/^-+/, "");
|
|
116
|
+
return stripped.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
117
|
+
}, "deriveOptionKey");
|
|
118
|
+
var attachLeaf = /* @__PURE__ */ __name((parent, leafName, mod, ctx, sourceLabel) => {
|
|
119
|
+
const cmd = parent.command(leafName).description(mod.description);
|
|
120
|
+
for (const arg of mod.args ?? []) {
|
|
121
|
+
const argName = arg.variadic ? `${arg.name}...` : arg.name;
|
|
122
|
+
if (arg.required) cmd.argument(`<${argName}>`, arg.description);
|
|
123
|
+
else cmd.argument(`[${argName}]`, arg.description);
|
|
124
|
+
}
|
|
125
|
+
for (const opt of mod.options ?? []) {
|
|
126
|
+
applyOption(cmd, opt);
|
|
127
|
+
}
|
|
128
|
+
if (mod.passthrough) cmd.allowUnknownOption(true).allowExcessArguments(true);
|
|
129
|
+
cmd.action(async (...allArgs) => {
|
|
130
|
+
const commandInstance = allArgs[allArgs.length - 1];
|
|
131
|
+
const opts = allArgs[allArgs.length - 2] ?? {};
|
|
132
|
+
const positional = allArgs.slice(0, allArgs.length - 2);
|
|
133
|
+
const positionalStrings = positional.flatMap((p) => Array.isArray(p) ? p.map(String) : p == null ? [] : [
|
|
134
|
+
String(p)
|
|
135
|
+
]);
|
|
136
|
+
const passthroughArgs = mod.passthrough ? commandInstance.args : positionalStrings;
|
|
137
|
+
for (const optSpec of mod.options ?? []) {
|
|
138
|
+
if (!optSpec.envVar) continue;
|
|
139
|
+
const key = deriveOptionKey(optSpec.flags);
|
|
140
|
+
if (opts[key] === void 0 && process.env[optSpec.envVar] !== void 0) {
|
|
141
|
+
opts[key] = process.env[optSpec.envVar];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let finalOpts = opts;
|
|
145
|
+
if (mod.interactive && ctx.isInteractive()) {
|
|
146
|
+
finalOpts = await mod.interactive(ctx, opts);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const exitCode = await mod.run(finalOpts, ctx, passthroughArgs);
|
|
150
|
+
if (typeof exitCode === "number" && exitCode !== 0) process.exit(exitCode);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
ctx.logger.error(`[${sourceLabel}] ${err.message}`);
|
|
153
|
+
if (err.stack) ctx.logger.debug(err.stack ?? "");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}, "attachLeaf");
|
|
158
|
+
var registerCommands = /* @__PURE__ */ __name((program, discovered, ctx) => {
|
|
159
|
+
const registeredPaths = /* @__PURE__ */ new Map();
|
|
160
|
+
const sorted = [
|
|
161
|
+
...discovered
|
|
162
|
+
].sort((a, b) => {
|
|
163
|
+
if (a.source === b.source) return 0;
|
|
164
|
+
return a.source === "core" ? -1 : 1;
|
|
165
|
+
});
|
|
166
|
+
for (const entry of sorted) {
|
|
167
|
+
const key = entry.path.join(" ");
|
|
168
|
+
const existing = registeredPaths.get(key);
|
|
169
|
+
if (existing) {
|
|
170
|
+
const incoming = entry.source === "plugin" ? entry.sourceName ?? "unknown plugin" : "core";
|
|
171
|
+
const owner = existing.source === "plugin" ? existing.sourceName ?? "unknown plugin" : "core";
|
|
172
|
+
throw new Error(`command "${key}" is already registered by ${owner}; ${incoming} cannot override it`);
|
|
173
|
+
}
|
|
174
|
+
registeredPaths.set(key, {
|
|
175
|
+
source: entry.source,
|
|
176
|
+
sourceName: entry.sourceName
|
|
177
|
+
});
|
|
178
|
+
let parent = program;
|
|
179
|
+
for (const segment of entry.path.slice(0, -1)) {
|
|
180
|
+
parent = findOrCreateGroup(parent, segment);
|
|
181
|
+
}
|
|
182
|
+
const leafName = entry.path[entry.path.length - 1];
|
|
183
|
+
if (!leafName) continue;
|
|
184
|
+
const sourceLabel = entry.source === "plugin" ? entry.sourceName ?? "plugin" : "core";
|
|
185
|
+
attachLeaf(parent, leafName, entry.module, ctx, sourceLabel);
|
|
186
|
+
}
|
|
187
|
+
}, "registerCommands");
|
|
188
|
+
|
|
189
|
+
// src/context.ts
|
|
190
|
+
import { existsSync, readFileSync } from "fs";
|
|
191
|
+
import { dirname, resolve } from "path";
|
|
192
|
+
import { AppConfigBuilder, AppConfigProviderDotenv } from "@maroonedsoftware/appconfig";
|
|
193
|
+
|
|
194
|
+
// src/util/logger.ts
|
|
195
|
+
var colour = /* @__PURE__ */ __name((code, text) => `\x1B[${code}m${text}\x1B[0m`, "colour");
|
|
196
|
+
var createDefaultLogger = /* @__PURE__ */ __name((options = {}) => ({
|
|
197
|
+
info: /* @__PURE__ */ __name((msg) => console.log(msg), "info"),
|
|
198
|
+
warn: /* @__PURE__ */ __name((msg) => console.warn(colour(33, `! ${msg}`)), "warn"),
|
|
199
|
+
error: /* @__PURE__ */ __name((msg) => console.error(colour(31, `\u2717 ${msg}`)), "error"),
|
|
200
|
+
success: /* @__PURE__ */ __name((msg) => console.log(colour(32, `\u2713 ${msg}`)), "success"),
|
|
201
|
+
debug: /* @__PURE__ */ __name((msg) => {
|
|
202
|
+
if (options.verbose) console.log(colour(90, `\xB7 ${msg}`));
|
|
203
|
+
}, "debug")
|
|
204
|
+
}), "createDefaultLogger");
|
|
205
|
+
|
|
206
|
+
// src/util/shell.ts
|
|
207
|
+
import { execa } from "execa";
|
|
208
|
+
var createShell = /* @__PURE__ */ __name((cwd, logger) => ({
|
|
209
|
+
run: /* @__PURE__ */ __name((command, args, options) => execa(command, args, {
|
|
210
|
+
cwd,
|
|
211
|
+
...options
|
|
212
|
+
}), "run"),
|
|
213
|
+
runStreaming: /* @__PURE__ */ __name(async (command, args, options) => {
|
|
214
|
+
logger.debug(`$ ${command} ${args.join(" ")}`);
|
|
215
|
+
const child = execa(command, args, {
|
|
216
|
+
cwd,
|
|
217
|
+
stdio: "inherit",
|
|
218
|
+
reject: false,
|
|
219
|
+
...options
|
|
220
|
+
});
|
|
221
|
+
const result = await child;
|
|
222
|
+
return result.exitCode ?? 0;
|
|
223
|
+
}, "runStreaming")
|
|
224
|
+
}), "createShell");
|
|
225
|
+
|
|
226
|
+
// src/util/tty.ts
|
|
227
|
+
var isInteractive = /* @__PURE__ */ __name(() => {
|
|
228
|
+
if (process.env["CI"] === "true" || process.env["CI"] === "1") return false;
|
|
229
|
+
if (process.env["JOHNNY5_NON_INTERACTIVE"] === "1") return false;
|
|
230
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
231
|
+
}, "isInteractive");
|
|
232
|
+
|
|
233
|
+
// src/context.ts
|
|
234
|
+
var findRepoRoot = /* @__PURE__ */ __name((start) => {
|
|
235
|
+
let dir = start;
|
|
236
|
+
for (let i = 0; i < 12; i++) {
|
|
237
|
+
if (existsSync(resolve(dir, "pnpm-workspace.yaml"))) return dir;
|
|
238
|
+
const parent = dirname(dir);
|
|
239
|
+
if (parent === dir) break;
|
|
240
|
+
dir = parent;
|
|
241
|
+
}
|
|
242
|
+
return process.cwd();
|
|
243
|
+
}, "findRepoRoot");
|
|
244
|
+
var expandValue = /* @__PURE__ */ __name((value) => value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => {
|
|
245
|
+
const key = braced ?? bare;
|
|
246
|
+
return process.env[key] ?? "";
|
|
247
|
+
}), "expandValue");
|
|
248
|
+
var loadEnvFile = /* @__PURE__ */ __name((path) => {
|
|
249
|
+
if (!existsSync(path)) return;
|
|
250
|
+
for (const line of readFileSync(path, "utf-8").split("\n")) {
|
|
251
|
+
const trimmed = line.trim();
|
|
252
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
253
|
+
const eqIdx = trimmed.indexOf("=");
|
|
254
|
+
if (eqIdx === -1) continue;
|
|
255
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
256
|
+
const rawValue = trimmed.slice(eqIdx + 1).trim();
|
|
257
|
+
const singleQuoted = rawValue.startsWith("'") && rawValue.endsWith("'");
|
|
258
|
+
const doubleQuoted = rawValue.startsWith('"') && rawValue.endsWith('"');
|
|
259
|
+
let value = singleQuoted || doubleQuoted ? rawValue.slice(1, -1) : rawValue;
|
|
260
|
+
if (!singleQuoted) value = expandValue(value);
|
|
261
|
+
if (!(key in process.env)) process.env[key] = value;
|
|
262
|
+
}
|
|
263
|
+
}, "loadEnvFile");
|
|
264
|
+
var buildDefaultAppConfig = /* @__PURE__ */ __name(async () => new AppConfigBuilder().addProvider(new AppConfigProviderDotenv()).build(), "buildDefaultAppConfig");
|
|
265
|
+
var buildContext = /* @__PURE__ */ __name(async (options = {}) => {
|
|
266
|
+
const cwd = process.cwd();
|
|
267
|
+
const repoRoot = options.repoRoot ?? findRepoRoot(cwd);
|
|
268
|
+
const paths = {
|
|
269
|
+
cwd,
|
|
270
|
+
repoRoot
|
|
271
|
+
};
|
|
272
|
+
for (const envFile of options.envFiles ?? [
|
|
273
|
+
".env",
|
|
274
|
+
"apps/api/.env"
|
|
275
|
+
]) {
|
|
276
|
+
const absolute = envFile.startsWith("/") ? envFile : resolve(repoRoot, envFile);
|
|
277
|
+
loadEnvFile(absolute);
|
|
278
|
+
}
|
|
279
|
+
const logger = options.logger ?? createDefaultLogger({
|
|
280
|
+
verbose: options.verbose
|
|
281
|
+
});
|
|
282
|
+
const shell = createShell(repoRoot, logger);
|
|
283
|
+
const config = options.config ?? await buildDefaultAppConfig();
|
|
284
|
+
return {
|
|
285
|
+
paths,
|
|
286
|
+
logger,
|
|
287
|
+
shell,
|
|
288
|
+
config,
|
|
289
|
+
env: process.env,
|
|
290
|
+
isInteractive
|
|
291
|
+
};
|
|
292
|
+
}, "buildContext");
|
|
293
|
+
|
|
294
|
+
// src/doctor/runner.ts
|
|
295
|
+
var runChecks = /* @__PURE__ */ __name(async (ctx, checks, options) => {
|
|
296
|
+
ctx.logger.info("Running doctor\u2026\n");
|
|
297
|
+
let failed = 0;
|
|
298
|
+
let fixed = 0;
|
|
299
|
+
for (const check of checks) {
|
|
300
|
+
process.stdout.write(` ${check.name.padEnd(36, " ")} `);
|
|
301
|
+
let result;
|
|
302
|
+
try {
|
|
303
|
+
result = await check.run(ctx);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
result = {
|
|
306
|
+
ok: false,
|
|
307
|
+
message: `threw: ${err.message}`
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (result.ok) {
|
|
311
|
+
process.stdout.write(`\x1B[32m\u2713\x1B[0m ${result.message}
|
|
312
|
+
`);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
process.stdout.write(`\x1B[31m\u2717\x1B[0m ${result.message}
|
|
316
|
+
`);
|
|
317
|
+
if (options.fix && check.autoFix) {
|
|
318
|
+
process.stdout.write(` \u21BB attempting auto-fix\u2026 `);
|
|
319
|
+
try {
|
|
320
|
+
const fixResult = await check.autoFix(ctx);
|
|
321
|
+
if (fixResult.ok) {
|
|
322
|
+
process.stdout.write(`\x1B[32m\u2713\x1B[0m ${fixResult.message}
|
|
323
|
+
`);
|
|
324
|
+
fixed++;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
process.stdout.write(`\x1B[31m\u2717\x1B[0m ${fixResult.message}
|
|
328
|
+
`);
|
|
329
|
+
} catch (err) {
|
|
330
|
+
process.stdout.write(`\x1B[31m\u2717\x1B[0m ${err.message}
|
|
331
|
+
`);
|
|
332
|
+
}
|
|
333
|
+
} else if (result.fixHint) {
|
|
334
|
+
process.stdout.write(` \u2192 ${result.fixHint}
|
|
335
|
+
`);
|
|
336
|
+
}
|
|
337
|
+
failed++;
|
|
338
|
+
}
|
|
339
|
+
process.stdout.write("\n");
|
|
340
|
+
if (failed === 0) {
|
|
341
|
+
ctx.logger.success("All checks passed.");
|
|
342
|
+
return 0;
|
|
343
|
+
}
|
|
344
|
+
if (options.fix && fixed > 0) {
|
|
345
|
+
ctx.logger.info(`Auto-fixed ${fixed} issue(s); re-run \`doctor\` to confirm.`);
|
|
346
|
+
}
|
|
347
|
+
ctx.logger.error(`${failed} check(s) failed.`);
|
|
348
|
+
return 1;
|
|
349
|
+
}, "runChecks");
|
|
350
|
+
var buildDoctorCommand = /* @__PURE__ */ __name((checks) => ({
|
|
351
|
+
description: "Run local-dev health checks",
|
|
352
|
+
options: [
|
|
353
|
+
{
|
|
354
|
+
flags: "--fix",
|
|
355
|
+
description: "Attempt auto-remediation for checks that support it"
|
|
356
|
+
}
|
|
357
|
+
],
|
|
358
|
+
run: /* @__PURE__ */ __name(async (opts, ctx) => runChecks(ctx, checks, {
|
|
359
|
+
fix: opts.fix === true
|
|
360
|
+
}), "run")
|
|
361
|
+
}), "buildDoctorCommand");
|
|
362
|
+
|
|
363
|
+
// src/plugin/workspace.loader.ts
|
|
364
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
365
|
+
import { join, resolve as resolve2 } from "path";
|
|
366
|
+
import { pathToFileURL } from "url";
|
|
367
|
+
var loadWorkspacePlugins = /* @__PURE__ */ __name(async (ctx, options) => {
|
|
368
|
+
const rootDirs = options.roots ?? [
|
|
369
|
+
"apps",
|
|
370
|
+
"packages"
|
|
371
|
+
];
|
|
372
|
+
const exclude = new Set(options.excludePackages ?? []);
|
|
373
|
+
const discovered = [];
|
|
374
|
+
for (const rootRel of rootDirs) {
|
|
375
|
+
const root = resolve2(options.repoRoot, rootRel);
|
|
376
|
+
if (!existsSync2(root)) continue;
|
|
377
|
+
for (const entry of readdirSync(root, {
|
|
378
|
+
withFileTypes: true
|
|
379
|
+
})) {
|
|
380
|
+
if (!entry.isDirectory()) continue;
|
|
381
|
+
const pkgPath = join(root, entry.name, "package.json");
|
|
382
|
+
if (!existsSync2(pkgPath)) continue;
|
|
383
|
+
let pkg;
|
|
384
|
+
try {
|
|
385
|
+
pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
386
|
+
} catch {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const commandsRel = pkg.johnny5?.commands;
|
|
390
|
+
if (!commandsRel) continue;
|
|
391
|
+
if (pkg.name && exclude.has(pkg.name)) continue;
|
|
392
|
+
const manifestPath = resolve2(root, entry.name, commandsRel);
|
|
393
|
+
if (!existsSync2(manifestPath)) {
|
|
394
|
+
ctx.logger.warn(`johnny5 plugin manifest missing for ${pkg.name ?? entry.name}: ${manifestPath}`);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
const mod = await import(pathToFileURL(manifestPath).href);
|
|
399
|
+
const manifest = mod.default;
|
|
400
|
+
if (!manifest || !Array.isArray(manifest.commands)) {
|
|
401
|
+
ctx.logger.warn(`johnny5 plugin ${pkg.name ?? entry.name} has no commands array; skipping`);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
for (const cmd of manifest.commands) {
|
|
405
|
+
discovered.push({
|
|
406
|
+
path: cmd.path,
|
|
407
|
+
source: "plugin",
|
|
408
|
+
sourceName: manifest.name ?? pkg.name,
|
|
409
|
+
module: cmd.module
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
} catch (err) {
|
|
413
|
+
ctx.logger.warn(`johnny5 plugin ${pkg.name ?? entry.name} failed to load: ${err.message}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return discovered;
|
|
418
|
+
}, "loadWorkspacePlugins");
|
|
419
|
+
|
|
420
|
+
// src/app.ts
|
|
421
|
+
var defineCommand = /* @__PURE__ */ __name((mod) => mod, "defineCommand");
|
|
422
|
+
var createCliApp = /* @__PURE__ */ __name(async (options) => {
|
|
423
|
+
const verbose = process.argv.includes("-v") || process.argv.includes("--verbose");
|
|
424
|
+
const resolvedConfig = typeof options.config === "function" ? await options.config() : options.config;
|
|
425
|
+
const ctx = await buildContext({
|
|
426
|
+
config: resolvedConfig,
|
|
427
|
+
logger: options.logger,
|
|
428
|
+
verbose
|
|
429
|
+
});
|
|
430
|
+
if (options.modules && options.modules.length > 0) {
|
|
431
|
+
const { configureServerKitModules: configureServerKitModules2 } = await Promise.resolve().then(() => (init_serverkit(), serverkit_exports));
|
|
432
|
+
configureServerKitModules2(ctx, options.modules);
|
|
433
|
+
}
|
|
434
|
+
const program = new Command().name(options.name).description(options.description).version(options.version).option("-v, --verbose", "Enable verbose logging", false);
|
|
435
|
+
const discovered = options.commands.map((c) => ({
|
|
436
|
+
...c,
|
|
437
|
+
source: "core"
|
|
438
|
+
}));
|
|
439
|
+
if (options.checks && options.checks.length > 0 && options.doctorCommandPath !== null) {
|
|
440
|
+
const doctorPath = options.doctorCommandPath ?? [
|
|
441
|
+
"doctor"
|
|
442
|
+
];
|
|
443
|
+
const alreadyDefined = discovered.some((c) => c.path.join(" ") === doctorPath.join(" "));
|
|
444
|
+
if (!alreadyDefined) {
|
|
445
|
+
discovered.push({
|
|
446
|
+
path: doctorPath,
|
|
447
|
+
source: "core",
|
|
448
|
+
module: buildDoctorCommand(options.checks)
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (options.plugins?.workspace) {
|
|
453
|
+
const workspaceOpts = {
|
|
454
|
+
...options.plugins.workspace,
|
|
455
|
+
repoRoot: options.plugins.workspace.repoRoot ?? ctx.paths.repoRoot
|
|
456
|
+
};
|
|
457
|
+
const plugins = await loadWorkspacePlugins(ctx, workspaceOpts);
|
|
458
|
+
discovered.push(...plugins);
|
|
459
|
+
}
|
|
460
|
+
registerCommands(program, discovered, ctx);
|
|
461
|
+
return {
|
|
462
|
+
run: /* @__PURE__ */ __name(async (argv = process.argv) => {
|
|
463
|
+
try {
|
|
464
|
+
await program.parseAsync(argv);
|
|
465
|
+
return 0;
|
|
466
|
+
} catch (err) {
|
|
467
|
+
ctx.logger.error(err.message);
|
|
468
|
+
return 1;
|
|
469
|
+
}
|
|
470
|
+
}, "run")
|
|
471
|
+
};
|
|
472
|
+
}, "createCliApp");
|
|
473
|
+
|
|
474
|
+
// src/util/prompts.ts
|
|
475
|
+
import * as clack from "@clack/prompts";
|
|
476
|
+
var prompts = clack;
|
|
477
|
+
var PromptCancelledError = class extends Error {
|
|
478
|
+
static {
|
|
479
|
+
__name(this, "PromptCancelledError");
|
|
480
|
+
}
|
|
481
|
+
constructor() {
|
|
482
|
+
super("prompt cancelled");
|
|
483
|
+
this.name = "PromptCancelledError";
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
var unwrap = /* @__PURE__ */ __name((value) => {
|
|
487
|
+
if (clack.isCancel(value)) throw new PromptCancelledError();
|
|
488
|
+
return value;
|
|
489
|
+
}, "unwrap");
|
|
490
|
+
export {
|
|
491
|
+
PromptCancelledError,
|
|
492
|
+
buildContext,
|
|
493
|
+
buildDefaultAppConfig,
|
|
494
|
+
buildDoctorCommand,
|
|
495
|
+
createCliApp,
|
|
496
|
+
createDefaultLogger,
|
|
497
|
+
createShell,
|
|
498
|
+
defineCommand,
|
|
499
|
+
isInteractive,
|
|
500
|
+
loadWorkspacePlugins,
|
|
501
|
+
prompts,
|
|
502
|
+
registerCommands,
|
|
503
|
+
runChecks,
|
|
504
|
+
unwrap
|
|
505
|
+
};
|
|
506
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integrations/serverkit/index.ts","../src/app.ts","../src/commander/register.ts","../src/context.ts","../src/util/logger.ts","../src/util/shell.ts","../src/util/tty.ts","../src/doctor/runner.ts","../src/plugin/workspace.loader.ts","../src/util/prompts.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","import { Command } from 'commander';\nimport type { AppConfig } from '@maroonedsoftware/appconfig';\nimport type { Check, CliContext, CommandModule, CommandRegistration, DiscoveredCommand } from './types.js';\nimport { registerCommands } from './commander/register.js';\nimport { buildContext } from './context.js';\nimport { buildDoctorCommand } from './doctor/runner.js';\nimport { loadWorkspacePlugins, type WorkspacePluginOptions } from './plugin/workspace.loader.js';\nimport type { CliLogger } from './util/logger.js';\n\n// Opaque ServerKit module shape — the concrete `ServerKitModule` type lives in\n// `@maroonedsoftware/koa`. Importing it here would force every johnny5 consumer\n// to pull koa as a hard dep even when not using ServerKit. The serverkit\n// integration is responsible for the actual setup() / shutdown() calls.\ninterface ServerKitModuleLike<ConfigT> {\n name?: string;\n setup?: (registry: unknown, config: ConfigT) => Promise<void>;\n start?: (container: unknown) => Promise<void>;\n shutdown?: (container: unknown) => Promise<void>;\n}\n\n/** Options accepted by `createCliApp`. */\nexport interface CliAppOptions<ConfigT extends AppConfig = AppConfig> {\n name: string;\n description: string;\n version: string;\n commands: CommandRegistration[];\n checks?: Check[];\n config?: ConfigT | (() => Promise<ConfigT>);\n logger?: CliLogger;\n // ServerKit modules to bootstrap lazily for commands written with\n // `requireContainer`. Setting this enables the @maroonedsoftware/johnny5/serverkit\n // integration — make sure that subpath is imported once for its side effect\n // of installing the bootstrap hook (or call configureServerKitModules\n // manually).\n modules?: ServerKitModuleLike<ConfigT>[];\n plugins?: {\n workspace?: Omit<WorkspacePluginOptions, 'repoRoot'> & { repoRoot?: string };\n };\n // Path of the built-in doctor command. Defaults to ['doctor']. Set to\n // null explicitly when supplying your own doctor command.\n doctorCommandPath?: string[] | null;\n}\n\n/** The runnable CLI returned by `createCliApp`. */\nexport interface CliApp {\n /** Parse `argv` (defaults to `process.argv`) and resolve with a process exit code. */\n run: (argv?: string[]) => Promise<number>;\n}\n\n/**\n * Identity helper that exists purely to give TypeScript a place to infer the\n * `Opts` generic from the literal passed in. Equivalent to writing the type\n * annotation manually.\n */\nexport const defineCommand = <Opts = Record<string, unknown>>(mod: CommandModule<Opts>): CommandModule<Opts> => mod;\n\n/**\n * Build a CLI from a list of `CommandModule` registrations. Auto-registers a\n * `doctor` subcommand when `checks` is non-empty, discovers workspace plugins\n * when `plugins.workspace` is configured, and wires up the ServerKit\n * integration when `modules` is supplied.\n */\nexport const createCliApp = async <ConfigT extends AppConfig = AppConfig>(options: CliAppOptions<ConfigT>): Promise<CliApp> => {\n const verbose = process.argv.includes('-v') || process.argv.includes('--verbose');\n const resolvedConfig = typeof options.config === 'function' ? await options.config() : options.config;\n const ctx = await buildContext({\n config: resolvedConfig,\n logger: options.logger,\n verbose,\n });\n\n if (options.modules && options.modules.length > 0) {\n const { configureServerKitModules } = (await import('./integrations/serverkit/index.js')) as {\n configureServerKitModules: (ctx: CliContext, modules: unknown[]) => void;\n };\n configureServerKitModules(ctx, options.modules);\n }\n\n const program = new Command()\n .name(options.name)\n .description(options.description)\n .version(options.version)\n .option('-v, --verbose', 'Enable verbose logging', false);\n\n const discovered: DiscoveredCommand[] = options.commands.map(c => ({ ...c, source: 'core' as const }));\n\n if (options.checks && options.checks.length > 0 && options.doctorCommandPath !== null) {\n const doctorPath = options.doctorCommandPath ?? ['doctor'];\n const alreadyDefined = discovered.some(c => c.path.join(' ') === doctorPath.join(' '));\n if (!alreadyDefined) {\n discovered.push({\n path: doctorPath,\n source: 'core',\n module: buildDoctorCommand(options.checks),\n });\n }\n }\n\n if (options.plugins?.workspace) {\n const workspaceOpts: WorkspacePluginOptions = {\n ...options.plugins.workspace,\n repoRoot: options.plugins.workspace.repoRoot ?? ctx.paths.repoRoot,\n };\n const plugins = await loadWorkspacePlugins(ctx, workspaceOpts);\n discovered.push(...plugins);\n }\n\n registerCommands(program, discovered, ctx);\n\n return {\n run: async (argv = process.argv) => {\n try {\n await program.parseAsync(argv);\n return 0;\n } catch (err) {\n ctx.logger.error((err as Error).message);\n return 1;\n }\n },\n };\n};\n","import { Command } from 'commander';\nimport type { CliContext, CommandModule, DiscoveredCommand, OptionSpec } from '../types.js';\n\nconst applyOption = (cmd: Command, spec: OptionSpec): void => {\n if (spec.required) {\n cmd.requiredOption(spec.flags, spec.description, spec.default as string | undefined);\n return;\n }\n if (spec.default !== undefined) {\n cmd.option(spec.flags, spec.description, spec.default as string | boolean);\n } else {\n cmd.option(spec.flags, spec.description);\n }\n};\n\nconst findOrCreateGroup = (parent: Command, name: string): Command => {\n const existing = parent.commands.find(c => c.name() === name);\n if (existing) return existing;\n return parent.command(name).description(`${name} commands`);\n};\n\n// Extract the long-name (or short-name) of a commander flags string and\n// convert kebab-case to camelCase, matching commander's own option key\n// derivation. e.g. `--org-name <name>` → 'orgName'.\nconst deriveOptionKey = (flags: string): string => {\n const tokens = flags.split(/[ ,]+/);\n const long = tokens.find(t => t.startsWith('--'));\n const target = long ?? tokens.find(t => t.startsWith('-'));\n if (!target) return flags;\n const stripped = target.replace(/^-+/, '');\n return stripped.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n};\n\nconst attachLeaf = (parent: Command, leafName: string, mod: CommandModule, ctx: CliContext, sourceLabel: string): void => {\n const cmd = parent.command(leafName).description(mod.description);\n\n for (const arg of mod.args ?? []) {\n const argName = arg.variadic ? `${arg.name}...` : arg.name;\n if (arg.required) cmd.argument(`<${argName}>`, arg.description);\n else cmd.argument(`[${argName}]`, arg.description);\n }\n\n for (const opt of mod.options ?? []) {\n applyOption(cmd, opt);\n }\n\n if (mod.passthrough) cmd.allowUnknownOption(true).allowExcessArguments(true);\n\n cmd.action(async (...allArgs: unknown[]) => {\n // Commander passes positional args first, then the parsed options\n // object, then the Command instance. We slice off the last two.\n const commandInstance = allArgs[allArgs.length - 1] as Command;\n const opts = (allArgs[allArgs.length - 2] ?? {}) as Record<string, unknown>;\n const positional = allArgs.slice(0, allArgs.length - 2);\n\n const positionalStrings: string[] = positional.flatMap(p => (Array.isArray(p) ? p.map(String) : p == null ? [] : [String(p)]));\n const passthroughArgs = mod.passthrough ? commandInstance.args : positionalStrings;\n\n for (const optSpec of mod.options ?? []) {\n if (!optSpec.envVar) continue;\n const key = deriveOptionKey(optSpec.flags);\n if (opts[key] === undefined && process.env[optSpec.envVar] !== undefined) {\n opts[key] = process.env[optSpec.envVar];\n }\n }\n\n let finalOpts = opts;\n if (mod.interactive && ctx.isInteractive()) {\n finalOpts = (await mod.interactive(ctx, opts)) as Record<string, unknown>;\n }\n\n try {\n const exitCode = await mod.run(finalOpts, ctx, passthroughArgs);\n if (typeof exitCode === 'number' && exitCode !== 0) process.exit(exitCode);\n } catch (err) {\n ctx.logger.error(`[${sourceLabel}] ${(err as Error).message}`);\n if ((err as Error).stack) ctx.logger.debug((err as Error).stack ?? '');\n process.exit(1);\n }\n });\n};\n\n/**\n * Attach every discovered command to a commander `Program`, building intermediate\n * group nodes as needed. Core registrations are processed before plugin ones, so\n * a plugin that tries to claim a path already held by core throws with a\n * descriptive error.\n */\nexport const registerCommands = (program: Command, discovered: DiscoveredCommand[], ctx: CliContext): void => {\n const registeredPaths = new Map<string, { source: 'core' | 'plugin'; sourceName?: string }>();\n\n // Core first, then plugins — plugins can extend but not override.\n const sorted = [...discovered].sort((a, b) => {\n if (a.source === b.source) return 0;\n return a.source === 'core' ? -1 : 1;\n });\n\n for (const entry of sorted) {\n const key = entry.path.join(' ');\n const existing = registeredPaths.get(key);\n if (existing) {\n const incoming = entry.source === 'plugin' ? (entry.sourceName ?? 'unknown plugin') : 'core';\n const owner = existing.source === 'plugin' ? (existing.sourceName ?? 'unknown plugin') : 'core';\n throw new Error(`command \"${key}\" is already registered by ${owner}; ${incoming} cannot override it`);\n }\n registeredPaths.set(key, { source: entry.source, sourceName: entry.sourceName });\n\n let parent: Command = program;\n for (const segment of entry.path.slice(0, -1)) {\n parent = findOrCreateGroup(parent, segment);\n }\n const leafName = entry.path[entry.path.length - 1];\n if (!leafName) continue;\n const sourceLabel = entry.source === 'plugin' ? (entry.sourceName ?? 'plugin') : 'core';\n attachLeaf(parent, leafName, entry.module, ctx, sourceLabel);\n }\n};\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { AppConfigBuilder, AppConfigProviderDotenv, type AppConfig } from '@maroonedsoftware/appconfig';\nimport type { CliContext, CliPaths } from './types.js';\nimport type { CliLogger } from './util/logger.js';\nimport { createDefaultLogger } from './util/logger.js';\nimport { createShell } from './util/shell.js';\nimport { isInteractive } from './util/tty.js';\n\n/** Options accepted by `buildContext`. */\nexport interface BuildContextOptions {\n config?: AppConfig;\n logger?: CliLogger;\n verbose?: boolean;\n repoRoot?: string;\n /**\n * Paths to .env files (absolute, or relative to the resolved repoRoot) to\n * load into process.env before building AppConfig. Missing files are\n * silently skipped. Existing process.env values are not overridden.\n * Defaults to ['.env', 'apps/api/.env'].\n */\n envFiles?: string[];\n}\n\nconst findRepoRoot = (start: string): string => {\n let dir = start;\n for (let i = 0; i < 12; i++) {\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return process.cwd();\n};\n\n// Expands `$VAR` and `${VAR}` references against process.env. Matches the\n// behaviour of dotenv-expand so .env files authored for dbmate/docker-compose\n// (where placeholders are common) still produce usable runtime values.\nconst expandValue = (value: string): string =>\n value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}|\\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced: string | undefined, bare: string | undefined) => {\n const key = (braced ?? bare) as string;\n return process.env[key] ?? '';\n });\n\nconst loadEnvFile = (path: string): void => {\n if (!existsSync(path)) return;\n for (const line of readFileSync(path, 'utf-8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eqIdx = trimmed.indexOf('=');\n if (eqIdx === -1) continue;\n const key = trimmed.slice(0, eqIdx).trim();\n const rawValue = trimmed.slice(eqIdx + 1).trim();\n\n // Detect quoting style before unwrapping. Single-quoted values are\n // taken literally; double-quoted and unquoted values get $VAR\n // expansion against the current process.env.\n const singleQuoted = rawValue.startsWith(\"'\") && rawValue.endsWith(\"'\");\n const doubleQuoted = rawValue.startsWith('\"') && rawValue.endsWith('\"');\n let value = singleQuoted || doubleQuoted ? rawValue.slice(1, -1) : rawValue;\n if (!singleQuoted) value = expandValue(value);\n\n if (!(key in process.env)) process.env[key] = value;\n }\n};\n\n/**\n * Build an AppConfig with only the dotenv provider attached. Callers are\n * expected to have loaded .env files into `process.env` beforehand — see\n * `buildContext` for the default loading sequence.\n */\nexport const buildDefaultAppConfig = async (): Promise<AppConfig> =>\n new AppConfigBuilder().addProvider(new AppConfigProviderDotenv()).build();\n\n/**\n * Build the `CliContext` handed to every command, check, and plugin hook. Loads\n * `.env` files into `process.env`, resolves the workspace `repoRoot`, and wires\n * up shell, logger, and config.\n */\nexport const buildContext = async (options: BuildContextOptions = {}): Promise<CliContext> => {\n // Start from cwd so consumers linked from a sibling repo (or installed\n // from npm into node_modules) still resolve to the CONSUMER's workspace\n // root rather than wherever johnny5 itself happens to live.\n const cwd = process.cwd();\n const repoRoot = options.repoRoot ?? findRepoRoot(cwd);\n const paths: CliPaths = { cwd, repoRoot };\n\n for (const envFile of options.envFiles ?? ['.env', 'apps/api/.env']) {\n const absolute = envFile.startsWith('/') ? envFile : resolve(repoRoot, envFile);\n loadEnvFile(absolute);\n }\n\n const logger = options.logger ?? createDefaultLogger({ verbose: options.verbose });\n const shell = createShell(repoRoot, logger);\n const config = options.config ?? (await buildDefaultAppConfig());\n\n return {\n paths,\n logger,\n shell,\n config,\n env: process.env,\n isInteractive,\n };\n};\n","/** Minimal logger interface that every command and check receives via `CliContext`. */\nexport interface CliLogger {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n error: (msg: string) => void;\n debug: (msg: string) => void;\n success: (msg: string) => void;\n}\n\nconst colour = (code: number, text: string): string => `\\x1b[${code}m${text}\\x1b[0m`;\n\n/** Options accepted by `createDefaultLogger`. */\nexport interface CreateLoggerOptions {\n /** When true, `debug` writes to stdout; otherwise it's a no-op. */\n verbose?: boolean;\n}\n\n/**\n * Build the default ANSI-coloured console logger used when a consumer doesn't\n * supply their own. `debug` output is gated on `verbose`.\n */\nexport const createDefaultLogger = (options: CreateLoggerOptions = {}): CliLogger => ({\n info: msg => console.log(msg),\n warn: msg => console.warn(colour(33, `! ${msg}`)),\n error: msg => console.error(colour(31, `✗ ${msg}`)),\n success: msg => console.log(colour(32, `✓ ${msg}`)),\n debug: msg => {\n if (options.verbose) console.log(colour(90, `· ${msg}`));\n },\n});\n","import { execa, type Options as ExecaOptions, type ResultPromise } from 'execa';\nimport type { CliLogger } from './logger.js';\n\n/** Execa options re-typed to require a string `cwd` at the call site. */\nexport interface ShellOptions extends ExecaOptions {\n cwd?: string;\n}\n\n/** Tiny shell wrapper around execa exposed on `CliContext.shell`. */\nexport interface Shell {\n /** Run a command, returning the execa result promise. Use this when the caller needs stdout/stderr. */\n run: (command: string, args: string[], options?: ShellOptions) => ResultPromise;\n /** Run a command with inherited stdio, returning the exit code. Failures don't throw — the exit code is returned instead. */\n runStreaming: (command: string, args: string[], options?: ShellOptions) => Promise<number>;\n}\n\n/** Build a `Shell` bound to `cwd`, logging streaming invocations through `logger.debug`. */\nexport const createShell = (cwd: string, logger: CliLogger): Shell => ({\n run: (command, args, options) => execa(command, args, { cwd, ...options }),\n runStreaming: async (command, args, options) => {\n logger.debug(`$ ${command} ${args.join(' ')}`);\n const child = execa(command, args, { cwd, stdio: 'inherit', reject: false, ...options });\n const result = await child;\n return result.exitCode ?? 0;\n },\n});\n","/**\n * Best-effort guess at whether the CLI is talking to a human. Returns false in\n * CI (`CI=true` / `CI=1`), when `JOHNNY5_NON_INTERACTIVE=1`, or when either of\n * stdout/stdin isn't a TTY.\n */\nexport const isInteractive = (): boolean => {\n if (process.env['CI'] === 'true' || process.env['CI'] === '1') return false;\n if (process.env['JOHNNY5_NON_INTERACTIVE'] === '1') return false;\n return Boolean(process.stdout.isTTY && process.stdin.isTTY);\n};\n","import type { Check, CheckResult, CliContext, CommandModule } from '../types.js';\n\n/** Options passed to `runChecks`. */\nexport interface DoctorOptions {\n /** When true, failing checks with an `autoFix` hook get a chance to remediate. */\n fix?: boolean;\n}\n\n/**\n * Run a list of doctor checks sequentially, rendering progress to stdout. Returns\n * a process exit code: `0` when every check passes (including via `autoFix` when\n * `--fix` is supplied), `1` when at least one check fails.\n */\nexport const runChecks = async (ctx: CliContext, checks: Check[], options: DoctorOptions): Promise<number> => {\n ctx.logger.info('Running doctor…\\n');\n\n let failed = 0;\n let fixed = 0;\n\n for (const check of checks) {\n process.stdout.write(` ${check.name.padEnd(36, ' ')} `);\n let result: CheckResult;\n try {\n result = await check.run(ctx);\n } catch (err) {\n result = { ok: false, message: `threw: ${(err as Error).message}` };\n }\n\n if (result.ok) {\n process.stdout.write(`\\x1b[32m✓\\x1b[0m ${result.message}\\n`);\n continue;\n }\n\n process.stdout.write(`\\x1b[31m✗\\x1b[0m ${result.message}\\n`);\n\n if (options.fix && check.autoFix) {\n process.stdout.write(` ↻ attempting auto-fix… `);\n try {\n const fixResult = await check.autoFix(ctx);\n if (fixResult.ok) {\n process.stdout.write(`\\x1b[32m✓\\x1b[0m ${fixResult.message}\\n`);\n fixed++;\n continue;\n }\n process.stdout.write(`\\x1b[31m✗\\x1b[0m ${fixResult.message}\\n`);\n } catch (err) {\n process.stdout.write(`\\x1b[31m✗\\x1b[0m ${(err as Error).message}\\n`);\n }\n } else if (result.fixHint) {\n process.stdout.write(` → ${result.fixHint}\\n`);\n }\n failed++;\n }\n\n process.stdout.write('\\n');\n if (failed === 0) {\n ctx.logger.success('All checks passed.');\n return 0;\n }\n if (options.fix && fixed > 0) {\n ctx.logger.info(`Auto-fixed ${fixed} issue(s); re-run \\`doctor\\` to confirm.`);\n }\n ctx.logger.error(`${failed} check(s) failed.`);\n return 1;\n};\n\n/**\n * Build the `CommandModule` for the built-in `doctor` subcommand from a set of\n * checks. `createCliApp` auto-registers this when `checks` is non-empty.\n */\nexport const buildDoctorCommand = (checks: Check[]): CommandModule<{ fix?: boolean }> => ({\n description: 'Run local-dev health checks',\n options: [{ flags: '--fix', description: 'Attempt auto-remediation for checks that support it' }],\n run: async (opts, ctx) => runChecks(ctx, checks, { fix: opts.fix === true }),\n});\n","import { existsSync, readFileSync, readdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { CliContext, DiscoveredCommand, PluginManifest } from '../types.js';\n\ninterface WorkspacePackageJson {\n name?: string;\n johnny5?: {\n commands?: string;\n };\n}\n\n/** Options accepted by `loadWorkspacePlugins`. */\nexport interface WorkspacePluginOptions {\n repoRoot: string;\n /**\n * Workspace-relative directories whose immediate children are scanned for\n * `package.json` files. Defaults to `['apps', 'packages']`.\n */\n roots?: string[];\n /**\n * Package names to skip — typically the consumer's own CLI package whose\n * commands are loaded directly, not via plugin discovery.\n */\n excludePackages?: string[];\n}\n\n/**\n * Scan every workspace package in the configured roots for a `\"johnny5\"` field\n * in `package.json`. When present, the referenced file is dynamically imported\n * and expected to default-export a `PluginManifest`. Failures to load a single\n * plugin log a warning through `ctx.logger.warn` but don't abort the CLI.\n */\nexport const loadWorkspacePlugins = async (ctx: CliContext, options: WorkspacePluginOptions): Promise<DiscoveredCommand[]> => {\n const rootDirs = options.roots ?? ['apps', 'packages'];\n const exclude = new Set(options.excludePackages ?? []);\n const discovered: DiscoveredCommand[] = [];\n\n for (const rootRel of rootDirs) {\n const root = resolve(options.repoRoot, rootRel);\n if (!existsSync(root)) continue;\n for (const entry of readdirSync(root, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const pkgPath = join(root, entry.name, 'package.json');\n if (!existsSync(pkgPath)) continue;\n\n let pkg: WorkspacePackageJson;\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as WorkspacePackageJson;\n } catch {\n continue;\n }\n\n const commandsRel = pkg.johnny5?.commands;\n if (!commandsRel) continue;\n if (pkg.name && exclude.has(pkg.name)) continue;\n\n const manifestPath = resolve(root, entry.name, commandsRel);\n if (!existsSync(manifestPath)) {\n ctx.logger.warn(`johnny5 plugin manifest missing for ${pkg.name ?? entry.name}: ${manifestPath}`);\n continue;\n }\n\n try {\n const mod = (await import(pathToFileURL(manifestPath).href)) as { default: PluginManifest };\n const manifest = mod.default;\n if (!manifest || !Array.isArray(manifest.commands)) {\n ctx.logger.warn(`johnny5 plugin ${pkg.name ?? entry.name} has no commands array; skipping`);\n continue;\n }\n for (const cmd of manifest.commands) {\n discovered.push({\n path: cmd.path,\n source: 'plugin',\n sourceName: manifest.name ?? pkg.name,\n module: cmd.module,\n });\n }\n } catch (err) {\n ctx.logger.warn(`johnny5 plugin ${pkg.name ?? entry.name} failed to load: ${(err as Error).message}`);\n }\n }\n }\n\n return discovered;\n};\n","import * as clack from '@clack/prompts';\n\n/** Re-export of the `@clack/prompts` namespace under a stable name. */\nexport const prompts = clack;\n\n/** Thrown by `unwrap` when the user cancels a clack prompt (e.g. Ctrl+C). */\nexport class PromptCancelledError extends Error {\n constructor() {\n super('prompt cancelled');\n this.name = 'PromptCancelledError';\n }\n}\n\n/**\n * Unwrap a clack prompt result, throwing `PromptCancelledError` when the user\n * cancelled. Lets command handlers use try/catch instead of branching on\n * `isCancel` at every prompt.\n */\nexport const unwrap = <T>(value: T | symbol): T => {\n if (clack.isCancel(value)) throw new PromptCancelledError();\n return value as T;\n};\n"],"mappings":"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","names":["InjectKitRegistry","AppConfig","ConsoleLogger","Logger","bootstrapForCli","STATE_KEY","getState","configureServerKitModules","getOrBootstrapContainer","requireContainer","options","registry","register","useInstance","logger","config","module","modules","setup","container","build","shutdown","reverse","Symbol","for","g","globalThis","containerByContext","WeakMap","ctx","set","lazy","get","Error","promise","handler","opts","args","scoped","createScopedContainer","enriched","Object","assign","Command","applyOption","cmd","spec","required","requiredOption","flags","description","default","undefined","option","findOrCreateGroup","parent","name","existing","commands","find","c","command","deriveOptionKey","tokens","split","long","t","startsWith","target","stripped","replace","_","toUpperCase","attachLeaf","leafName","mod","ctx","sourceLabel","arg","args","argName","variadic","argument","opt","options","passthrough","allowUnknownOption","allowExcessArguments","action","allArgs","commandInstance","length","opts","positional","slice","positionalStrings","flatMap","p","Array","isArray","map","String","passthroughArgs","optSpec","envVar","key","process","env","finalOpts","interactive","isInteractive","exitCode","run","exit","err","logger","error","message","stack","debug","registerCommands","program","discovered","registeredPaths","Map","sorted","sort","a","b","source","entry","path","join","get","incoming","sourceName","owner","Error","set","segment","module","existsSync","readFileSync","dirname","resolve","AppConfigBuilder","AppConfigProviderDotenv","colour","code","text","createDefaultLogger","options","info","msg","console","log","warn","error","success","debug","verbose","execa","createShell","cwd","logger","run","command","args","options","execa","runStreaming","debug","join","child","stdio","reject","result","exitCode","isInteractive","process","env","Boolean","stdout","isTTY","stdin","findRepoRoot","start","dir","i","existsSync","resolve","parent","dirname","process","cwd","expandValue","value","replace","_","braced","bare","key","env","loadEnvFile","path","line","readFileSync","split","trimmed","trim","startsWith","eqIdx","indexOf","slice","rawValue","singleQuoted","endsWith","doubleQuoted","buildDefaultAppConfig","AppConfigBuilder","addProvider","AppConfigProviderDotenv","build","buildContext","options","repoRoot","paths","envFile","envFiles","absolute","logger","createDefaultLogger","verbose","shell","createShell","config","isInteractive","runChecks","ctx","checks","options","logger","info","failed","fixed","check","process","stdout","write","name","padEnd","result","run","err","ok","message","fix","autoFix","fixResult","fixHint","success","error","buildDoctorCommand","description","flags","opts","existsSync","readFileSync","readdirSync","join","resolve","pathToFileURL","loadWorkspacePlugins","ctx","options","rootDirs","roots","exclude","Set","excludePackages","discovered","rootRel","root","resolve","repoRoot","existsSync","entry","readdirSync","withFileTypes","isDirectory","pkgPath","join","name","pkg","JSON","parse","readFileSync","commandsRel","johnny5","commands","has","manifestPath","logger","warn","mod","pathToFileURL","href","manifest","default","Array","isArray","cmd","push","path","source","sourceName","module","err","message","defineCommand","mod","createCliApp","options","verbose","process","argv","includes","resolvedConfig","config","ctx","buildContext","logger","modules","length","configureServerKitModules","program","Command","name","description","version","option","discovered","commands","map","c","source","checks","doctorCommandPath","doctorPath","alreadyDefined","some","path","join","push","module","buildDoctorCommand","plugins","workspace","workspaceOpts","repoRoot","paths","loadWorkspacePlugins","registerCommands","run","parseAsync","err","error","message","clack","prompts","clack","PromptCancelledError","Error","name","unwrap","value","isCancel"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { b as Check } from '../../types-CH7ccr3j.js';
|
|
2
|
+
import '@maroonedsoftware/appconfig';
|
|
3
|
+
import 'execa';
|
|
4
|
+
|
|
5
|
+
/** Options for `dockerServicesUp`. */
|
|
6
|
+
interface DockerServicesOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Path to `docker-compose.yml`. Relative paths are resolved against the
|
|
9
|
+
* repo root. Defaults to `'docker-compose.yml'`.
|
|
10
|
+
*/
|
|
11
|
+
composeFile?: string;
|
|
12
|
+
/**
|
|
13
|
+
* When true (default), a missing compose file is treated as a passing
|
|
14
|
+
* check with a skip message. When false, it's a failure.
|
|
15
|
+
*/
|
|
16
|
+
skipIfMissing?: boolean;
|
|
17
|
+
/** When true, the check attaches an `autoFix` that runs `docker compose up -d`. */
|
|
18
|
+
autoStart?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check that `docker compose ps` reports every service in the running state.
|
|
22
|
+
* Parses both JSON-array and NDJSON output formats. When `autoStart` is set the
|
|
23
|
+
* returned check exposes an `autoFix` that runs `docker compose up -d`.
|
|
24
|
+
*/
|
|
25
|
+
declare const dockerServicesUp: (options?: DockerServicesOptions) => Check;
|
|
26
|
+
|
|
27
|
+
export { type DockerServicesOptions, dockerServicesUp };
|