@visulima/vis 1.0.0-alpha.1 → 1.0.0-alpha.11

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 (120) hide show
  1. package/CHANGELOG.md +403 -12
  2. package/LICENSE.md +283 -0
  3. package/README.md +254 -9
  4. package/dist/bin.js +9 -146
  5. package/dist/config/index.d.ts +1818 -0
  6. package/dist/config/index.js +2 -0
  7. package/dist/generate/index.d.ts +157 -0
  8. package/dist/generate/index.js +3 -0
  9. package/dist/packem_chunks/applyDefaults.js +336 -0
  10. package/dist/packem_chunks/bin.js +9577 -0
  11. package/dist/packem_chunks/doctor-probe.js +112 -0
  12. package/dist/packem_chunks/fix.js +234 -0
  13. package/dist/packem_chunks/handler.js +99 -0
  14. package/dist/packem_chunks/handler10.js +53 -0
  15. package/dist/packem_chunks/handler11.js +32 -0
  16. package/dist/packem_chunks/handler12.js +100 -0
  17. package/dist/packem_chunks/handler13.js +25 -0
  18. package/dist/packem_chunks/handler14.js +916 -0
  19. package/dist/packem_chunks/handler15.js +206 -0
  20. package/dist/packem_chunks/handler16.js +124 -0
  21. package/dist/packem_chunks/handler17.js +13 -0
  22. package/dist/packem_chunks/handler18.js +106 -0
  23. package/dist/packem_chunks/handler19.js +19 -0
  24. package/dist/packem_chunks/handler2.js +75 -0
  25. package/dist/packem_chunks/handler20.js +29 -0
  26. package/dist/packem_chunks/handler21.js +222 -0
  27. package/dist/packem_chunks/handler22.js +237 -0
  28. package/dist/packem_chunks/handler23.js +101 -0
  29. package/dist/packem_chunks/handler24.js +110 -0
  30. package/dist/packem_chunks/handler25.js +402 -0
  31. package/dist/packem_chunks/handler26.js +13 -0
  32. package/dist/packem_chunks/handler27.js +63 -0
  33. package/dist/packem_chunks/handler28.js +34 -0
  34. package/dist/packem_chunks/handler29.js +458 -0
  35. package/dist/packem_chunks/handler3.js +95 -0
  36. package/dist/packem_chunks/handler30.js +170 -0
  37. package/dist/packem_chunks/handler31.js +530 -0
  38. package/dist/packem_chunks/handler32.js +214 -0
  39. package/dist/packem_chunks/handler33.js +119 -0
  40. package/dist/packem_chunks/handler34.js +630 -0
  41. package/dist/packem_chunks/handler35.js +283 -0
  42. package/dist/packem_chunks/handler36.js +542 -0
  43. package/dist/packem_chunks/handler37.js +762 -0
  44. package/dist/packem_chunks/handler38.js +989 -0
  45. package/dist/packem_chunks/handler39.js +574 -0
  46. package/dist/packem_chunks/handler4.js +90 -0
  47. package/dist/packem_chunks/handler40.js +1685 -0
  48. package/dist/packem_chunks/handler41.js +1088 -0
  49. package/dist/packem_chunks/handler42.js +797 -0
  50. package/dist/packem_chunks/handler43.js +2658 -0
  51. package/dist/packem_chunks/handler44.js +3886 -0
  52. package/dist/packem_chunks/handler45.js +2574 -0
  53. package/dist/packem_chunks/handler46.js +3769 -0
  54. package/dist/packem_chunks/handler47.js +1491 -0
  55. package/dist/packem_chunks/handler5.js +174 -0
  56. package/dist/packem_chunks/handler6.js +95 -0
  57. package/dist/packem_chunks/handler7.js +115 -0
  58. package/dist/packem_chunks/handler8.js +12 -0
  59. package/dist/packem_chunks/handler9.js +29 -0
  60. package/dist/packem_chunks/heal-accept.js +522 -0
  61. package/dist/packem_chunks/heal.js +673 -0
  62. package/dist/packem_chunks/index.js +873 -0
  63. package/dist/packem_chunks/loader.js +23 -0
  64. package/dist/packem_shared/VisUpdateApp-D-Yz_wvg.js +1316 -0
  65. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
  66. package/dist/packem_shared/ai-analysis-CHeB1joD.js +367 -0
  67. package/dist/packem_shared/ai-cache-Be_jexe4.js +142 -0
  68. package/dist/packem_shared/ai-fix-B9iQVcD2.js +379 -0
  69. package/dist/packem_shared/cache-directory-2qvs4goY.js +98 -0
  70. package/dist/packem_shared/catalog-BJTtyi-O.js +1371 -0
  71. package/dist/packem_shared/dependency-scan-A0KSklpG.js +188 -0
  72. package/dist/packem_shared/docker-2iZzc280.js +181 -0
  73. package/dist/packem_shared/failure-log-Cz3Z4SKL.js +100 -0
  74. package/dist/packem_shared/flakiness-goTxXuCX.js +180 -0
  75. package/dist/packem_shared/otel-DCvqCTz_.js +158 -0
  76. package/dist/packem_shared/otelPlugin-DFaLDvJf.js +3 -0
  77. package/dist/packem_shared/registry-CbqXI0rc.js +272 -0
  78. package/dist/packem_shared/run-summary-utils-PVMl4aIh.js +130 -0
  79. package/dist/packem_shared/runtime-check-Cobi3p6l.js +127 -0
  80. package/dist/packem_shared/selectors-SM69TfqC.js +194 -0
  81. package/dist/packem_shared/symbols-Ta7g2nU-.js +14 -0
  82. package/dist/packem_shared/toolchain-BdZd9eBi.js +975 -0
  83. package/dist/packem_shared/typosquats-C-bCh3PX.js +1210 -0
  84. package/dist/packem_shared/use-measured-height-CNP0vT4M.js +20 -0
  85. package/dist/packem_shared/utils-CthVdBPS.js +40 -0
  86. package/dist/packem_shared/xxh3-Ck8mXNg1.js +239 -0
  87. package/index.js +773 -0
  88. package/package.json +82 -21
  89. package/schemas/project.schema.json +420 -0
  90. package/schemas/vis-config.schema.json +501 -0
  91. package/skills/vis/SKILL.md +96 -0
  92. package/templates/buildkite-ci/.buildkite/pipeline.yml.tera +85 -0
  93. package/templates/buildkite-ci/template.yml +20 -0
  94. package/dist/ai-analysis.d.ts +0 -40
  95. package/dist/ai-cache.d.ts +0 -21
  96. package/dist/bin.d.ts +0 -1
  97. package/dist/catalog.d.ts +0 -110
  98. package/dist/commands/affected.d.ts +0 -3
  99. package/dist/commands/ai.d.ts +0 -3
  100. package/dist/commands/analyze.d.ts +0 -3
  101. package/dist/commands/check.d.ts +0 -3
  102. package/dist/commands/graph.d.ts +0 -3
  103. package/dist/commands/hook/constants.d.ts +0 -8
  104. package/dist/commands/hook/index.d.ts +0 -3
  105. package/dist/commands/hook/install.d.ts +0 -7
  106. package/dist/commands/hook/migrate.d.ts +0 -27
  107. package/dist/commands/hook/uninstall.d.ts +0 -3
  108. package/dist/commands/migrate/constants.d.ts +0 -12
  109. package/dist/commands/migrate/deps.d.ts +0 -32
  110. package/dist/commands/migrate/index.d.ts +0 -3
  111. package/dist/commands/migrate/json.d.ts +0 -20
  112. package/dist/commands/migrate/lint-staged.d.ts +0 -62
  113. package/dist/commands/migrate/types.d.ts +0 -20
  114. package/dist/commands/run.d.ts +0 -3
  115. package/dist/commands/staged.d.ts +0 -3
  116. package/dist/commands/update.d.ts +0 -3
  117. package/dist/config.d.ts +0 -40
  118. package/dist/config.js +0 -1
  119. package/dist/package-manager.d.ts +0 -23
  120. package/dist/workspace.d.ts +0 -58
