@lunora/cli 0.0.0 → 1.0.0-alpha.10

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 (75) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +109 -9
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/bin.mjs +11 -0
  5. package/dist/index.d.mts +956 -0
  6. package/dist/index.d.ts +956 -0
  7. package/dist/index.mjs +19 -0
  8. package/dist/packem_chunks/handler.mjs +150 -0
  9. package/dist/packem_chunks/handler10.mjs +22 -0
  10. package/dist/packem_chunks/handler11.mjs +192 -0
  11. package/dist/packem_chunks/handler12.mjs +131 -0
  12. package/dist/packem_chunks/handler13.mjs +65 -0
  13. package/dist/packem_chunks/handler14.mjs +58 -0
  14. package/dist/packem_chunks/handler15.mjs +79 -0
  15. package/dist/packem_chunks/handler16.mjs +43 -0
  16. package/dist/packem_chunks/handler17.mjs +105 -0
  17. package/dist/packem_chunks/handler18.mjs +170 -0
  18. package/dist/packem_chunks/handler19.mjs +89 -0
  19. package/dist/packem_chunks/handler2.mjs +114 -0
  20. package/dist/packem_chunks/handler20.mjs +94 -0
  21. package/dist/packem_chunks/handler21.mjs +311 -0
  22. package/dist/packem_chunks/handler3.mjs +204 -0
  23. package/dist/packem_chunks/handler4.mjs +33 -0
  24. package/dist/packem_chunks/handler5.mjs +49 -0
  25. package/dist/packem_chunks/handler6.mjs +91 -0
  26. package/dist/packem_chunks/handler7.mjs +42 -0
  27. package/dist/packem_chunks/handler8.mjs +174 -0
  28. package/dist/packem_chunks/handler9.mjs +16 -0
  29. package/dist/packem_chunks/planDevCommand.mjs +500 -0
  30. package/dist/packem_chunks/runCodegenCommand.mjs +52 -0
  31. package/dist/packem_chunks/runDeployCommand.mjs +504 -0
  32. package/dist/packem_chunks/runInitCommand.mjs +1498 -0
  33. package/dist/packem_chunks/runMigrateGenerateCommand.mjs +397 -0
  34. package/dist/packem_chunks/runResetCommand.mjs +41 -0
  35. package/dist/packem_chunks/runRpcCommand.mjs +68 -0
  36. package/dist/packem_shared/COMMANDS-D3h9Iwvl.mjs +944 -0
  37. package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +244 -0
  38. package/dist/packem_shared/admin-url-4UzT-CI4.mjs +19 -0
  39. package/dist/packem_shared/api-spec-CtA6ilu4.mjs +13 -0
  40. package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +38 -0
  41. package/dist/packem_shared/command-BC30oSBW.mjs +14 -0
  42. package/dist/packem_shared/commands-DPKWlqqX.mjs +812 -0
  43. package/dist/packem_shared/createLogger-B40gPzQo.mjs +78 -0
  44. package/dist/packem_shared/createRecordingSpawner-DxI3mebw.mjs +43 -0
  45. package/dist/packem_shared/detect-package-manager-DYp7n3mJ.mjs +61 -0
  46. package/dist/packem_shared/diffSnapshots-BeDvvNiF.mjs +161 -0
  47. package/dist/packem_shared/docker-hMQ97KSQ.mjs +21 -0
  48. package/dist/packem_shared/insertSchemaExtension-BuzF6-t2.mjs +59 -0
  49. package/dist/packem_shared/open-url-Dfq6fAyT.mjs +41 -0
  50. package/dist/packem_shared/output-format-wUvAN6AL.mjs +17 -0
  51. package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +56 -0
  52. package/dist/packem_shared/parseManifest--vZf2FY1.mjs +94 -0
  53. package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +16 -0
  54. package/dist/packem_shared/runAddCommand-CTRA_JlL.mjs +4 -0
  55. package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +79 -0
  56. package/dist/packem_shared/schemaIrToSnapshot-DdsljJT-.mjs +43 -0
  57. package/dist/packem_shared/storage-2RJBhUC4.mjs +84 -0
  58. package/dist/packem_shared/tui-prompts-DEiPCKV-.mjs +661 -0
  59. package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +12 -0
  60. package/package.json +62 -18
  61. package/skills/README.md +29 -0
  62. package/skills/lunora/SKILL.md +83 -0
  63. package/skills/lunora-create-package/SKILL.md +129 -0
  64. package/skills/lunora-deploy/SKILL.md +150 -0
  65. package/skills/lunora-functions/SKILL.md +182 -0
  66. package/skills/lunora-migration-helper/SKILL.md +194 -0
  67. package/skills/lunora-performance-audit/SKILL.md +143 -0
  68. package/skills/lunora-quickstart/SKILL.md +240 -0
  69. package/skills/lunora-realtime/SKILL.md +177 -0
  70. package/skills/lunora-setup-auth/SKILL.md +170 -0
  71. package/skills/lunora-setup-hyperdrive/SKILL.md +154 -0
  72. package/skills/lunora-setup-hyperdrive-global/SKILL.md +171 -0
  73. package/skills/lunora-setup-mail/SKILL.md +151 -0
  74. package/skills/lunora-setup-scheduler/SKILL.md +157 -0
  75. package/skills/lunora-setup-storage/SKILL.md +158 -0
