kalup 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.
Files changed (145) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +68 -0
  4. package/bin/kalup.mjs +13 -0
  5. package/dist/commands-C--Xsk1p.mjs +16145 -0
  6. package/dist/commands.d.mts +191 -0
  7. package/dist/commands.mjs +2 -0
  8. package/dist/context-D10Syqfd.d.mts +1265 -0
  9. package/dist/host-BJWvo3sX.mjs +390 -0
  10. package/dist/host.d.mts +44 -0
  11. package/dist/host.mjs +2 -0
  12. package/dist/index.d.mts +1 -0
  13. package/dist/index.mjs +14 -0
  14. package/dist/schemas/blueprint-1.schema.json +151 -0
  15. package/dist/schemas/blueprints-lock-1.schema.json +76 -0
  16. package/dist/schemas/ir-1.schema.json +379 -0
  17. package/dist/schemas/plan-1.schema.json +868 -0
  18. package/dist/schemas/state-1.schema.json +98 -0
  19. package/docs/apply.md +64 -0
  20. package/docs/blueprints.md +86 -0
  21. package/docs/compare.md +73 -0
  22. package/docs/config.md +88 -0
  23. package/docs/dictionary.md +43 -0
  24. package/docs/errors/E_ACCEPT_UNMATCHED.md +17 -0
  25. package/docs/errors/E_APPROVAL_REQUIRED.md +17 -0
  26. package/docs/errors/E_APPROVE_CREDENTIAL.md +17 -0
  27. package/docs/errors/E_APPROVE_MISMATCH.md +17 -0
  28. package/docs/errors/E_AUTH.md +17 -0
  29. package/docs/errors/E_BAD_CHAIN.md +21 -0
  30. package/docs/errors/E_BINDING_CHANGED.md +17 -0
  31. package/docs/errors/E_BIOME_CONFIG.md +17 -0
  32. package/docs/errors/E_BLUEPRINT_ADDED.md +17 -0
  33. package/docs/errors/E_BLUEPRINT_COLLISION.md +17 -0
  34. package/docs/errors/E_BLUEPRINT_INTEGRITY.md +17 -0
  35. package/docs/errors/E_BLUEPRINT_LOCK.md +17 -0
  36. package/docs/errors/E_BLUEPRINT_ORIGINAL.md +17 -0
  37. package/docs/errors/E_BLUEPRINT_REF.md +17 -0
  38. package/docs/errors/E_BLUEPRINT_REQUIRES.md +17 -0
  39. package/docs/errors/E_BLUEPRINT_SCHEMA.md +17 -0
  40. package/docs/errors/E_BLUEPRINT_SOURCE.md +17 -0
  41. package/docs/errors/E_BLUEPRINT_UNKNOWN.md +17 -0
  42. package/docs/errors/E_BUDGET.md +17 -0
  43. package/docs/errors/E_CANCELLED.md +21 -0
  44. package/docs/errors/E_CONFIG_EXISTS.md +17 -0
  45. package/docs/errors/E_DAILY_LIMIT.md +17 -0
  46. package/docs/errors/E_DEFAULT_TARGET.md +17 -0
  47. package/docs/errors/E_DUPLICATE_ADDRESS.md +17 -0
  48. package/docs/errors/E_DUPLICATE_ALIAS.md +24 -0
  49. package/docs/errors/E_DUPLICATE_KEY.md +24 -0
  50. package/docs/errors/E_DUPLICATE_OPTION.md +25 -0
  51. package/docs/errors/E_DUPLICATE_PORTAL.md +24 -0
  52. package/docs/errors/E_FIRST_PULL.md +18 -0
  53. package/docs/errors/E_HS_PREFIX.md +21 -0
  54. package/docs/errors/E_HTTP.md +17 -0
  55. package/docs/errors/E_INCOMPLETE.md +23 -0
  56. package/docs/errors/E_IR_SCHEMA.md +18 -0
  57. package/docs/errors/E_JOURNAL_WRITE.md +17 -0
  58. package/docs/errors/E_KEY_COLLISION.md +22 -0
  59. package/docs/errors/E_KEY_INVALID.md +19 -0
  60. package/docs/errors/E_LIFECYCLE.md +23 -0
  61. package/docs/errors/E_LOCKED.md +17 -0
  62. package/docs/errors/E_LOCK_DIR.md +17 -0
  63. package/docs/errors/E_MISSING_EXPORT.md +17 -0
  64. package/docs/errors/E_MISSING_KEY.md +17 -0
  65. package/docs/errors/E_NOT_DATA.md +21 -0
  66. package/docs/errors/E_NO_CONFIG.md +17 -0
  67. package/docs/errors/E_NO_TARGETS.md +17 -0
  68. package/docs/errors/E_OVERRIDE_AMBIGUOUS.md +21 -0
  69. package/docs/errors/E_OVERRIDE_DEFINITION.md +26 -0
  70. package/docs/errors/E_OVERRIDE_NAME.md +23 -0
  71. package/docs/errors/E_PLAN_DELETE.md +17 -0
  72. package/docs/errors/E_PLAN_DESTINATION.md +17 -0
  73. package/docs/errors/E_PLAN_DIGEST.md +17 -0
  74. package/docs/errors/E_PLAN_INVALID.md +17 -0
  75. package/docs/errors/E_PLAN_RISK.md +17 -0
  76. package/docs/errors/E_PLAN_SCHEMA.md +17 -0
  77. package/docs/errors/E_PLAN_STALE.md +17 -0
  78. package/docs/errors/E_PLAN_VERSION.md +18 -0
  79. package/docs/errors/E_POLICY_CHANGED.md +17 -0
  80. package/docs/errors/E_PORTAL_ID.md +17 -0
  81. package/docs/errors/E_PREVENT_DESTROY.md +17 -0
  82. package/docs/errors/E_PROJECT_WRITE.md +17 -0
  83. package/docs/errors/E_PROTECTED_SAVED_PLAN.md +17 -0
  84. package/docs/errors/E_PULL_INVALID.md +20 -0
  85. package/docs/errors/E_RATE_LIMIT.md +17 -0
  86. package/docs/errors/E_REBIND_STANDARD.md +17 -0
  87. package/docs/errors/E_REFERENCE_DEFINITION.md +21 -0
  88. package/docs/errors/E_RM_DEPENDENTS.md +17 -0
  89. package/docs/errors/E_SCOPE.md +17 -0
  90. package/docs/errors/E_SETTING_LEVEL.md +21 -0
  91. package/docs/errors/E_SETTING_VALUE.md +23 -0
  92. package/docs/errors/E_SNAPSHOT.md +17 -0
  93. package/docs/errors/E_STANDARD_OBJECT.md +21 -0
  94. package/docs/errors/E_STATE_CHANGED.md +19 -0
  95. package/docs/errors/E_STATE_CONFLICT.md +17 -0
  96. package/docs/errors/E_STATE_INVALID.md +17 -0
  97. package/docs/errors/E_STATE_SCHEMA.md +17 -0
  98. package/docs/errors/E_STATE_WRITE.md +19 -0
  99. package/docs/errors/E_STRICT_WITHOUT_OPTIONS.md +21 -0
  100. package/docs/errors/E_TAKE_UNMATCHED.md +19 -0
  101. package/docs/errors/E_TARGET_NAME.md +17 -0
  102. package/docs/errors/E_TARGET_PORTAL_MISMATCH.md +19 -0
  103. package/docs/errors/E_TARGET_REQUIRED.md +17 -0
  104. package/docs/errors/E_TOMBSTONE_ADDRESS.md +23 -0
  105. package/docs/errors/E_TOMBSTONE_CONFLICT.md +17 -0
  106. package/docs/errors/E_TYPE_FIELDTYPE.md +21 -0
  107. package/docs/errors/E_UNCERTAIN_WRITE.md +17 -0
  108. package/docs/errors/E_UNEXPECTED.md +17 -0
  109. package/docs/errors/E_UNKNOWN_BUILDER.md +21 -0
  110. package/docs/errors/E_UNKNOWN_GROUP.md +21 -0
  111. package/docs/errors/E_UNKNOWN_INCLUDE.md +17 -0
  112. package/docs/errors/E_UNKNOWN_OBJECT.md +17 -0
  113. package/docs/errors/E_UNKNOWN_OVERRIDE.md +17 -0
  114. package/docs/errors/E_UNKNOWN_TARGET.md +17 -0
  115. package/docs/errors/E_UNREACHABLE.md +17 -0
  116. package/docs/errors/E_UNSUPPORTED_FILE.md +17 -0
  117. package/docs/errors/E_USAGE.md +17 -0
  118. package/docs/errors/E_WRITE_IN_READ_MODE.md +17 -0
  119. package/docs/errors/E_WRITE_NOT_ALLOWED.md +17 -0
  120. package/docs/errors/W_BLUEPRINT_DOWNGRADE.md +17 -0
  121. package/docs/errors/W_CODEC_MISMATCH.md +18 -0
  122. package/docs/errors/W_INCOMPLETE.md +19 -0
  123. package/docs/errors/W_JSON_FIELDTYPE.md +17 -0
  124. package/docs/errors/W_KEY_COLLISION.md +17 -0
  125. package/docs/errors/W_LARGE_SCOPE.md +23 -0
  126. package/docs/errors/W_LIMIT_HEADROOM.md +19 -0
  127. package/docs/errors/W_LIMIT_UNREADABLE.md +17 -0
  128. package/docs/errors/W_MODE_SHADOWED.md +17 -0
  129. package/docs/errors/W_OVERRIDE_OPTION.md +17 -0
  130. package/docs/errors/W_PIN_EXPIRES.md +17 -0
  131. package/docs/errors/W_PREFIX.md +17 -0
  132. package/docs/errors/W_RATE_HEADERS.md +21 -0
  133. package/docs/errors/W_RATE_LIMIT.md +17 -0
  134. package/docs/errors/W_UNADDRESSABLE_NAME.md +21 -0
  135. package/docs/errors/W_UNFINISHED_APPLY.md +19 -0
  136. package/docs/errors/W_UNRESOLVED.md +17 -0
  137. package/docs/errors/W_UNSUPPORTED_TYPE.md +19 -0
  138. package/docs/errors/W_UNVERIFIED.md +17 -0
  139. package/docs/plan.md +85 -0
  140. package/docs/pull.md +83 -0
  141. package/docs/rm.md +44 -0
  142. package/docs/snapshot.md +52 -0
  143. package/docs/state.md +37 -0
  144. package/docs/targets.md +58 -0
  145. package/package.json +86 -0
