@lunora/cli 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (27) hide show
  1. package/dist/bin.mjs +1 -1
  2. package/dist/index.d.mts +50 -10
  3. package/dist/index.d.ts +50 -10
  4. package/dist/index.mjs +5 -5
  5. package/dist/packem_chunks/handler.mjs +4 -3
  6. package/dist/packem_chunks/handler14.mjs +1 -1
  7. package/dist/packem_chunks/handler16.mjs +1 -1
  8. package/dist/packem_chunks/handler18.mjs +3 -5
  9. package/dist/packem_chunks/handler19.mjs +1 -1
  10. package/dist/packem_chunks/handler2.mjs +1 -1
  11. package/dist/packem_chunks/handler21.mjs +1 -1
  12. package/dist/packem_chunks/handler5.mjs +1 -1
  13. package/dist/packem_chunks/handler6.mjs +1 -1
  14. package/dist/packem_chunks/planDevCommand.mjs +5 -48
  15. package/dist/packem_chunks/runDeployCommand.mjs +1 -1
  16. package/dist/packem_chunks/runInitCommand.mjs +488 -46
  17. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +4 -4
  18. package/dist/packem_chunks/runResetCommand.mjs +3 -3
  19. package/dist/packem_shared/{COMMANDS-CHw4zOZ9.mjs → COMMANDS-Bn8luojF.mjs} +10 -2
  20. package/dist/packem_shared/{commands-SUPdjsu5.mjs → commands-DqsEzojt.mjs} +5 -4
  21. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +61 -0
  22. package/dist/packem_shared/{diffSnapshots-RR2ZE8Ya.mjs → diffSnapshots-BeDvvNiF.mjs} +1 -1
  23. package/dist/packem_shared/{runAddCommand-Txwh1Xw1.mjs → runAddCommand-G544_v6e.mjs} +1 -1
  24. package/dist/packem_shared/{schemaIrToSnapshot-aBTo7TM5.mjs → schemaIrToSnapshot-DdsljJT-.mjs} +1 -1
  25. package/dist/packem_shared/tui-prompts-XHFxlOg5.mjs +269 -0
  26. package/package.json +6 -5
  27. /package/dist/packem_shared/{defaultSpawner-DxI3mebw.mjs → createRecordingSpawner-DxI3mebw.mjs} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { existsSync, rmSync } from 'node:fs';
2
- import { promptYesNo } from '@lunora/config';
3
2
  import { join } from '@visulima/path';
4
3
  import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
4
+ import { a as tuiConfirm } from '../packem_shared/tui-prompts-XHFxlOg5.mjs';
5
5
 