@@ -0,0 +1,244 @@
1
+ import { createWriteStream, createReadStream } from 'node:fs';
2
+ import { unlink, stat } from 'node:fs/promises';
3
+ import { r as resolveAdminBaseUrl } from './admin-url-4UzT-CI4.mjs';
4
+
5
+ const EXPORT_ENDPOINT_PATH = "/_lunora/admin/export";
6
+ const IMPORT_ENDPOINT_PATH = "/_lunora/admin/import";
7
+ const DEFAULT_IMPORT_BATCH_SIZE = 500;
8
+ const resolveTables = (raw) => {
9
+ if (raw === void 0) {
10
+ return void 0;
11
+ }
12
+ const tables = raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
13
+ return tables.length > 0 ? tables : void 0;
14
+ };
15
+ const writeWithBackpressure = async (sink, line) => {
16
+ if (!sink.write(line)) {
17
+ await new Promise((resolve) => {
18
+ sink.once("drain", resolve);
19
+ });
20
+ }
21
+ };
22
+ const streamNdjsonToSink = async (body, sink) => {
23
+ const reader = body.getReader();
24
+ const decoder = new TextDecoder();
25
+ let bytes = 0;
26
+ let rows = 0;
27
+ let leftover = "";
28
+ let done = false;
29
+ try {
30
+ while (!done) {
31
+ const read = await reader.read();
32
+ done = read.done;
33
+ if (read.value === void 0) {
34
+ continue;
35
+ }
36
+ bytes += read.value.length;
37
+ leftover += decoder.decode(read.value, { stream: true });
38
+ let newlineIndex = leftover.indexOf("\n");
39
+ while (newlineIndex !== -1) {
40
+ rows += 1;
41
+ const line = `${leftover.slice(0, newlineIndex)}
42
+ `;
43
+ await writeWithBackpressure(sink, line);
44
+ leftover = leftover.slice(newlineIndex + 1);
45
+ newlineIndex = leftover.indexOf("\n");
46
+ }
47
+ }
48
+ if (leftover.length > 0) {
49
+ rows += 1;
50
+ await writeWithBackpressure(sink, `${leftover}
51
+ `);
52
+ }
53
+ return { bytes, rows };
54
+ } finally {
55
+ reader.releaseLock();
56
+ }
57
+ };
58
+ const discardPartialExport = async (sink, outPath) => {
59
+ if (outPath === void 0) {
60
+ return;
61
+ }
62
+ sink.destroy();
63
+ try {
64
+ await unlink(outPath);
65
+ } catch {
66
+ }
67
+ };
68
+ const runExportCommand = async (options) => {
69
+ if (options.prod && options.url === void 0) {
70
+ options.logger.error("--prod requires an explicit --url (refusing to export from the implicit localhost worker)");
71
+ return { bytes: 0, code: 1, rows: 0 };
72
+ }
73
+ const token = options.token ?? process.env["LUNORA_ADMIN_TOKEN"];
74
+ if (!token) {
75
+ options.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");
76
+ return { bytes: 0, code: 1, rows: 0 };
77
+ }
78
+ const baseUrl = resolveAdminBaseUrl(options.url, options.logger);
79
+ if (baseUrl === void 0) {
80
+ return { bytes: 0, code: 1, rows: 0 };
81
+ }
82
+ const requestUrl = `${baseUrl}${EXPORT_ENDPOINT_PATH}`;
83
+ const tables = resolveTables(options.tables);
84
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
85
+ if (typeof fetchImpl !== "function") {
86
+ throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");
87
+ }
88
+ options.logger.info(`POST ${requestUrl} -> export${tables ? ` (tables: ${tables.join(",")})` : ""}`);
89
+ const response = await fetchImpl(requestUrl, {
90
+ body: JSON.stringify(tables ? { tables } : {}),
91
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
92
+ method: "POST"
93
+ });
94
+ if (!response.ok) {
95
+ const errorText = await response.text();
96
+ options.logger.error(`export failed: HTTP ${String(response.status)}: ${errorText}`);
97
+ return { bytes: 0, code: 1, rows: 0 };
98
+ }
99
+ if (!response.body) {
100
+ options.logger.error("export response carried no body");
101
+ return { bytes: 0, code: 1, rows: 0 };
102
+ }
103
+ const outPath = options.out === void 0 || options.out === "-" ? void 0 : options.out;
104
+ const sink = outPath === void 0 ? process.stdout : createWriteStream(outPath, { encoding: "utf8" });
105
+ let bytes;
106
+ let rows;
107
+ try {
108
+ ({ bytes, rows } = await streamNdjsonToSink(response.body, sink));
109
+ } catch (error) {
110
+ await discardPartialExport(sink, outPath);
111
+ throw error;
112
+ }
113
+ if (outPath !== void 0) {
114
+ await new Promise((resolve, reject) => {
115
+ sink.end((error) => {
116
+ if (error) {
117
+ reject(error);
118
+ } else {
119
+ resolve();
120
+ }
121
+ });
122
+ });
123
+ options.logger.success(`wrote ${String(rows)} rows to ${outPath} (${String(bytes)} bytes)`);
124
+ }
125
+ return { bytes, code: 0, rows };
126
+ };
127
+ const resolveImportRequest = async (options) => {
128
+ if (options.prod && options.url === void 0) {
129
+ options.logger.error("--prod requires an explicit --url (refusing to import to the implicit localhost worker)");
130
+ return void 0;
131
+ }
132
+ const token = options.token ?? process.env["LUNORA_ADMIN_TOKEN"];
133
+ if (!token) {
134
+ options.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");
135
+ return void 0;
136
+ }
137
+ try {
138
+ const stats = await stat(options.file);
139
+ if (!stats.isFile()) {
140
+ options.logger.error(`not a file: ${options.file}`);
141
+ return void 0;
142
+ }
143
+ } catch (error) {
144
+ const message = error instanceof Error ? error.message : String(error);
145
+ options.logger.error(`failed to stat ${options.file}: ${message}`);
146
+ return void 0;
147
+ }
148
+ const baseUrl = resolveAdminBaseUrl(options.url, options.logger);
149
+ if (baseUrl === void 0) {
150
+ return void 0;
151
+ }
152
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
153
+ if (typeof fetchImpl !== "function") {
154
+ throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");
155
+ }
156
+ return { fetchImpl, requestUrl: `${baseUrl}${IMPORT_ENDPOINT_PATH}`, token };
157
+ };
158
+ const runImportCommand = async (options) => {
159
+ const request = await resolveImportRequest(options);
160
+ if (request === void 0) {
161
+ return { body: void 0, code: 1, inserted: 0 };
162
+ }
163
+ const { fetchImpl, requestUrl, token } = request;
164
+ const batchSize = options.batchSize ?? DEFAULT_IMPORT_BATCH_SIZE;
165
+ options.logger.info(`POST ${requestUrl} -> import ${options.file}`);
166
+ const stream = createReadStream(options.file, { encoding: "utf8" });
167
+ const inserted = {};
168
+ const errors = [];
169
+ let conflicts = 0;
170
+ let buffer = "";
171
+ let batch = [];
172
+ let lineNumber = 0;
173
+ const flush = async () => {
174
+ if (batch.length === 0) {
175
+ return;
176
+ }
177
+ const body2 = batch.join("\n");
178
+ batch = [];
179
+ const response = await fetchImpl(requestUrl, {
180
+ body: body2,
181
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/x-ndjson" },
182
+ method: "POST"
183
+ });
184
+ if (!response.ok) {
185
+ const text = await response.text().catch(() => "<no body>");
186
+ throw new Error(`import batch failed (HTTP ${String(response.status)}): ${text}`);
187
+ }
188
+ const json = await response.json();
189
+ if (json.inserted) {
190
+ for (const [table, count] of Object.entries(json.inserted)) {
191
+ inserted[table] = (inserted[table] ?? 0) + count;
192
+ }
193
+ }
194
+ if (Array.isArray(json.errors)) {
195
+ errors.push(...json.errors);
196
+ }
197
+ if (typeof json.conflicts === "number") {
198
+ conflicts += json.conflicts;
199
+ }
200
+ };
201
+ const processLine = (line) => {
202
+ const trimmed = line.trim();
203
+ if (trimmed.length === 0) {
204
+ return;
205
+ }
206
+ lineNumber += 1;
207
+ if (options.table === void 0) {
208
+ batch.push(trimmed);
209
+ return;
210
+ }
211
+ let parsedDocument;
212
+ try {
213
+ parsedDocument = JSON.parse(trimmed);
214
+ } catch (error) {
215
+ const message = error instanceof Error ? error.message : String(error);
216
+ throw new Error(`invalid JSON on line ${String(lineNumber)}: ${message}`, { cause: error });
217
+ }
218
+ batch.push(JSON.stringify({ doc: parsedDocument, table: options.table }));
219
+ };
220
+ for await (const chunk of stream) {
221
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
222
+ buffer += text;
223
+ let newlineIndex = buffer.indexOf("\n");
224
+ while (newlineIndex !== -1) {
225
+ processLine(buffer.slice(0, newlineIndex));
226
+ buffer = buffer.slice(newlineIndex + 1);
227
+ newlineIndex = buffer.indexOf("\n");
228
+ if (batch.length >= batchSize) {
229
+ await flush();
230
+ }
231
+ }
232
+ }
233
+ if (buffer.length > 0) {
234
+ processLine(buffer);
235
+ }
236
+ await flush();
237
+ const insertedTotal = Object.values(inserted).reduce((a, b) => a + b, 0);
238
+ const body = { conflicts, errors, inserted };
239
+ options.logger.info(JSON.stringify(body, void 0, 2));
240
+ options.logger.success(`imported ${String(insertedTotal)} rows (${String(conflicts)} conflicts, ${String(errors.length)} errors)`);
241
+ return { body, code: errors.length > 0 ? 1 : 0, inserted: insertedTotal };
242
+ };
243
+
244
+ export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand };
@@ -0,0 +1,19 @@
1
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "[::1]", "localhost"]);
2
+ const TRAILING_SLASH = /\/$/u;
3
+ const resolveAdminBaseUrl = (rawUrl, logger) => {
4
+ const candidate = rawUrl ?? "http://localhost:8787";
5
+ let parsed;
6
+ try {
7
+ parsed = new URL(candidate);
8
+ } catch {
9
+ logger.error(`invalid --url: ${candidate}`);
10
+ return void 0;
11
+ }
12
+ if (!LOOPBACK_HOSTS.has(parsed.hostname) && parsed.protocol !== "https:") {
13
+ logger.error(`refusing to send the admin bearer over ${parsed.protocol}// to ${parsed.hostname} — use https for non-localhost targets`);
14
+ return void 0;
15
+ }
16
+ return candidate.replace(TRAILING_SLASH, "");
17
+ };
18
+
19
+ export { resolveAdminBaseUrl as r };
@@ -0,0 +1,13 @@
1
+ const API_SPEC_VALUES = ["both", "none", "openapi", "openrpc"];
2
+ const API_SPEC_HELP = API_SPEC_VALUES.join(" | ");
3
+ const parseApiSpec = (value) => {
4
+ if (typeof value !== "string" || value.length === 0) {
5
+ return void 0;
6
+ }
7
+ if (API_SPEC_VALUES.includes(value)) {
8
+ return value;
9
+ }
10
+ throw new Error(`invalid --api-spec "${value}" — expected one of: ${API_SPEC_HELP}`);
11
+ };
12
+
13
+ export { API_SPEC_HELP as A, parseApiSpec as p };
@@ -0,0 +1,38 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from '@visulima/path';
3
+ import parseManifest from './parseManifest--vZf2FY1.mjs';
4
+
5
+ const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/gu;
6
+ const stripControlChars = (value) => value === void 0 ? void 0 : value.replaceAll(CONTROL_CHARS, "");
7
+ const listItemDirectories = (root) => readdirSync(root).filter((entry) => {
8
+ const full = join(root, entry);
9
+ return statSync(full).isDirectory() && existsSync(join(full, "registry.json"));
10
+ });
11
+ const collectCatalog = (root) => {
12
+ const indexPath = join(root, "index.json");
13
+ if (existsSync(indexPath)) {
14
+ const parsed = JSON.parse(readFileSync(indexPath, "utf8"));
15
+ if (Array.isArray(parsed.items)) {
16
+ return parsed.items.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.name === "string").map((entry) => {
17
+ return { description: stripControlChars(entry.description), name: stripControlChars(entry.name) ?? entry.name };
18
+ });
19
+ }
20
+ }
21
+ return listItemDirectories(root).map((name) => {
22
+ const raw = JSON.parse(readFileSync(join(root, name, "registry.json"), "utf8"));
23
+ return { description: stripControlChars(raw.description), name };
24
+ });
25
+ };
26
+ const buildRegistryIndex = (root) => {
27
+ const items = listItemDirectories(root).map((name) => {
28
+ const manifest = parseManifest(JSON.parse(readFileSync(join(root, name, "registry.json"), "utf8")), name);
29
+ return {
30
+ ...manifest.description === void 0 ? {} : { description: manifest.description },
31
+ name: manifest.name,
32
+ ...manifest.title === void 0 ? {} : { title: manifest.title }
33
+ };
34
+ }).toSorted((a, b) => a.name.localeCompare(b.name));
35
+ return { items };
36
+ };
37
+
38
+ export { buildRegistryIndex, collectCatalog, listItemDirectories };
@@ -0,0 +1,14 @@
1
+ import { createLogger } from './createLogger-B40gPzQo.mjs';
2
+
3
+ const defineHandler = (body) => async (toolbox) => {
4
+ const logger = createLogger();
5
+ try {
6
+ const { code } = await body({ argument: toolbox.argument, cwd: toolbox.process.cwd, logger, options: toolbox.options });
7
+ toolbox.process.exit(code);
8
+ } catch (error) {
9
+ logger.error(error instanceof Error ? error.message : String(error));
10
+ toolbox.process.exit(1);
11
+ }
12
+ };
13
+
14
+ export { defineHandler as d };