@@ -0,0 +1,390 @@
1
+ import { A as sanitize, C as usageError, D as disclaimer, E as bin, O as escapeJson, S as versionText, T as KalupError, b as formats, k as exitCodes, u as KalupCommand, w as IssueError, x as version, y as PATH_MAX } from "./commands-C--Xsk1p.mjs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Config, Errors, Help } from "@oclif/core";
6
+ import { createInterface } from "node:readline/promises";
7
+ import { Writable } from "node:stream";
8
+ //#region src/lib/output.ts
9
+ /** Builds an envelope/1 document. `data` is left out when undefined. */
10
+ function envelope(ok, data, issues = []) {
11
+ return {
12
+ format: "envelope/1",
13
+ ok,
14
+ ...data === void 0 ? {} : { data },
15
+ issues
16
+ };
17
+ }
18
+ /** Prints one envelope/1 document, the only thing `--json` writes to stdout. Keys stay in envelope order. */
19
+ function printEnvelope(env, out = process.stdout) {
20
+ out.write(`${escapeJson(JSON.stringify(env, null, 2))}\n`);
21
+ }
22
+ //#endregion
23
+ //#region src/host/prompt.ts
24
+ const ATTEMPTS = 3;
25
+ const NUMBER = /^\d+$/;
26
+ /** How much of an unrecognised answer the retry line quotes back. */
27
+ const ANSWER_MAX = 40;
28
+ /** Room for a name sanitize capped at 120 characters and the portal ID the target selector puts after it. */
29
+ const LABEL_MAX = 160;
30
+ /** Room for a line of a summary shown before a question: a step title with its risk and labels. */
31
+ const LINE_MAX = 400;
32
+ /**
33
+ * A Prompter reading answers from `input` and writing the questions, the choices, any summary and any retry line to
34
+ * `output`. Lines read ahead of a question, such as a second answer piped in with the first, wait for the next one.
35
+ */
36
+ function createPrompter(input, output) {
37
+ const lines = lineQueue(input, output);
38
+ return {
39
+ choose(question, choices) {
40
+ return lines.session(async (next) => {
41
+ const ask = `Enter 1-${choices.length} or a name: `;
42
+ output.write(`${sanitize(question)}\n`);
43
+ choices.forEach((choice, i) => {
44
+ output.write(` ${i + 1}) ${sanitize(choice.label, LABEL_MAX)}\n`);
45
+ });
46
+ const attempt = async (left) => {
47
+ const answer = left > 0 ? await next(ask) : void 0;
48
+ if (answer === void 0) return;
49
+ const picked = pick(answer, choices);
50
+ if (picked !== void 0) return picked;
51
+ output.write(`'${sanitize(answer, ANSWER_MAX)}' is not one of the ${choices.length} choices.\n`);
52
+ return await attempt(left - 1);
53
+ };
54
+ return await attempt(ATTEMPTS);
55
+ });
56
+ },
57
+ ask(question) {
58
+ return lines.session((next) => next(`${sanitize(question)} `));
59
+ },
60
+ tell(text) {
61
+ for (const line of text) output.write(`${sanitize(line, LINE_MAX)}\n`);
62
+ }
63
+ };
64
+ }
65
+ function lineQueue(input, output) {
66
+ const queued = [];
67
+ let ended = false;
68
+ async function session(ask) {
69
+ ended ||= input.readableEnded === true;
70
+ if (ended) return ask((prompt) => {
71
+ output.write(prompt);
72
+ return Promise.resolve(take());
73
+ });
74
+ const terminal = input.isTTY === true;
75
+ const rl = createInterface({
76
+ input,
77
+ output: toStream(output),
78
+ terminal
79
+ });
80
+ const waiting = [];
81
+ let closing = false;
82
+ let closed = false;
83
+ rl.on("line", (line) => {
84
+ const waiter = waiting.shift();
85
+ if (waiter) waiter(line);
86
+ else queued.push(line);
87
+ });
88
+ rl.once("close", () => {
89
+ closed = true;
90
+ ended ||= !closing;
91
+ for (const waiter of waiting.splice(0)) {
92
+ output.write("\n");
93
+ waiter(void 0);
94
+ }
95
+ });
96
+ rl.on("SIGINT", () => rl.close());
97
+ const next = (prompt) => {
98
+ if (closed) output.write(prompt);
99
+ else {
100
+ rl.setPrompt(prompt);
101
+ rl.prompt();
102
+ }
103
+ if (queued.length > 0 || closed) return Promise.resolve(take());
104
+ return new Promise((resolve) => waiting.push(resolve));
105
+ };
106
+ try {
107
+ return await ask(next);
108
+ } finally {
109
+ closing = true;
110
+ rl.close();
111
+ }
112
+ }
113
+ function take() {
114
+ const line = queued.shift();
115
+ if (line === void 0) output.write("\n");
116
+ return line;
117
+ }
118
+ return { session };
119
+ }
120
+ function pick(answer, choices) {
121
+ const trimmed = answer.trim();
122
+ const n = NUMBER.test(trimmed) ? Number(trimmed) : 0;
123
+ if (n >= 1 && n <= choices.length) return choices[n - 1]?.value;
124
+ return choices.find((choice) => choice.value === answer || choice.value === trimmed)?.value;
125
+ }
126
+ function toStream(out) {
127
+ if (out instanceof Writable) return out;
128
+ return new Writable({ write(chunk, _encoding, callback) {
129
+ out.write(chunk.toString());
130
+ callback();
131
+ } });
132
+ }
133
+ //#endregion
134
+ //#region src/host/host.ts
135
+ const HELP_WIDTH = 100;
136
+ const PROGRESS_MAX = 200;
137
+ /** A person is at a terminal when stdin and stderr are terminals and CI is not set. The entry passes the process's. */
138
+ function isInteractive(stdin, stderr, env) {
139
+ return stdin.isTTY === true && stderr.isTTY === true && !env.CI;
140
+ }
141
+ /** Runs one invocation and returns its exit code. Only the executable entry sets process.exitCode. */
142
+ async function run(argv, io) {
143
+ const json = argv.includes("--json");
144
+ const invocation = {};
145
+ const prompt = io.interactive === true && !json && io.stdin ? createPrompter(io.stdin, io.stderr) : void 0;
146
+ try {
147
+ const result = await dispatch(argv, io.cwd, invocation, prompt, io);
148
+ const exitCode = result.exitCode ?? exitCodes.done;
149
+ const issues = withDocs(result.issues ?? []);
150
+ if (json) printEnvelope(envelope(exitCode === exitCodes.done || exitCode === exitCodes.differences, result.data, issues), io.stdout);
151
+ else {
152
+ if (result.text) io.stdout.write(result.text);
153
+ printLines(issues, io.stderr);
154
+ }
155
+ return exitCode;
156
+ } catch (error) {
157
+ const failed = failure(error);
158
+ const issues = withDocs(failed.issues);
159
+ if (json) printEnvelope(envelope(false, void 0, issues), io.stdout);
160
+ else {
161
+ printLines(issues, io.stderr);
162
+ if (invocation.config && issues.some((issue) => issue.code === "E_USAGE")) io.stderr.write(`\n${await helpText(invocation.config, invocation.id)}\n`);
163
+ }
164
+ return failed.exitCode;
165
+ }
166
+ }
167
+ /**
168
+ * Parses and runs one invocation and returns its result without printing anything. Errors are thrown as they
169
+ * happen; `run` turns them into issues and an exit code. Without `prompt` nothing asks, as in a run without a terminal.
170
+ */
171
+ function execute(argv, cwd, prompt) {
172
+ return dispatch(argv, cwd, {}, prompt);
173
+ }
174
+ async function dispatch(argv, cwd, invocation, prompt, io) {
175
+ const config = await loadConfig();
176
+ invocation.config = config;
177
+ const { id, rest } = splitCommand(argv, config);
178
+ if (argv.includes("--version")) return {
179
+ data: {
180
+ name: bin,
181
+ version,
182
+ disclaimer,
183
+ formats: [...formats]
184
+ },
185
+ text: versionText()
186
+ };
187
+ const topic = id !== void 0 && !config.findCommand(id) && isTopic(config, id);
188
+ invocation.id = id !== void 0 && (config.findCommand(id) || topic) ? id : void 0;
189
+ if (id === void 0 || topic || argv.includes("--help") || argv.includes("-h")) {
190
+ const usage = await helpText(config, invocation.id);
191
+ return {
192
+ data: { usage },
193
+ text: `${usage}\n`
194
+ };
195
+ }
196
+ const found = config.findCommand(id);
197
+ if (!found) throw usageError(`unknown command '${id}'`);
198
+ const Found = await found.load();
199
+ const command = new Found(rest, config);
200
+ if (!(command instanceof KalupCommand)) throw new Error(`command ${id} is not a Kalup command`);
201
+ command.cwd = cwd;
202
+ command.prompt = prompt;
203
+ const stderr = io?.stderr;
204
+ command.progress = stderr && !argv.includes("--json") ? (line) => stderr.write(`${sanitize(line, PROGRESS_MAX)}\n`) : void 0;
205
+ const interrupts = Found.interruptible === true && io ? watch(io) : void 0;
206
+ command.signal = interrupts?.signal;
207
+ try {
208
+ return await command.run();
209
+ } catch (error) {
210
+ throw fromParser(error);
211
+ } finally {
212
+ interrupts?.dispose();
213
+ }
214
+ }
215
+ /** Exit code of a second signal: the run stopped mid-way, and a write may have landed. */
216
+ const HARD_STOP = exitCodes.partial;
217
+ /**
218
+ * A second signal this soon after the first is the same interrupt: under npx, npm forwards the terminal's SIGINT to
219
+ * its child, which the terminal already sent to the whole process group, so one Ctrl-C arrives twice.
220
+ */
221
+ const SAME_INTERRUPT_MS = 1e3;
222
+ function watch(io) {
223
+ const { signals } = io;
224
+ if (!signals) return;
225
+ const controller = new AbortController();
226
+ let first;
227
+ const listener = () => {
228
+ if (first === void 0) {
229
+ first = Date.now();
230
+ controller.abort();
231
+ io.stderr.write("Stopping after the request in flight. Send the signal again to stop at once.\n");
232
+ return;
233
+ }
234
+ if (Date.now() - first >= SAME_INTERRUPT_MS) io.exit?.(HARD_STOP);
235
+ };
236
+ signals.on("SIGINT", listener);
237
+ signals.on("SIGTERM", listener);
238
+ return {
239
+ signal: controller.signal,
240
+ dispose: () => {
241
+ signals.off("SIGINT", listener);
242
+ signals.off("SIGTERM", listener);
243
+ }
244
+ };
245
+ }
246
+ let configLoad;
247
+ function loadConfig() {
248
+ configLoad ??= Config.load({
249
+ root: packageRoot(),
250
+ userPlugins: false,
251
+ devPlugins: false,
252
+ jitPlugins: false
253
+ });
254
+ return configLoad;
255
+ }
256
+ function packageRoot() {
257
+ let dir = dirname(fileURLToPath(import.meta.url));
258
+ while (dirname(dir) !== dir) {
259
+ const file = join(dir, "package.json");
260
+ if (existsSync(file) && JSON.parse(readFileSync(file, "utf8")).name === "kalup") return dir;
261
+ dir = dirname(dir);
262
+ }
263
+ throw new Error("the kalup package.json was not found above the host module");
264
+ }
265
+ const ROOT_FLAGS = /* @__PURE__ */ new Set([
266
+ "--json",
267
+ "--help",
268
+ "-h",
269
+ "--version"
270
+ ]);
271
+ /**
272
+ * The command id is the first argument that is neither a flag nor a flag's value, so flags may come before or
273
+ * after it. A flag takes a value when any command declares it as an option; `--flag=value` carries its own.
274
+ * Without a command only the root flags are accepted: anything else, even beside --help or --version, is E_USAGE.
275
+ */
276
+ function splitCommand(argv, config) {
277
+ const flags = config.commands.flatMap((command) => Object.entries(command.flags));
278
+ const valued = new Set(flags.filter(([, flag]) => flag.type === "option").map(([name]) => `--${name}`));
279
+ for (let i = 0; i < argv.length; i += 1) {
280
+ const arg = argv[i];
281
+ if (arg.startsWith("-")) {
282
+ if (valued.has(arg)) i += 1;
283
+ continue;
284
+ }
285
+ const next = isTopic(config, arg) ? subcommand(argv, i + 1, valued) : void 0;
286
+ const id = next === void 0 ? void 0 : `${arg}:${argv[next]}`;
287
+ if (next !== void 0 && id !== void 0) {
288
+ if (!config.findCommand(id)) throw usageError(`unknown command '${arg} ${argv[next]}'`);
289
+ return {
290
+ id,
291
+ rest: argv.filter((_, at) => at !== i && at !== next)
292
+ };
293
+ }
294
+ return {
295
+ id: arg,
296
+ rest: [...argv.slice(0, i), ...argv.slice(i + 1)]
297
+ };
298
+ }
299
+ const stray = argv.find((arg) => !ROOT_FLAGS.has(arg));
300
+ if (stray !== void 0) {
301
+ const [name] = stray.split("=");
302
+ throw usageError(flags.some(([declared]) => `--${declared}` === name) ? `${name} needs a command` : `unknown flag ${stray}`);
303
+ }
304
+ return { rest: argv };
305
+ }
306
+ function isTopic(config, id) {
307
+ return config.commands.some((command) => command.id.startsWith(`${id}:`));
308
+ }
309
+ function subcommand(argv, from, valued) {
310
+ for (let i = from; i < argv.length; i += 1) {
311
+ const arg = argv[i];
312
+ if (!arg.startsWith("-")) return i;
313
+ if (valued.has(arg)) i += 1;
314
+ }
315
+ }
316
+ /** The help for one command, or for the whole CLI, as oclif renders it, plain text, with the disclaimer. */
317
+ async function helpText(config, id) {
318
+ const help = new CapturedHelp(config, {
319
+ maxWidth: HELP_WIDTH,
320
+ stripAnsi: true
321
+ });
322
+ const ids = config.commands.map((command) => [
323
+ command,
324
+ command.id,
325
+ command.aliases
326
+ ]);
327
+ try {
328
+ await help.showHelp(id === void 0 ? [] : [id]);
329
+ } finally {
330
+ for (const [command, original, aliases] of ids) {
331
+ command.id = original;
332
+ command.aliases = aliases;
333
+ }
334
+ }
335
+ return `${help.lines.join("\n").trimEnd()}\n\n${disclaimer}`;
336
+ }
337
+ var CapturedHelp = class extends Help {
338
+ lines = [];
339
+ log(...args) {
340
+ this.lines.push(args.join(" "));
341
+ }
342
+ };
343
+ function fromParser(error) {
344
+ if (!(error instanceof Errors.CLIError) || error.oclif.exit !== 2) return error;
345
+ const { flags, args } = error;
346
+ if (Array.isArray(flags) && typeof flags[0] === "string") return usageError(`unknown flag ${flags[0]}`);
347
+ if (Array.isArray(args) && typeof args[0] === "string") return usageError(`unexpected argument '${args[0]}'`);
348
+ if (Array.isArray(args) && args.length > 0) {
349
+ const names = args.map((arg) => arg.name.toUpperCase());
350
+ return usageError(`missing argument${names.length > 1 ? "s" : ""} ${names.join(", ")}`);
351
+ }
352
+ return usageError(sanitize(error.message.split("\n")[0] ?? ""));
353
+ }
354
+ function failure(error) {
355
+ if (error instanceof KalupError) return error;
356
+ if (error instanceof IssueError) return {
357
+ issues: error.issues,
358
+ exitCode: exitCodes.invalid
359
+ };
360
+ const message = error instanceof Error ? error.message : String(error);
361
+ return {
362
+ issues: [{
363
+ code: "E_UNEXPECTED",
364
+ message: sanitize(message.split("\n")[0] ?? "")
365
+ }],
366
+ exitCode: exitCodes.error
367
+ };
368
+ }
369
+ /** Points each issue at its page in the shipped docs folder. test/docs.test.ts checks every code has one. */
370
+ function withDocs(issues) {
371
+ return issues.map((issue) => ({
372
+ ...issue,
373
+ docs: issue.docs ?? `errors/${issue.code}.md`
374
+ }));
375
+ }
376
+ /**
377
+ * One line per issue: `file:line: CODE: message (fix: ...) (docs: ...)`. A file named on the command line is text, and
378
+ * a message or fix can quote kalup.config.ts (a target name, defaultTarget), so each is sanitized, never shortened.
379
+ */
380
+ function formatIssue(issue) {
381
+ const where = issue.file ? `${sanitize(issue.file, PATH_MAX)}${issue.line === void 0 ? "" : `:${issue.line}`}: ` : "";
382
+ const fix = issue.fix ? ` (fix: ${sanitize(issue.fix, Number.POSITIVE_INFINITY)})` : "";
383
+ const docs = issue.docs ? ` (docs: ${issue.docs})` : "";
384
+ return `${where}${issue.code}: ${sanitize(issue.message, Number.POSITIVE_INFINITY)}${fix}${docs}`;
385
+ }
386
+ function printLines(issues, out) {
387
+ if (issues.length > 0) out.write(`${issues.map(formatIssue).join("\n")}\n`);
388
+ }
389
+ //#endregion
390
+ export { isInteractive as n, run as r, execute as t };
@@ -0,0 +1,44 @@
1
+ import { i as ExitCode, n as Prompter, r as Result } from "./context-D10Syqfd.mjs";
2
+ //#region src/host/host.d.ts
3
+ interface Out {
4
+ write: (text: string) => unknown;
5
+ }
6
+ type Signal = 'SIGINT' | 'SIGTERM';
7
+ /** Where SIGINT and SIGTERM arrive: the process, or a test's emitter. */
8
+ export interface Signals {
9
+ off: (signal: Signal, listener: () => void) => unknown;
10
+ on: (signal: Signal, listener: () => void) => unknown;
11
+ }
12
+ export interface Io {
13
+ cwd: string;
14
+ /** Ends the process at once, on a second signal during an interruptible command. */
15
+ exit?: (code: number) => void;
16
+ /**
17
+ * Whether a person is at a terminal: stdin and stderr are terminals and CI is not set. The entry decides it from the
18
+ * process; a command prompts only when this is true, stdin is given and --json is absent.
19
+ */
20
+ interactive?: boolean;
21
+ /**
22
+ * The process's signals. While an interruptible command runs (apply), the first SIGINT or SIGTERM aborts its signal,
23
+ * so it stops before its next request, and a second one a second or more later exits at once.
24
+ */
25
+ signals?: Signals;
26
+ stderr: Out;
27
+ /** Where a prompt reads its answer. */
28
+ stdin?: NodeJS.ReadableStream;
29
+ stdout: Out;
30
+ }
31
+ /** A person is at a terminal when stdin and stderr are terminals and CI is not set. The entry passes the process's. */
32
+ export declare function isInteractive(stdin: {
33
+ isTTY?: boolean;
34
+ }, stderr: {
35
+ isTTY?: boolean;
36
+ }, env: NodeJS.ProcessEnv): boolean;
37
+ /** Runs one invocation and returns its exit code. Only the executable entry sets process.exitCode. */
38
+ export declare function run(argv: string[], io: Io): Promise<ExitCode>;
39
+ /**
40
+ * Parses and runs one invocation and returns its result without printing anything. Errors are thrown as they
41
+ * happen; `run` turns them into issues and an exit code. Without `prompt` nothing asks, as in a run without a terminal.
42
+ */
43
+ export declare function execute(argv: string[], cwd: string, prompt?: Prompter): Promise<Result>;
44
+ //#endregion
package/dist/host.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { n as isInteractive, r as run, t as execute } from "./host-BJWvo3sX.mjs";
2
+ export { execute, isInteractive, run };
@@ -0,0 +1 @@
1
+ export {}
package/dist/index.mjs ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { n as isInteractive, r as run } from "./host-BJWvo3sX.mjs";
3
+ //#region src/index.ts
4
+ process.exitCode = await run(process.argv.slice(2), {
5
+ cwd: process.cwd(),
6
+ stdin: process.stdin,
7
+ interactive: isInteractive(process.stdin, process.stderr, process.env),
8
+ signals: process,
9
+ exit: (code) => process.exit(code),
10
+ stdout: process.stdout,
11
+ stderr: process.stderr
12
+ });
13
+ //#endregion
14
+ export {};
@@ -0,0 +1,151 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kalup.dev/schemas/blueprint-1.schema.json",
4
+ "title": "Kalup blueprint, version 1",
5
+ "description": "A versioned, data-only fragment of IR that `kalup add` renders into a project's config files. Groups and managed properties only, in IR form. Closed at every level: a field this schema does not list is an error. No code runs, ever.",
6
+ "type": "object",
7
+ "required": ["blueprintVersion", "name", "version", "irVersion", "resources"],
8
+ "properties": {
9
+ "blueprintVersion": { "const": 1 },
10
+ "name": {
11
+ "description": "One or two segments of lowercase letters and digits joined by single dashes, `acme/renewals` for example. A segment never holds two dashes in a row, so the stored original's file name, the slash written as two dashes, names one blueprint.",
12
+ "type": "string",
13
+ "pattern": "^[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)?$"
14
+ },
15
+ "version": { "$ref": "#/$defs/version" },
16
+ "irVersion": { "const": 1 },
17
+ "description": {
18
+ "description": "The author's own text. Printed only to a person at a terminal, labelled as third-party text.",
19
+ "type": "string"
20
+ },
21
+ "requires": {
22
+ "description": "The objects the project must provide.",
23
+ "type": "array",
24
+ "items": { "$ref": "#/$defs/objectRef" }
25
+ },
26
+ "resources": {
27
+ "type": "object",
28
+ "patternProperties": { "^[a-z]+:\\S+$": { "$ref": "#/$defs/resource" } },
29
+ "additionalProperties": false
30
+ }
31
+ },
32
+ "additionalProperties": false,
33
+ "$defs": {
34
+ "version": {
35
+ "description": "Semantic version MAJOR.MINOR.PATCH with an optional pre-release.",
36
+ "type": "string",
37
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
38
+ },
39
+ "address": {
40
+ "type": "string",
41
+ "pattern": "^[a-z]+:\\S+$"
42
+ },
43
+ "ref": {
44
+ "type": "object",
45
+ "required": ["$ref"],
46
+ "properties": { "$ref": { "$ref": "#/$defs/address" } },
47
+ "additionalProperties": false
48
+ },
49
+ "objectRef": {
50
+ "type": "object",
51
+ "required": ["$ref"],
52
+ "properties": { "$ref": { "type": "string", "pattern": "^object:[a-z][a-z0-9_]*$" } },
53
+ "additionalProperties": false
54
+ },
55
+ "resource": {
56
+ "type": "object",
57
+ "required": ["type", "definition"],
58
+ "properties": {
59
+ "type": { "enum": ["group", "property"] },
60
+ "definition": { "type": "object" },
61
+ "binding": { "$ref": "#/$defs/binding" },
62
+ "lifecycle": { "$ref": "#/$defs/lifecycle" }
63
+ },
64
+ "additionalProperties": false,
65
+ "allOf": [
66
+ {
67
+ "if": { "required": ["type"], "properties": { "type": { "const": "property" } } },
68
+ "then": { "properties": { "definition": { "$ref": "#/$defs/propertyDefinition" } } }
69
+ },
70
+ {
71
+ "if": { "required": ["type"], "properties": { "type": { "const": "group" } } },
72
+ "then": {
73
+ "description": "A group has a label and nothing else.",
74
+ "properties": { "type": {}, "definition": { "$ref": "#/$defs/groupDefinition" } },
75
+ "additionalProperties": false
76
+ }
77
+ }
78
+ ]
79
+ },
80
+ "propertyDefinition": {
81
+ "description": "A managed property definition in HubSpot terms, as the IR holds it.",
82
+ "type": "object",
83
+ "required": ["label", "group", "type", "fieldType"],
84
+ "properties": {
85
+ "label": { "type": "string" },
86
+ "group": { "$ref": "#/$defs/ref" },
87
+ "type": { "enum": ["string", "number", "bool", "date", "datetime", "enumeration"] },
88
+ "fieldType": {
89
+ "enum": [
90
+ "text",
91
+ "textarea",
92
+ "file",
93
+ "phonenumber",
94
+ "number",
95
+ "booleancheckbox",
96
+ "select",
97
+ "radio",
98
+ "checkbox",
99
+ "date"
100
+ ]
101
+ },
102
+ "description": { "type": "string" },
103
+ "options": { "type": "array", "items": { "$ref": "#/$defs/option" } },
104
+ "hasUniqueValue": { "type": "boolean" },
105
+ "formField": { "type": "boolean" }
106
+ },
107
+ "additionalProperties": false
108
+ },
109
+ "option": {
110
+ "type": "object",
111
+ "required": ["value", "label"],
112
+ "properties": {
113
+ "value": { "type": "string" },
114
+ "label": { "type": "string" },
115
+ "hidden": { "type": "boolean" },
116
+ "description": { "type": "string" }
117
+ },
118
+ "additionalProperties": false
119
+ },
120
+ "groupDefinition": {
121
+ "type": "object",
122
+ "required": ["label"],
123
+ "properties": { "label": { "type": "string" } },
124
+ "additionalProperties": false
125
+ },
126
+ "binding": {
127
+ "description": "App terms. The key defaults to camelCase of the unprefixed name, the codec to the one the HubSpot type implies.",
128
+ "type": "object",
129
+ "properties": {
130
+ "key": { "type": "string", "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*$" },
131
+ "codec": { "enum": ["string", "number", "boolean", "date", "datetime", "enum", "multiEnum", "stringArray"] },
132
+ "aliases": { "type": "object", "additionalProperties": { "type": "string" } },
133
+ "required": { "type": "boolean" },
134
+ "readonly": { "type": "boolean" },
135
+ "strict": { "type": "boolean" }
136
+ },
137
+ "additionalProperties": false
138
+ },
139
+ "lifecycle": {
140
+ "type": "object",
141
+ "required": ["options"],
142
+ "properties": {
143
+ "options": { "enum": ["additive", "exact"] },
144
+ "removedOptions": { "type": "array", "items": { "type": "string" } },
145
+ "ignoreChanges": { "type": "array", "items": { "type": "string" } },
146
+ "preventDestroy": { "type": "boolean" }
147
+ },
148
+ "additionalProperties": false
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://kalup.dev/schemas/blueprints-lock-1.schema.json",
4
+ "title": "Kalup blueprints lock, version 1",
5
+ "description": "kalup/blueprints.lock.json, written by `kalup add` and `kalup blueprint upgrade`, never by hand. Records where each blueprint came from, the stored original upgrades merge against, and which config resources it provides. Closed at every level.",
6
+ "type": "object",
7
+ "required": ["lockVersion", "blueprints", "sources"],
8
+ "properties": {
9
+ "lockVersion": { "const": 1 },
10
+ "blueprints": {
11
+ "type": "object",
12
+ "patternProperties": { "^[a-z0-9]+(-[a-z0-9]+)*(/[a-z0-9]+(-[a-z0-9]+)*)?$": { "$ref": "#/$defs/entry" } },
13
+ "additionalProperties": false
14
+ },
15
+ "sources": {
16
+ "description": "Every `<source>@<version>` ever recorded, to its hash: the same source and version with another hash is refused.",
17
+ "type": "object",
18
+ "additionalProperties": { "$ref": "#/$defs/hash" }
19
+ }
20
+ },
21
+ "additionalProperties": false,
22
+ "$defs": {
23
+ "hash": {
24
+ "description": "sha256: and the hex digest of the bytes as fetched.",
25
+ "type": "string",
26
+ "pattern": "^sha256:[0-9a-f]{64}$"
27
+ },
28
+ "address": {
29
+ "type": "string",
30
+ "pattern": "^(group|property):[^\\s/]+/[^\\s/]+$"
31
+ },
32
+ "entry": {
33
+ "type": "object",
34
+ "required": ["version", "source", "hash", "prefix", "original", "resources", "held"],
35
+ "properties": {
36
+ "version": {
37
+ "type": "string",
38
+ "pattern": "^(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
39
+ },
40
+ "source": {
41
+ "description": "A path relative to the project root with forward slashes, or an https URL.",
42
+ "type": "string"
43
+ },
44
+ "hash": { "$ref": "#/$defs/hash" },
45
+ "prefix": { "type": "string", "pattern": "^([a-z][a-z0-9_]*)?$" },
46
+ "original": {
47
+ "description": "The stored original, kalup/.blueprints/<name with / as -->@<version>.json.",
48
+ "type": "string"
49
+ },
50
+ "resources": {
51
+ "description": "Each local address the blueprint provides, to its address in the blueprint.",
52
+ "type": "object",
53
+ "patternProperties": { "^(group|property):[^\\s/]+/[^\\s/]+$": { "$ref": "#/$defs/address" } },
54
+ "additionalProperties": false
55
+ },
56
+ "held": {
57
+ "description": "The conflicts the last upgrade kept config's value for.",
58
+ "type": "array",
59
+ "items": { "$ref": "#/$defs/held" }
60
+ }
61
+ },
62
+ "additionalProperties": false
63
+ },
64
+ "held": {
65
+ "type": "object",
66
+ "required": ["address", "unit"],
67
+ "properties": {
68
+ "address": { "$ref": "#/$defs/address" },
69
+ "unit": { "type": "string" },
70
+ "local": { "description": "Config's value; absent when config has none." },
71
+ "remote": { "description": "The upstream value; absent when the blueprint has none." }
72
+ },
73
+ "additionalProperties": false
74
+ }
75
+ }
76
+ }