@@ -0,0 +1,762 @@
1
+ import { createRequire as __cjs_createRequire } from "node:module";
2
+
3
+ const __cjs_require = __cjs_createRequire(import.meta.url);
4
+
5
+ const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
6
+
7
+ const __cjs_getBuiltinModule = (module) => {
8
+ // Check if we're in Node.js and version supports getBuiltinModule
9
+ if (typeof __cjs_getProcess !== "undefined" && __cjs_getProcess.versions && __cjs_getProcess.versions.node) {
10
+ const [major, minor] = __cjs_getProcess.versions.node.split(".").map(Number);
11
+ // Node.js 20.16.0+ and 22.3.0+
12
+ if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
13
+ return __cjs_getProcess.getBuiltinModule(module);
14
+ }
15
+ }
16
+ // Fallback to createRequire
17
+ return __cjs_require(module);
18
+ };
19
+
20
+ import { dim, bold, cyan } from '@visulima/colorize';
21
+ import { join, dirname, relative, isAbsolute, sep, resolve } from '@visulima/path';
22
+ const {
23
+ fileURLToPath
24
+ } = __cjs_getBuiltinModule("node:url");
25
+ import { isAccessibleSync, walkSync, ensureDirSync, writeFileSync } from '@visulima/fs';
26
+ const {
27
+ createInterface
28
+ } = __cjs_getBuiltinModule("node:readline");
29
+ const {
30
+ mkdtempSync,
31
+ rmSync
32
+ } = __cjs_getBuiltinModule("node:fs");
33
+ const {
34
+ tmpdir
35
+ } = __cjs_getBuiltinModule("node:os");
36
+ import { downloadTemplate } from 'giget';
37
+ import { p as pail } from './bin.js';
38
+ const {
39
+ spawnSync
40
+ } = __cjs_getBuiltinModule("node:child_process");
41
+
42
+ const NATIVE_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
43
+ const TEMPLATE_YML = "template.yml";
44
+ const BLOCKED_BASE_SUFFIXES = [".d", ".test", ".spec", ".config", ".bench", ".stories"];
45
+ const BLOCKED_FULL_EXTENSIONS = [".d.ts", ".d.mts", ".d.cts", ".js.map", ".mjs.map", ".cjs.map", ".ts.map"];
46
+ const stripExtension = (filename) => {
47
+ for (const ext of NATIVE_EXTENSIONS) {
48
+ if (filename.endsWith(ext)) {
49
+ return filename.slice(0, -ext.length);
50
+ }
51
+ }
52
+ return filename;
53
+ };
54
+ const isNativeFile = (filename) => {
55
+ if (BLOCKED_FULL_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
56
+ return false;
57
+ }
58
+ if (!NATIVE_EXTENSIONS.some((ext) => filename.endsWith(ext))) {
59
+ return false;
60
+ }
61
+ const base = stripExtension(filename);
62
+ return !BLOCKED_BASE_SUFFIXES.some((suffix) => base.endsWith(suffix));
63
+ };
64
+ const scanNativeDirectory = (directory, source) => {
65
+ const results = [];
66
+ if (!isAccessibleSync(directory)) {
67
+ return results;
68
+ }
69
+ for (const entry of walkSync(directory, { includeDirs: false, includeSymlinks: false, maxDepth: 1 })) {
70
+ if (!isNativeFile(entry.name)) {
71
+ continue;
72
+ }
73
+ const name = stripExtension(entry.name);
74
+ results.push({
75
+ // Lazy loader — module is loaded only when the user picks the template.
76
+ load: () => loadNativeFromPath(entry.path),
77
+ name,
78
+ path: entry.path,
79
+ source
80
+ });
81
+ }
82
+ return results;
83
+ };
84
+ const scanMoonDirectory = (directory, source) => {
85
+ const results = [];
86
+ if (!isAccessibleSync(directory)) {
87
+ return results;
88
+ }
89
+ for (const entry of walkSync(directory, { includeFiles: false, includeSymlinks: false, maxDepth: 1 })) {
90
+ if (entry.path === directory) {
91
+ continue;
92
+ }
93
+ const yamlPath = join(entry.path, TEMPLATE_YML);
94
+ if (!isAccessibleSync(yamlPath)) {
95
+ continue;
96
+ }
97
+ results.push({
98
+ load: () => loadMoonFromPath(entry.path, entry.name),
99
+ name: entry.name,
100
+ path: entry.path,
101
+ source
102
+ });
103
+ }
104
+ return results;
105
+ };
106
+ const resolveBuiltinTemplatesDirectory = () => {
107
+ try {
108
+ const here = fileURLToPath(import.meta.url);
109
+ const candidate = join(dirname(here), "..", "..", "templates");
110
+ return isAccessibleSync(candidate) ? candidate : void 0;
111
+ } catch {
112
+ return void 0;
113
+ }
114
+ };
115
+ const discoverTemplates = (options) => {
116
+ const { extraDirectories = [], onWarning, workspaceRoot } = options;
117
+ const results = [];
118
+ results.push(...scanNativeDirectory(join(workspaceRoot, ".vis", "templates"), "native"));
119
+ results.push(...scanMoonDirectory(join(workspaceRoot, ".vis", "templates"), "moon"));
120
+ results.push(...scanMoonDirectory(join(workspaceRoot, ".moon", "templates"), "moon"));
121
+ for (const directory of extraDirectories) {
122
+ results.push(...scanMoonDirectory(directory, "config"));
123
+ results.push(...scanNativeDirectory(directory, "config"));
124
+ }
125
+ const builtinDirectory = resolveBuiltinTemplatesDirectory();
126
+ if (builtinDirectory) {
127
+ results.push(...scanMoonDirectory(builtinDirectory, "builtin"));
128
+ }
129
+ const seen = /* @__PURE__ */ new Map();
130
+ for (const template of results) {
131
+ const existing = seen.get(template.name);
132
+ if (!existing) {
133
+ seen.set(template.name, template);
134
+ continue;
135
+ }
136
+ if (onWarning) {
137
+ onWarning(
138
+ `Template "${template.name}" exists in multiple sources — using ${existing.source} (${existing.path}), ignoring ${template.source} (${template.path}).`
139
+ );
140
+ }
141
+ }
142
+ return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
143
+ };
144
+ const loadNativeFromPath = async (path) => {
145
+ const { loadNativeTemplate } = await import('./loader.js');
146
+ return loadNativeTemplate(path);
147
+ };
148
+ const loadMoonFromPath = async (path, name) => {
149
+ const { loadMoonTemplate } = await import('./index.js');
150
+ return loadMoonTemplate(path, name);
151
+ };
152
+
153
+ const ask = (rl, question) => new Promise((resolve) => {
154
+ rl.question(question, (answer) => {
155
+ resolve(answer.trim());
156
+ });
157
+ });
158
+ const confirm = async (rl, question, defaultYes) => {
159
+ const hint = defaultYes ? "[Y/n]" : "[y/N]";
160
+ const answer = await ask(rl, ` ${question} ${dim(hint)} `);
161
+ if (answer === "") {
162
+ return defaultYes;
163
+ }
164
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
165
+ };
166
+ const selectOne = async (rl, question, choices, defaultValue) => {
167
+ process.stderr.write(` ${question}
168
+ `);
169
+ for (const [index, choice] of choices.entries()) {
170
+ const prefix = bold(cyan(` ${String(index + 1)}.`));
171
+ const star = choice === defaultValue ? dim(" (default)") : "";
172
+ process.stderr.write(`${prefix} ${choice}${star}
173
+ `);
174
+ }
175
+ while (true) {
176
+ const answer = await ask(rl, `
177
+ ${dim(`Enter choice (1-${String(choices.length)}):`)} `);
178
+ if (answer === "" && defaultValue !== void 0) {
179
+ return defaultValue;
180
+ }
181
+ const number_ = Number.parseInt(answer, 10);
182
+ if (Number.isInteger(number_) && number_ >= 1 && number_ <= choices.length) {
183
+ return choices[number_ - 1];
184
+ }
185
+ const match = choices.find((c) => c === answer);
186
+ if (match) {
187
+ return match;
188
+ }
189
+ process.stderr.write(` ${dim("Invalid choice. Try again.")}
190
+ `);
191
+ }
192
+ };
193
+ const selectMany = async (rl, question, choices, defaultValues) => {
194
+ process.stderr.write(` ${question} ${dim("(comma-separated numbers)")}
195
+ `);
196
+ for (const [index, choice] of choices.entries()) {
197
+ const prefix = bold(cyan(` ${String(index + 1)}.`));
198
+ const star = defaultValues.includes(choice) ? dim(" (default)") : "";
199
+ process.stderr.write(`${prefix} ${choice}${star}
200
+ `);
201
+ }
202
+ while (true) {
203
+ const answer = await ask(rl, `
204
+ ${dim(`Enter choices:`)} `);
205
+ if (answer === "" && defaultValues.length > 0) {
206
+ return defaultValues;
207
+ }
208
+ const indices = answer.split(",").map((part) => Number.parseInt(part.trim(), 10)).filter((n) => Number.isInteger(n) && n >= 1 && n <= choices.length);
209
+ if (indices.length > 0) {
210
+ return indices.map((index) => choices[index - 1]);
211
+ }
212
+ process.stderr.write(` ${dim("Invalid choice. Try again.")}
213
+ `);
214
+ }
215
+ };
216
+ const promptText = (label) => ` ${dim(`${label}:`)} `;
217
+ const variableLabel = (name, variable) => variable.prompt ?? name;
218
+ const sortVariables = (variables) => Object.entries(variables).sort(([nameA, a], [nameB, b]) => {
219
+ const orderA = a.order ?? 0;
220
+ const orderB = b.order ?? 0;
221
+ if (orderA !== orderB) {
222
+ return orderA - orderB;
223
+ }
224
+ return nameA.localeCompare(nameB);
225
+ });
226
+ const parseValue = (variable, raw) => {
227
+ switch (variable.type) {
228
+ case "array": {
229
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
230
+ }
231
+ case "boolean": {
232
+ return raw === "true" || raw === "1" || raw === "yes" || raw === "y";
233
+ }
234
+ case "enum": {
235
+ if (variable.multiple) {
236
+ return raw.split(",").map((s) => s.trim()).filter(Boolean);
237
+ }
238
+ return raw;
239
+ }
240
+ case "number": {
241
+ const parsed = Number(raw);
242
+ if (Number.isNaN(parsed)) {
243
+ throw new TypeError(`Expected a number, got "${raw}"`);
244
+ }
245
+ return parsed;
246
+ }
247
+ default: {
248
+ return raw;
249
+ }
250
+ }
251
+ };
252
+ const validateValue = (name, variable, value) => {
253
+ if (variable.type === "enum") {
254
+ const values = Array.isArray(value) ? value : [value];
255
+ for (const candidate of values) {
256
+ if (typeof candidate !== "string" || !variable.values.includes(candidate)) {
257
+ throw new Error(`Variable "${name}" must be one of: ${variable.values.join(", ")} (got "${String(candidate)}")`);
258
+ }
259
+ }
260
+ }
261
+ };
262
+ const collectOptions = async (collectOptionsArguments) => {
263
+ const { defaults, interactive, overrides, variables } = collectOptionsArguments;
264
+ const result = {};
265
+ const rl = interactive ? createInterface({ input: process.stdin, output: process.stderr }) : null;
266
+ try {
267
+ for (const [name, variable] of sortVariables(variables)) {
268
+ if (Object.hasOwn(overrides, name)) {
269
+ const value2 = parseValue(variable, overrides[name] ?? "");
270
+ validateValue(name, variable, value2);
271
+ result[name] = value2;
272
+ continue;
273
+ }
274
+ if (defaults || !interactive || variable.internal) {
275
+ if (variable.default !== void 0) {
276
+ validateValue(name, variable, variable.default);
277
+ result[name] = variable.default;
278
+ continue;
279
+ }
280
+ if (variable.required) {
281
+ throw new Error(`Required variable "${name}" not provided. Pass --${name}=<value> or remove --defaults.`);
282
+ }
283
+ continue;
284
+ }
285
+ const label = variableLabel(name, variable);
286
+ let value;
287
+ if (variable.type === "boolean") {
288
+ value = await confirm(rl, label, Boolean(variable.default ?? false));
289
+ } else if (variable.type === "enum") {
290
+ if (variable.multiple) {
291
+ const defaultValues = Array.isArray(variable.default) ? variable.default : [];
292
+ value = await selectMany(rl, label, variable.values, defaultValues);
293
+ } else {
294
+ const defaultValue = typeof variable.default === "string" ? variable.default : void 0;
295
+ value = await selectOne(rl, label, variable.values, defaultValue);
296
+ }
297
+ } else {
298
+ const defaultHint = variable.default === void 0 ? "" : ` (${String(variable.default)})`;
299
+ const raw = await ask(rl, promptText(`${label}${defaultHint}`));
300
+ if (raw === "" && variable.default !== void 0) {
301
+ value = variable.default;
302
+ } else if (raw === "") {
303
+ if (variable.required) {
304
+ throw new Error(`Variable "${name}" is required`);
305
+ }
306
+ continue;
307
+ } else {
308
+ value = parseValue(variable, raw);
309
+ }
310
+ }
311
+ validateValue(name, variable, value);
312
+ result[name] = value;
313
+ }
314
+ return result;
315
+ } finally {
316
+ rl?.close();
317
+ }
318
+ };
319
+
320
+ const REMOTE_PROTOCOLS = ["git://", "npm://", "https://", "github:", "gitlab:", "bitbucket:", "sourcehut:"];
321
+ const isRemoteSource = (input) => REMOTE_PROTOCOLS.some((protocol) => input.startsWith(protocol));
322
+ const fetchRemoteTemplate = async (source, options = {}) => {
323
+ const ownDirectory = options.targetDirectory === void 0;
324
+ const directory = options.targetDirectory ?? mkdtempSync(join(tmpdir(), "vis-generate-"));
325
+ const cleanup = () => {
326
+ if (!ownDirectory) {
327
+ return;
328
+ }
329
+ try {
330
+ rmSync(directory, { force: true, recursive: true });
331
+ } catch {
332
+ }
333
+ };
334
+ pail.info(`Downloading ${source}…`);
335
+ try {
336
+ const result = await downloadTemplate(source, {
337
+ auth: options.auth || process.env.GIGET_AUTH || process.env.GITHUB_TOKEN || process.env.GH_TOKEN || void 0,
338
+ dir: directory,
339
+ force: true,
340
+ preferOffline: options.preferOffline
341
+ });
342
+ return { cleanup, directory: result.dir };
343
+ } catch (error) {
344
+ cleanup();
345
+ const message = error instanceof Error ? error.message : String(error);
346
+ pail.warn(`Failed to download template: ${message}`);
347
+ throw error;
348
+ }
349
+ };
350
+
351
+ const flattenTree = (tree, prefix = "") => {
352
+ const result = [];
353
+ for (const [key, value] of Object.entries(tree)) {
354
+ const path = prefix ? `${prefix}/${key}` : key;
355
+ if (typeof value === "string" || Buffer.isBuffer(value)) {
356
+ result.push({ content: value, path });
357
+ } else if (value && typeof value === "object") {
358
+ result.push(...flattenTree(value, path));
359
+ }
360
+ }
361
+ return result;
362
+ };
363
+ const formatSize = (bytes) => {
364
+ if (bytes < 1024) {
365
+ return `${bytes} B`;
366
+ }
367
+ if (bytes < 1024 * 1024) {
368
+ return `${(bytes / 1024).toFixed(1)} KB`;
369
+ }
370
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
371
+ };
372
+ const safeJoinDestination = (destination, relPath) => {
373
+ if (isAbsolute(relPath)) {
374
+ throw new Error(`Refusing to write outside destination: template produced absolute path "${relPath}".`);
375
+ }
376
+ const target = join(destination, relPath);
377
+ const rel = relative(destination, target);
378
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
379
+ throw new Error(`Refusing to write outside destination: "${relPath}" resolves to "${target}" which escapes "${destination}".`);
380
+ }
381
+ return target;
382
+ };
383
+ const writeOne = (destinationPath, content) => {
384
+ ensureDirSync(dirname(destinationPath));
385
+ writeFileSync(destinationPath, content);
386
+ };
387
+ const runScript = (script, cwd, silent = false) => {
388
+ const commands = typeof script === "string" ? [script] : script.commands;
389
+ const isSilent = typeof script === "string" ? silent : script.silent ?? silent;
390
+ for (const command of commands) {
391
+ if (!isSilent) {
392
+ pail.info(`$ ${command}`);
393
+ }
394
+ const result = spawnSync(command, {
395
+ cwd,
396
+ shell: true,
397
+ stdio: isSilent ? "ignore" : "inherit"
398
+ });
399
+ if (result.status !== 0) {
400
+ pail.warn(`Script failed (exit ${String(result.status)}): ${command}`);
401
+ return false;
402
+ }
403
+ }
404
+ return true;
405
+ };
406
+ const groupByPhase = (scripts) => {
407
+ const phases = /* @__PURE__ */ new Map();
408
+ for (const script of scripts) {
409
+ const phase = typeof script === "string" ? 0 : script.phase ?? 0;
410
+ const bucket = phases.get(phase);
411
+ if (bucket) {
412
+ bucket.push(script);
413
+ } else {
414
+ phases.set(phase, [script]);
415
+ }
416
+ }
417
+ return [...phases.entries()].sort(([a], [b]) => a - b);
418
+ };
419
+ const runTemplate = async (template, options) => {
420
+ const builtins = {
421
+ dest_dir: options.destination,
422
+ dest_rel_dir: relative(options.workspaceRoot, options.destination) || ".",
423
+ working_dir: options.cwd,
424
+ workspace_root: options.workspaceRoot
425
+ };
426
+ const context = {
427
+ builtins,
428
+ options: options.options
429
+ };
430
+ const creation = await template.produce(context);
431
+ const files = creation.files ? flattenTree(creation.files) : [];
432
+ const filesMeta = creation.filesMeta ?? {};
433
+ const plans = [];
434
+ for (const file of files) {
435
+ const target = safeJoinDestination(options.destination, file.path);
436
+ plans.push({ file, meta: filesMeta[file.path] ?? {}, target });
437
+ }
438
+ if (options.dryRun) {
439
+ pail.info(`${bold(cyan("Plan"))} ${dim("(dry-run, no files written)")}`);
440
+ for (const plan of plans) {
441
+ const size = Buffer.isBuffer(plan.file.content) ? plan.file.content.length : Buffer.byteLength(plan.file.content, "utf8");
442
+ process.stderr.write(` ${dim("write")} ${plan.file.path} ${dim(`(${formatSize(size)})`)}
443
+ `);
444
+ }
445
+ } else {
446
+ ensureDirSync(options.destination);
447
+ let written = 0;
448
+ let skipped = 0;
449
+ for (const plan of plans) {
450
+ const { file, meta, target } = plan;
451
+ const exists = isAccessibleSync(target);
452
+ const effectiveForce = options.force || meta.force === true;
453
+ if (exists && !effectiveForce) {
454
+ pail.warn(`Skipped existing file: ${file.path} (use --force or set frontmatter force: true to overwrite)`);
455
+ skipped += 1;
456
+ continue;
457
+ }
458
+ writeOne(target, file.content);
459
+ written += 1;
460
+ }
461
+ pail.success(`Wrote ${String(written)} file${written === 1 ? "" : "s"}${skipped > 0 ? `, skipped ${String(skipped)}` : ""}`);
462
+ }
463
+ if (!options.dryRun && !options.skipScripts && creation.scripts && creation.scripts.length > 0) {
464
+ const phases = groupByPhase(creation.scripts);
465
+ pail.info(
466
+ `Running ${String(creation.scripts.length)} script${creation.scripts.length === 1 ? "" : "s"} across ${String(phases.length)} phase${phases.length === 1 ? "" : "s"}…`
467
+ );
468
+ for (const [, scripts] of phases) {
469
+ const results = await Promise.all(scripts.map((script) => Promise.resolve(runScript(script, options.destination))));
470
+ if (results.includes(false)) {
471
+ throw new Error("Script failed — aborting.");
472
+ }
473
+ }
474
+ }
475
+ if (creation.suggestions && creation.suggestions.length > 0) {
476
+ process.stderr.write("\n");
477
+ pail.notice("Next steps:");
478
+ for (const suggestion of creation.suggestions) {
479
+ process.stderr.write(` ${dim("•")} ${suggestion}
480
+ `);
481
+ }
482
+ }
483
+ };
484
+
485
+ const toListEntry = async (template) => {
486
+ let description;
487
+ try {
488
+ const loaded = await template.load();
489
+ description = loaded.about?.description;
490
+ } catch {
491
+ }
492
+ return { description, name: template.name, path: template.path, source: template.source };
493
+ };
494
+ const summarizeVariable = (name, variable) => {
495
+ const summary = {
496
+ default: variable.default,
497
+ name,
498
+ order: variable.order,
499
+ prompt: variable.prompt,
500
+ required: variable.required,
501
+ type: variable.type
502
+ };
503
+ if (variable.type === "enum") {
504
+ summary.multiple = variable.multiple;
505
+ summary.values = variable.values;
506
+ }
507
+ return summary;
508
+ };
509
+ const describeTemplate = async (discovered) => {
510
+ const loaded = await discovered.load();
511
+ const variables = Object.entries(loaded.options ?? {}).sort(([nameA, a], [nameB, b]) => {
512
+ const orderA = a.order ?? 0;
513
+ const orderB = b.order ?? 0;
514
+ return orderA === orderB ? nameA.localeCompare(nameB) : orderA - orderB;
515
+ }).map(([name, variable]) => summarizeVariable(name, variable));
516
+ return {
517
+ description: loaded.about?.description ?? "",
518
+ destination: loaded.destination,
519
+ name: discovered.name,
520
+ path: discovered.path,
521
+ source: discovered.source,
522
+ variables
523
+ };
524
+ };
525
+ const printList = (templates) => {
526
+ if (templates.length === 0) {
527
+ pail.info("No templates found.");
528
+ pail.notice("Create one at .vis/templates/<name>.ts (programmatic) or .vis/templates/<name>/ (moon-format with template.yml).");
529
+ return;
530
+ }
531
+ pail.info("Available templates:");
532
+ for (const template of templates) {
533
+ const tag = dim(`(${template.source})`);
534
+ process.stderr.write(` ${bold(cyan(template.name))} ${tag}
535
+ `);
536
+ }
537
+ };
538
+ const parsePassthroughOverrides = (extraArguments) => {
539
+ const overrides = {};
540
+ const remaining = [];
541
+ for (const argument of extraArguments) {
542
+ if (!argument.startsWith("--")) {
543
+ remaining.push(argument);
544
+ continue;
545
+ }
546
+ const equalsIndex = argument.indexOf("=");
547
+ if (equalsIndex === -1) {
548
+ const key2 = argument.slice(2);
549
+ if (key2.startsWith("no-")) {
550
+ overrides[key2.slice(3)] = "false";
551
+ } else {
552
+ overrides[key2] = "true";
553
+ }
554
+ continue;
555
+ }
556
+ const key = argument.slice(2, equalsIndex);
557
+ const value = argument.slice(equalsIndex + 1);
558
+ overrides[key] = value;
559
+ }
560
+ return { overrides, remaining };
561
+ };
562
+ const pickInteractive = async (templates) => {
563
+ const { createInterface } = await import('node:readline');
564
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
565
+ try {
566
+ process.stderr.write(` ${bold(cyan("vis generate"))} ${dim("— pick a template")}
567
+
568
+ `);
569
+ for (const [index, template] of templates.entries()) {
570
+ const prefix = bold(cyan(` ${String(index + 1)}.`));
571
+ process.stderr.write(`${prefix} ${template.name} ${dim(`(${template.source})`)}
572
+ `);
573
+ }
574
+ return new Promise((resolveValue, reject) => {
575
+ rl.question(`
576
+ ${dim(`Enter choice (1-${String(templates.length)}):`)} `, (answer) => {
577
+ const number_ = Number.parseInt(answer.trim(), 10);
578
+ if (Number.isInteger(number_) && number_ >= 1 && number_ <= templates.length) {
579
+ resolveValue(templates[number_ - 1].name);
580
+ } else {
581
+ reject(new Error("Invalid choice."));
582
+ }
583
+ });
584
+ });
585
+ } finally {
586
+ rl.close();
587
+ }
588
+ };
589
+ const execute = async ({ argument, options, rawUnknown, visConfig, workspaceRoot: wsRoot }) => {
590
+ const cwd = options.cwd || wsRoot || process.cwd();
591
+ const workspaceRoot = wsRoot ?? cwd;
592
+ const generatorConfig = visConfig?.generator;
593
+ const args = Array.isArray(argument) ? argument : argument ? [argument] : [];
594
+ if (options.list) {
595
+ const discovered = discoverTemplates({
596
+ extraDirectories: generatorConfig?.templates ?? [],
597
+ onWarning: (message) => {
598
+ pail.warn(message);
599
+ },
600
+ workspaceRoot
601
+ });
602
+ if (options.json) {
603
+ const entries = await Promise.all(discovered.map((t) => toListEntry(t)));
604
+ process.stdout.write(`${JSON.stringify(entries, null, 2)}
605
+ `);
606
+ return;
607
+ }
608
+ printList(discovered);
609
+ return;
610
+ }
611
+ if (options.describe) {
612
+ const wanted = args[0];
613
+ if (!wanted) {
614
+ throw new Error("`--describe` requires a template name. Run `vis generate --list` to see available templates.");
615
+ }
616
+ const discovered = discoverTemplates({
617
+ extraDirectories: generatorConfig?.templates ?? [],
618
+ onWarning: (message) => {
619
+ pail.warn(message);
620
+ },
621
+ workspaceRoot
622
+ });
623
+ const match = discovered.find((t) => t.name === wanted);
624
+ if (!match) {
625
+ throw new Error(`Template "${wanted}" not found. Run \`vis generate --list\` to see available templates.`);
626
+ }
627
+ const described = await describeTemplate(match);
628
+ if (options.json) {
629
+ process.stdout.write(`${JSON.stringify(described, null, 2)}
630
+ `);
631
+ return;
632
+ }
633
+ pail.info(`Template: ${bold(cyan(described.name))} ${dim(`(${described.source})`)}`);
634
+ if (described.description) {
635
+ pail.info(described.description);
636
+ }
637
+ if (described.destination) {
638
+ pail.info(`Destination: ${dim(described.destination)}`);
639
+ }
640
+ if (described.variables.length === 0) {
641
+ pail.info("No variables.");
642
+ } else {
643
+ pail.info("Variables:");
644
+ for (const variable of described.variables) {
645
+ const flags = [variable.type];
646
+ if (variable.required) flags.push("required");
647
+ if (variable.default !== void 0) flags.push(`default=${JSON.stringify(variable.default)}`);
648
+ if (variable.values) flags.push(`values=${variable.values.join("|")}`);
649
+ process.stderr.write(` ${bold(cyan(variable.name))} ${dim(`(${flags.join(", ")})`)}
650
+ `);
651
+ }
652
+ }
653
+ return;
654
+ }
655
+ let passthrough = [...rawUnknown ?? []];
656
+ if (passthrough.length === 0) {
657
+ const rawArgv = process.argv.slice(2);
658
+ const argvDashIndex = rawArgv.indexOf("--");
659
+ if (argvDashIndex !== -1) {
660
+ passthrough = rawArgv.slice(argvDashIndex + 1);
661
+ }
662
+ }
663
+ const legacyDashIndex = args.indexOf("--");
664
+ const legacyExtras = legacyDashIndex === -1 ? [] : args.slice(legacyDashIndex + 1);
665
+ const ownArgs = legacyDashIndex === -1 ? args : args.slice(0, legacyDashIndex);
666
+ const { overrides } = parsePassthroughOverrides([...legacyExtras, ...passthrough]);
667
+ let template;
668
+ let templateName;
669
+ let templateDestination;
670
+ const input = ownArgs[0];
671
+ let remoteCleanup;
672
+ if (input && isRemoteSource(input)) {
673
+ const fetched = await fetchRemoteTemplate(input, {
674
+ auth: generatorConfig?.auth,
675
+ preferOffline: Boolean(options.preferOffline) || generatorConfig?.preferOffline
676
+ });
677
+ remoteCleanup = fetched.cleanup;
678
+ try {
679
+ const discovered = discoverTemplates({ extraDirectories: [fetched.directory], workspaceRoot });
680
+ const remoteTemplate = discovered.find((t) => t.path.startsWith(fetched.directory));
681
+ if (!remoteTemplate) {
682
+ throw new Error(`Downloaded template at ${fetched.directory} contains no template.yml or *.ts entrypoint.`);
683
+ }
684
+ template = await remoteTemplate.load();
685
+ templateName = remoteTemplate.name;
686
+ templateDestination = template.destination;
687
+ } catch (error) {
688
+ remoteCleanup();
689
+ remoteCleanup = void 0;
690
+ throw error;
691
+ }
692
+ } else {
693
+ const discovered = discoverTemplates({
694
+ extraDirectories: generatorConfig?.templates ?? [],
695
+ onWarning: (message) => {
696
+ pail.warn(message);
697
+ },
698
+ workspaceRoot
699
+ });
700
+ if (discovered.length === 0) {
701
+ throw new Error("No templates found. Create one at .vis/templates/<name>.ts or .vis/templates/<name>/template.yml.");
702
+ }
703
+ let wanted;
704
+ if (input) {
705
+ wanted = input;
706
+ } else if (options.noInteractive || !process.stdin.isTTY) {
707
+ throw new Error("No template specified. Pass a template name (see `vis generate --list`) or run interactively in a terminal.");
708
+ } else {
709
+ wanted = await pickInteractive(discovered);
710
+ }
711
+ const match = discovered.find((t) => t.name === wanted);
712
+ if (!match) {
713
+ throw new Error(`Template "${wanted}" not found. Run 'vis generate --list' to see available templates.`);
714
+ }
715
+ template = await match.load();
716
+ templateName = match.name;
717
+ templateDestination = template.destination;
718
+ }
719
+ const toFlag = options.to;
720
+ const dryRun = Boolean(options.dryRun);
721
+ const force = Boolean(options.force);
722
+ const useDefaults = Boolean(options.defaults);
723
+ const skipScripts = Boolean(options.skipScripts);
724
+ const isInteractive = !options.noInteractive && Boolean(process.stdin.isTTY) && !useDefaults;
725
+ let destinationInput;
726
+ if (toFlag) {
727
+ destinationInput = toFlag;
728
+ } else if (templateDestination) {
729
+ destinationInput = templateDestination;
730
+ } else {
731
+ destinationInput = ".";
732
+ }
733
+ const destination = isAbsolute(destinationInput) ? destinationInput : resolve(cwd, destinationInput);
734
+ pail.info(`Template: ${bold(cyan(templateName))}`);
735
+ pail.info(`Target: ${dim(destination)}`);
736
+ process.stderr.write("\n");
737
+ const collectedOptions = await collectOptions({
738
+ defaults: useDefaults,
739
+ interactive: isInteractive,
740
+ overrides,
741
+ variables: template.options ?? {}
742
+ });
743
+ try {
744
+ await runTemplate(template, {
745
+ cwd,
746
+ destination,
747
+ dryRun,
748
+ force,
749
+ options: collectedOptions,
750
+ skipScripts,
751
+ workspaceRoot
752
+ });
753
+ if (!dryRun) {
754
+ process.stderr.write("\n");
755
+ pail.success(`Template '${templateName}' applied.`);
756
+ }
757
+ } finally {
758
+ remoteCleanup?.();
759
+ }
760
+ };
761
+
762
+ export { execute as default };