6
6
  const runResetCommand = async (options) => {
7
7
  const cwd = options.cwd ?? process.cwd();
@@ -15,8 +15,8 @@ const runResetCommand = async (options) => {
15
15
  options.logger.error("reset: stdin is not a TTY — re-run with --yes to confirm deleting .wrangler/state");
16
16
  return { code: 1, removed: [] };
17
17
  }
18
- const confirmer = options.confirm ?? promptYesNo;
19
- const confirmed = await confirmer("This will delete .wrangler/state. Continue? [y/N] ");
18
+ const confirmer = options.confirm ?? tuiConfirm;
19
+ const confirmed = await confirmer("This will delete .wrangler/state. Continue?");
20
20
  if (!confirmed) {
21
21
  options.logger.info("reset: aborted");
22
22
  return { code: 1, removed: [] };
@@ -361,11 +361,19 @@ const initCommand = {
361
361
  options: [
362
362
  {
363
363
  alias: "t",
364
- defaultValue: "vite",
365
- description: "Template to scaffold (vite | standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid)",
364
+ // No default: when omitted, an interactive run shows the framework
365
+ // picker (default React overlay) and a non-interactive run errors.
366
+ // For React/Vue/Solid/Svelte SPAs use `--vite <framework>` (overlay);
367
+ // `-t` selects a bespoke template.
368
+ description: "Bespoke template (standalone | astro | nuxt | sveltekit | tanstack-start-react | tanstack-start-solid). For an SPA use --vite react|vue|solid|svelte.",
366
369
  name: "template",
367
370
  type: String
368
371
  },
372
+ {
373
+ description: "Scaffold via the create-vite overlay for a framework (react | vue | solid | svelte | vanilla) — official create-vite base + Lunora layer",
374
+ name: "vite",
375
+ type: String
376
+ },
369
377
  {
370
378
  description: "Local templates root to copy from (offline-friendly; expects <type>/ subdirs)",
371
379
  name: "from",
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
2
2
  import { dirname, join } from '@visulima/path';
3
- import { DEV_VARS_FILE, parseDevVariableEntries, promptYesNo } from '@lunora/config';
3
+ import { DEV_VARS_FILE, parseDevVariableEntries } from '@lunora/config';
4
4
  import { modify, applyEdits, parse } from 'jsonc-parser';
5
5
  import { fileURLToPath } from 'node:url';
6
+ import { a as tuiConfirm } from './tui-prompts-XHFxlOg5.mjs';
6
7
  import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BcYe607_.mjs';
7
8
  import { insertSchemaExtension } from './insertSchemaExtension-BuzF6-t2.mjs';
8
9
  import { createHash } from 'node:crypto';
@@ -272,8 +273,8 @@ const confirmDepMutation = async (items, options) => {
272
273
  options.logger.error(`add: stdin is not a TTY and the requested items ${reasonText} — re-run with --yes to confirm`);
273
274
  return false;
274
275
  }
275
- const confirmer = options.confirm ?? promptYesNo;
276
- const confirmed = await confirmer(`The requested items ${reasonText}. Continue? [y/N] `);
276
+ const confirmer = options.confirm ?? tuiConfirm;
277
+ const confirmed = await confirmer(`The requested items ${reasonText}. Continue?`);
277
278
  if (!confirmed) {
278
279
  options.logger.info("add: aborted");
279
280
  }
@@ -784,4 +785,4 @@ const runBuildIndexCommand = async (options) => {
784
785
  return empty;
785
786
  };
786
787
 
787
- export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveSourceRef as c, resolveDistTag as d, runListCommand as e, runAddCommand as r };
788
+ export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveDistTag as c, resolveSourceRef as d, runListCommand as e, runAddCommand as r };
@@ -0,0 +1,61 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join } from '@visulima/path';
4
+
5
+ const FALLBACK = "pnpm";
6
+ const KNOWN_MANAGERS = ["pnpm", "yarn", "npm", "bun"];
7
+ const INSTALL_PREFERENCE = ["pnpm", "bun", "yarn", "npm"];
8
+ const isManagerInstalled = (manager) => {
9
+ try {
10
+ return spawnSync(manager, ["--version"], { stdio: "ignore", timeout: 5e3 }).status === 0;
11
+ } catch {
12
+ return false;
13
+ }
14
+ };
15
+ const detectInstalledManagers = (probe = isManagerInstalled) => INSTALL_PREFERENCE.filter((manager) => probe(manager));
16
+ const installArgsFor = (manager) => {
17
+ return { args: ["install"], command: manager };
18
+ };
19
+ const parseDeclaredManager = (declared) => {
20
+ if (typeof declared !== "string") {
21
+ return void 0;
22
+ }
23
+ return KNOWN_MANAGERS.find((manager) => declared.startsWith(`${manager}@`));
24
+ };
25
+ const readDeclaredManager = (directory) => {
26
+ const candidate = join(directory, "package.json");
27
+ if (!existsSync(candidate)) {
28
+ return void 0;
29
+ }
30
+ try {
31
+ const parsed = JSON.parse(readFileSync(candidate, "utf8"));
32
+ return parseDeclaredManager(parsed.packageManager);
33
+ } catch {
34
+ return void 0;
35
+ }
36
+ };
37
+ const detectPackageManager = (startDirectory) => {
38
+ let directory = startDirectory;
39
+ while (directory && directory !== dirname(directory)) {
40
+ const declared = readDeclaredManager(directory);
41
+ if (declared !== void 0) {
42
+ return declared;
43
+ }
44
+ directory = dirname(directory);
45
+ }
46
+ return FALLBACK;
47
+ };
48
+ const execArgsFor = (manager, command, args) => {
49
+ if (manager === "yarn") {
50
+ return { args: [command, ...args], command: "yarn" };
51
+ }
52
+ if (manager === "bun") {
53
+ return { args: ["x", command, ...args], command: "bun" };
54
+ }
55
+ if (manager === "npm") {
56
+ return { args: ["--", command, ...args], command: "npx" };
57
+ }
58
+ return { args: ["exec", command, ...args], command: "pnpm" };
59
+ };
60
+
61
+ export { detectInstalledManagers as a, detectPackageManager as d, execArgsFor as e, installArgsFor as i };
@@ -1,4 +1,4 @@
1
- import { quoteIdentifier, columnRef, physicalIndexName, frameworkColumnDdl, sqlAffinityForKind } from '@lunora/d1/dialect';
1
+ import { quoteIdentifier, sqlAffinityForKind, frameworkColumnDdl, columnRef, physicalIndexName } from '@lunora/d1/dialect';
2
2
 
3
3
  const validatorKindToSqlType = (kind) => sqlAffinityForKind(kind);
4
4
  const renderColumnDefinition = (name, column) => {
@@ -1,4 +1,4 @@
1
1
  import 'node:fs';
2
2
  import '@visulima/path';
3
- export { r as runAddCommand, a as runBuildIndexCommand, e as runListCommand, b as runRegistryViewCommand } from './commands-SUPdjsu5.mjs';
3
+ export { r as runAddCommand, a as runBuildIndexCommand, e as runListCommand, b as runRegistryViewCommand } from './commands-DqsEzojt.mjs';
4
4
  import './buildRegistryIndex-BcYe607_.mjs';
@@ -1,4 +1,4 @@
1
- import { validatorKindToSqlType } from './diffSnapshots-RR2ZE8Ya.mjs';
1
+ import { validatorKindToSqlType } from './diffSnapshots-BeDvvNiF.mjs';
2
2
 
3
3
  const validatorToColumn = (validator) => {
4
4
  if (validator.kind === "optional" && validator.inner) {
@@ -0,0 +1,269 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { isInteractive } from '@lunora/config';
3
+ import { render } from '@visulima/tui';
4
+ import { BigText } from '@visulima/tui/components/big-text';
5
+ import { Box } from '@visulima/tui/components/box';
6
+ import { CommandPalette } from '@visulima/tui/components/command-palette';
7
+ import { ConfirmInput } from '@visulima/tui/components/confirm-input';
8
+ import { MultiSelect } from '@visulima/tui/components/multi-select';
9
+ import { SelectInput } from '@visulima/tui/components/select-input';
10
+ import { Spinner } from '@visulima/tui/components/spinner';
11
+ import { Text } from '@visulima/tui/components/text';
12
+ import { TextInput } from '@visulima/tui/components/text-input';
13
+ import { useApp } from '@visulima/tui/hooks/use-app';
14
+ import { useInput } from '@visulima/tui/hooks/use-input';
15
+ import { useEffect } from 'react';
16
+
17
+ const ACCENT = "#a855f7";
18
+ const SCROLL_LIMIT = 10;
19
+ const FILTER_THRESHOLD = 8;
20
+ const PromptFrame = ({ children }) => jsx(Box, { borderColor: ACCENT, borderStyle: "round", flexDirection: "column", paddingX: 1, children });
21
+ const runInkPrompt = async (build, fallback) => {
22
+ let result = fallback;
23
+ const instance = render(
24
+ build((value) => {
25
+ result = value;
26
+ }),
27
+ { exitOnCtrlC: true }
28
+ );
29
+ try {
30
+ await instance.waitUntilExit();
31
+ } catch {
32
+ } finally {
33
+ instance.unmount();
34
+ }
35
+ return result;
36
+ };
37
+ const itemLabel = (option) => option.description === void 0 ? option.label : `${option.label} — ${option.description}`;
38
+ const useCommit = (finish) => {
39
+ const { exit } = useApp();
40
+ return (result) => {
41
+ finish(result);
42
+ exit();
43
+ };
44
+ };
45
+ const useEscapeToExit = () => {
46
+ const { exit } = useApp();
47
+ useInput((_input, key) => {
48
+ if (key.escape) {
49
+ exit();
50
+ }
51
+ });
52
+ };
53
+ const SelectView = ({ finish, initialIndex, message, options }) => {
54
+ const commit = useCommit(finish);
55
+ useEscapeToExit();
56
+ return jsxs(PromptFrame, { children: [
57
+ jsx(Text, { bold: true, children: message }),
58
+ jsx(
59
+ SelectInput,
60
+ {
61
+ accentColor: ACCENT,
62
+ initialIndex,
63
+ items: options.map((option) => {
64
+ return { key: option.value, label: itemLabel(option), value: option.value };
65
+ }),
66
+ limit: SCROLL_LIMIT,
67
+ onSelect: (item) => {
68
+ commit(item.value);
69
+ }
70
+ }
71
+ )
72
+ ] });
73
+ };
74
+ const PaletteView = ({ finish, message, options }) => {
75
+ const { exit } = useApp();
76
+ const commit = useCommit(finish);
77
+ return jsxs(Box, { flexDirection: "column", children: [
78
+ jsx(Text, { bold: true, children: message }),
79
+ jsx(
80
+ CommandPalette,
81
+ {
82
+ accentColor: ACCENT,
83
+ autoFocus: true,
84
+ commands: options.map((option) => {
85
+ return { description: option.description, id: option.value, label: option.label };
86
+ }),
87
+ limit: SCROLL_LIMIT,
88
+ onCancel: () => {
89
+ exit();
90
+ },
91
+ onSelect: (id) => {
92
+ commit(id);
93
+ },
94
+ placeholder: "Type to filter…"
95
+ }
96
+ )
97
+ ] });
98
+ };
99
+ const tuiSelect = async (message, options, settings) => {
100
+ if (!isInteractive() || options.length === 0) {
101
+ return settings?.default;
102
+ }
103
+ if (options.length > FILTER_THRESHOLD) {
104
+ return runInkPrompt((finish) => jsx(PaletteView, { finish, message, options }), settings?.default);
105
+ }
106
+ const defaultIndex = settings?.default === void 0 ? -1 : options.findIndex((option) => option.value === settings.default);
107
+ return runInkPrompt(
108
+ (finish) => jsx(SelectView, { finish, initialIndex: defaultIndex >= 0 ? defaultIndex : void 0, message, options }),
109
+ settings?.default
110
+ );
111
+ };
112
+ const TextView = ({ defaultValue, finish, message, placeholder }) => {
113
+ const commit = useCommit(finish);
114
+ useEscapeToExit();
115
+ return jsxs(PromptFrame, { children: [
116
+ jsx(Text, { bold: true, children: message }),
117
+ jsx(
118
+ TextInput,
119
+ {
120
+ defaultValue,
121
+ onSubmit: (value) => {
122
+ commit(value.trim() === "" ? defaultValue : value.trim());
123
+ },
124
+ placeholder
125
+ }
126
+ )
127
+ ] });
128
+ };
129
+ const tuiText = async (message, settings) => {
130
+ const fallback = settings?.default ?? "";
131
+ if (!isInteractive()) {
132
+ return fallback;
133
+ }
134
+ return runInkPrompt(
135
+ (finish) => jsx(TextView, { defaultValue: fallback, finish, message, placeholder: settings?.placeholder }),
136
+ fallback
137
+ );
138
+ };
139
+ const MultiSelectView = ({ defaults, finish, message, options }) => {
140
+ const commit = useCommit(finish);
141
+ useEscapeToExit();
142
+ return jsxs(PromptFrame, { children: [
143
+ jsx(Text, { bold: true, children: message }),
144
+ jsx(Text, { dimColor: true, children: "space toggles · enter confirms · esc cancels" }),
145
+ jsx(
146
+ MultiSelect,
147
+ {
148
+ accentColor: ACCENT,
149
+ defaultValue: [...defaults],
150
+ limit: SCROLL_LIMIT,
151
+ onSubmit: (values) => {
152
+ const chosen = new Set(values);
153
+ commit(options.filter((option) => chosen.has(option.value)).map((option) => option.value));
154
+ },
155
+ options: options.map((option) => {
156
+ return { key: option.value, label: itemLabel(option), value: option.value };
157
+ })
158
+ }
159
+ )
160
+ ] });
161
+ };
162
+ const tuiMultiSelect = async (message, options, settings) => {
163
+ const defaults = settings?.defaults ?? [];
164
+ if (!isInteractive() || options.length === 0) {
165
+ return [...defaults];
166
+ }
167
+ return runInkPrompt((finish) => jsx(MultiSelectView, { defaults, finish, message, options }), [...defaults]);
168
+ };
169
+ const ConfirmView = ({ defaultYes, finish, message }) => {
170
+ const commit = useCommit(finish);
171
+ return jsx(PromptFrame, { children: jsxs(Box, { children: [
172
+ jsxs(Text, { bold: true, children: [
173
+ message,
174
+ " "
175
+ ] }),
176
+ jsx(
177
+ ConfirmInput,
178
+ {
179
+ defaultChoice: defaultYes ? "confirm" : "cancel",
180
+ onCancel: () => {
181
+ commit(false);
182
+ },
183
+ onConfirm: () => {
184
+ commit(true);
185
+ }
186
+ }
187
+ )
188
+ ] }) });
189
+ };
190
+ const tuiConfirm = async (message, options) => {
191
+ const defaultYes = options?.defaultYes === true;
192
+ if (!isInteractive()) {
193
+ return defaultYes;
194
+ }
195
+ return runInkPrompt((finish) => jsx(ConfirmView, { defaultYes, finish, message }), false);
196
+ };
197
+ const createTuiConfirm = () => isInteractive() ? (message) => tuiConfirm(message, { defaultYes: true }) : () => Promise.resolve(false);
198
+ const SelfExit = ({ children }) => {
199
+ const { exit } = useApp();
200
+ useEffect(() => {
201
+ exit();
202
+ }, [exit]);
203
+ return children;
204
+ };
205
+ const printFrame = async (element) => {
206
+ const instance = render(jsx(SelfExit, { children: element }));
207
+ try {
208
+ await instance.waitUntilExit();
209
+ } catch {
210
+ } finally {
211
+ instance.unmount();
212
+ }
213
+ };
214
+ const tuiIntro = async (message) => {
215
+ if (!isInteractive()) {
216
+ return;
217
+ }
218
+ await printFrame(
219
+ jsxs(Box, { borderColor: ACCENT, borderStyle: "round", paddingX: 1, children: [
220
+ jsxs(Text, { bold: true, color: ACCENT, children: [
221
+ "◆ Lunora",
222
+ " "
223
+ ] }),
224
+ jsx(Text, { children: message })
225
+ ] })
226
+ );
227
+ };
228
+ const tuiOutro = async (message) => {
229
+ if (!isInteractive()) {
230
+ return;
231
+ }
232
+ await printFrame(
233
+ jsxs(Box, { paddingX: 1, children: [
234
+ jsx(Text, { color: "green", children: "✔ " }),
235
+ jsx(Text, { children: message })
236
+ ] })
237
+ );
238
+ };
239
+ const tuiBanner = async (subtitle) => {
240
+ if (!isInteractive()) {
241
+ return;
242
+ }
243
+ await printFrame(
244
+ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
245
+ jsx(BigText, { colors: [ACCENT], font: "tiny", space: false, text: "LUNORA" }),
246
+ jsx(Text, { dimColor: true, children: subtitle })
247
+ ] })
248
+ );
249
+ };
250
+ const SpinnerView = ({ label }) => jsxs(Box, { children: [
251
+ jsx(Text, { color: ACCENT, children: jsx(Spinner, { type: "dots" }) }),
252
+ jsxs(Text, { children: [
253
+ " ",
254
+ label
255
+ ] })
256
+ ] });
257
+ const withTuiSpinner = async (label, task) => {
258
+ if (!isInteractive()) {
259
+ return task();
260
+ }
261
+ const instance = render(jsx(SpinnerView, { label }));
262
+ try {
263
+ return await task();
264
+ } finally {
265
+ instance.unmount();
266
+ }
267
+ };
268
+
269
+ export { tuiConfirm as a, tuiOutro as b, createTuiConfirm as c, tuiBanner as d, tuiText as e, tuiIntro as f, tuiMultiSelect as g, tuiSelect as t, withTuiSpinner as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/cli",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.6",
4
4
  "description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -30,7 +30,7 @@
30
30
  "lunora": "./dist/bin.mjs"
31
31
  },
32
32
  "files": [
33
- "dist",
33
+ "./dist",
34
34
  "__assets__",
35
35
  "skills",
36
36
  "README.md",
@@ -57,17 +57,18 @@
57
57
  "@lunora/container": "1.0.0-alpha.1",
58
58
  "@lunora/d1": "1.0.0-alpha.3",
59
59
  "@lunora/seed": "1.0.0-alpha.1",
60
- "@lunora/server": "1.0.0-alpha.1",
61
- "@lunora/studio": "1.0.0-alpha.2",
62
- "@lunora/values": "1.0.0-alpha.1",
63
60
  "@visulima/cerebro": "3.0.0-alpha.32",
64
61
  "@visulima/fs": "5.0.0-alpha.32",
65
62
  "@visulima/pail": "4.0.0-alpha.22",
66
63
  "@visulima/path": "3.0.0-alpha.13",
67
64
  "@visulima/spinner": "1.0.0-alpha.4",
65
+ "@visulima/tui": "1.0.0-alpha.26",
66
+ "cfonts": "^3.3.1",
68
67
  "giget": "3.3.0",
69
68
  "jsonc-parser": "^3.3.1",
70
69
  "magic-string": "^0.30.21",
70
+ "react": "^19.2.7",
71
+ "react-reconciler": "^0.33.0",
71
72
  "ts-morph": "^28.0.0"
72
73
  },
73
74
  "engines": {