@lunora/cli 0.0.0 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +105 -0
- package/README.md +109 -9
- package/__assets__/package-og.svg +14 -0
- package/dist/bin.mjs +11 -0
- package/dist/index.d.mts +852 -0
- package/dist/index.d.ts +852 -0
- package/dist/index.mjs +19 -0
- package/dist/packem_chunks/handler.mjs +76 -0
- package/dist/packem_chunks/handler10.mjs +22 -0
- package/dist/packem_chunks/handler11.mjs +192 -0
- package/dist/packem_chunks/handler12.mjs +131 -0
- package/dist/packem_chunks/handler13.mjs +65 -0
- package/dist/packem_chunks/handler14.mjs +58 -0
- package/dist/packem_chunks/handler15.mjs +79 -0
- package/dist/packem_chunks/handler16.mjs +41 -0
- package/dist/packem_chunks/handler17.mjs +105 -0
- package/dist/packem_chunks/handler18.mjs +172 -0
- package/dist/packem_chunks/handler19.mjs +89 -0
- package/dist/packem_chunks/handler2.mjs +114 -0
- package/dist/packem_chunks/handler20.mjs +94 -0
- package/dist/packem_chunks/handler21.mjs +311 -0
- package/dist/packem_chunks/handler3.mjs +204 -0
- package/dist/packem_chunks/handler4.mjs +33 -0
- package/dist/packem_chunks/handler5.mjs +49 -0
- package/dist/packem_chunks/handler6.mjs +91 -0
- package/dist/packem_chunks/handler7.mjs +42 -0
- package/dist/packem_chunks/handler8.mjs +174 -0
- package/dist/packem_chunks/handler9.mjs +16 -0
- package/dist/packem_chunks/planDevCommand.mjs +543 -0
- package/dist/packem_chunks/runCodegenCommand.mjs +52 -0
- package/dist/packem_chunks/runDeployCommand.mjs +504 -0
- package/dist/packem_chunks/runInitCommand.mjs +652 -0
- package/dist/packem_chunks/runMigrateGenerateCommand.mjs +397 -0
- package/dist/packem_chunks/runResetCommand.mjs +41 -0
- package/dist/packem_chunks/runRpcCommand.mjs +68 -0
- package/dist/packem_shared/COMMANDS-1V_KEx35.mjs +905 -0
- package/dist/packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs +244 -0
- package/dist/packem_shared/admin-url-4UzT-CI4.mjs +19 -0
- package/dist/packem_shared/api-spec-CtA6ilu4.mjs +13 -0
- package/dist/packem_shared/buildRegistryIndex-BcYe607_.mjs +38 -0
- package/dist/packem_shared/command-BDXcJCCJ.mjs +14 -0
- package/dist/packem_shared/createLogger-CHPNjFw2.mjs +73 -0
- package/dist/packem_shared/defaultSpawner-DxI3mebw.mjs +43 -0
- package/dist/packem_shared/diffSnapshots-RR2ZE8Ya.mjs +161 -0
- package/dist/packem_shared/docker-hMQ97KSQ.mjs +21 -0
- package/dist/packem_shared/features-ocSSpZtS.mjs +24 -0
- package/dist/packem_shared/insertSchemaExtension-BuzF6-t2.mjs +59 -0
- package/dist/packem_shared/open-url-Dfq6fAyT.mjs +41 -0
- package/dist/packem_shared/output-format-7gyGR3h8.mjs +17 -0
- package/dist/packem_shared/parseArgs-YXFuKdEk.mjs +56 -0
- package/dist/packem_shared/parseManifest--vZf2FY1.mjs +94 -0
- package/dist/packem_shared/resolve-target-qbsJ_5sF.mjs +16 -0
- package/dist/packem_shared/runAddCommand-BZGkRnBs.mjs +693 -0
- package/dist/packem_shared/schema-drift-gate-BtBt0as0.mjs +79 -0
- package/dist/packem_shared/schemaIrToSnapshot-aBTo7TM5.mjs +43 -0
- package/dist/packem_shared/wrangler-name-cy4yhm9j.mjs +12 -0
- package/package.json +61 -18
- package/skills/README.md +29 -0
- package/skills/lunora/SKILL.md +83 -0
- package/skills/lunora-create-package/SKILL.md +129 -0
- package/skills/lunora-deploy/SKILL.md +150 -0
- package/skills/lunora-functions/SKILL.md +182 -0
- package/skills/lunora-migration-helper/SKILL.md +194 -0
- package/skills/lunora-performance-audit/SKILL.md +143 -0
- package/skills/lunora-quickstart/SKILL.md +240 -0
- package/skills/lunora-realtime/SKILL.md +177 -0
- package/skills/lunora-setup-auth/SKILL.md +170 -0
- package/skills/lunora-setup-hyperdrive/SKILL.md +154 -0
- package/skills/lunora-setup-hyperdrive-global/SKILL.md +171 -0
- package/skills/lunora-setup-mail/SKILL.md +151 -0
- package/skills/lunora-setup-scheduler/SKILL.md +157 -0
- package/skills/lunora-setup-storage/SKILL.md +154 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { discoverMigrations, discoverSchema } from '@lunora/codegen';
|
|
4
|
+
import { join } from '@visulima/path';
|
|
5
|
+
import { Project } from 'ts-morph';
|
|
6
|
+
import { r as resolveAdminBaseUrl } from '../packem_shared/admin-url-4UzT-CI4.mjs';
|
|
7
|
+
import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
|
|
8
|
+
import { diffSnapshots, renderMigrationFile } from '../packem_shared/diffSnapshots-RR2ZE8Ya.mjs';
|
|
9
|
+
import { a as resolveProductionWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
|
|
10
|
+
import schemaIrToSnapshot from '../packem_shared/schemaIrToSnapshot-aBTo7TM5.mjs';
|
|
11
|
+
import { runExportCommand, runImportCommand } from '../packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
|
|
12
|
+
|
|
13
|
+
const SNAPSHOT_FILENAME = ".snapshot.json";
|
|
14
|
+
const NON_ALPHANUMERIC = /[^\da-z]+/gu;
|
|
15
|
+
const trimChar = (value, char) => {
|
|
16
|
+
let start = 0;
|
|
17
|
+
let end = value.length;
|
|
18
|
+
while (start < end && value[start] === char) {
|
|
19
|
+
start += 1;
|
|
20
|
+
}
|
|
21
|
+
while (end > start && value[end - 1] === char) {
|
|
22
|
+
end -= 1;
|
|
23
|
+
}
|
|
24
|
+
return value.slice(start, end);
|
|
25
|
+
};
|
|
26
|
+
const slugify = (input) => {
|
|
27
|
+
const slug = trimChar(input.toLowerCase().replaceAll(NON_ALPHANUMERIC, "_"), "_");
|
|
28
|
+
return slug === "" ? "auto" : slug;
|
|
29
|
+
};
|
|
30
|
+
const formatTimestamp = (now) => {
|
|
31
|
+
const pad = (n, w = 2) => n.toString().padStart(w, "0");
|
|
32
|
+
return `${String(now.getUTCFullYear())}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}${pad(now.getUTCHours())}${pad(
|
|
33
|
+
now.getUTCMinutes()
|
|
34
|
+
)}${pad(now.getUTCSeconds())}`;
|
|
35
|
+
};
|
|
36
|
+
const loadSnapshot = (path) => {
|
|
37
|
+
if (!existsSync(path)) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const raw = readFileSync(path, "utf8");
|
|
42
|
+
const parsed = JSON.parse(raw);
|
|
43
|
+
if (parsed.version !== 1) {
|
|
44
|
+
throw new Error(`unsupported snapshot version: ${parsed.version}`);
|
|
45
|
+
}
|
|
46
|
+
return parsed;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
+
throw new Error(`failed to read ${path}: ${message}`, { cause: error });
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const runMigrateGenerateCommand = (options) => {
|
|
53
|
+
const cwd = options.cwd ?? process.cwd();
|
|
54
|
+
const schemaPath = join(cwd, "lunora", "schema.ts");
|
|
55
|
+
if (!existsSync(schemaPath)) {
|
|
56
|
+
options.logger.error(`schema not found: ${schemaPath} — run \`vis generate lunora-table --name=<name>\` to create one`);
|
|
57
|
+
return { code: 1, empty: true, migrationFile: "" };
|
|
58
|
+
}
|
|
59
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
60
|
+
const schemaIr = discoverSchema(project, schemaPath);
|
|
61
|
+
const nextSnapshot = schemaIrToSnapshot(schemaIr);
|
|
62
|
+
const migrationsDirectory = join(cwd, "lunora", "migrations");
|
|
63
|
+
const snapshotPath = join(migrationsDirectory, SNAPSHOT_FILENAME);
|
|
64
|
+
let previousSnapshot;
|
|
65
|
+
try {
|
|
66
|
+
previousSnapshot = loadSnapshot(snapshotPath);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
options.logger.error(error instanceof Error ? error.message : String(error));
|
|
69
|
+
return { code: 1, empty: true, migrationFile: "" };
|
|
70
|
+
}
|
|
71
|
+
const diff = diffSnapshots(previousSnapshot, nextSnapshot);
|
|
72
|
+
if (diff.empty) {
|
|
73
|
+
options.logger.info("no schema changes detected — snapshot is already up to date");
|
|
74
|
+
return { code: 0, empty: true, migrationFile: "" };
|
|
75
|
+
}
|
|
76
|
+
const nowFunction = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
77
|
+
const now = nowFunction();
|
|
78
|
+
const slug = slugify(options.name ?? "auto");
|
|
79
|
+
const timestamp = formatTimestamp(now);
|
|
80
|
+
const filename = `${timestamp}_${slug}.sql`;
|
|
81
|
+
const migrationFile = join(migrationsDirectory, filename);
|
|
82
|
+
mkdirSync(migrationsDirectory, { recursive: true });
|
|
83
|
+
const body = renderMigrationFile(slug, diff, now.toISOString());
|
|
84
|
+
writeFileSync(migrationFile, body, "utf8");
|
|
85
|
+
writeFileSync(snapshotPath, `${JSON.stringify(nextSnapshot, void 0, 4)}
|
|
86
|
+
`, "utf8");
|
|
87
|
+
options.logger.success(`wrote ${migrationFile}`);
|
|
88
|
+
if (diff.unsupported.length > 0) {
|
|
89
|
+
options.logger.warn(`${String(diff.unsupported.length)} unsupported diff(s) — see the comment block in ${filename} and write the SQL manually`);
|
|
90
|
+
}
|
|
91
|
+
return { code: 0, empty: false, migrationFile };
|
|
92
|
+
};
|
|
93
|
+
const DATA_MIGRATIONS_FILENAME = "migrations.ts";
|
|
94
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_]\w*$/u;
|
|
95
|
+
const RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
96
|
+
"await",
|
|
97
|
+
"break",
|
|
98
|
+
"case",
|
|
99
|
+
"catch",
|
|
100
|
+
"class",
|
|
101
|
+
"const",
|
|
102
|
+
"continue",
|
|
103
|
+
"debugger",
|
|
104
|
+
"default",
|
|
105
|
+
"delete",
|
|
106
|
+
"do",
|
|
107
|
+
"else",
|
|
108
|
+
"enum",
|
|
109
|
+
"export",
|
|
110
|
+
"extends",
|
|
111
|
+
"false",
|
|
112
|
+
"finally",
|
|
113
|
+
"for",
|
|
114
|
+
"function",
|
|
115
|
+
"if",
|
|
116
|
+
"implements",
|
|
117
|
+
"import",
|
|
118
|
+
"in",
|
|
119
|
+
"instanceof",
|
|
120
|
+
"interface",
|
|
121
|
+
"let",
|
|
122
|
+
"new",
|
|
123
|
+
"null",
|
|
124
|
+
"package",
|
|
125
|
+
"private",
|
|
126
|
+
"protected",
|
|
127
|
+
"public",
|
|
128
|
+
"return",
|
|
129
|
+
"static",
|
|
130
|
+
"super",
|
|
131
|
+
"switch",
|
|
132
|
+
"this",
|
|
133
|
+
"throw",
|
|
134
|
+
"true",
|
|
135
|
+
"try",
|
|
136
|
+
"typeof",
|
|
137
|
+
"var",
|
|
138
|
+
"void",
|
|
139
|
+
"while",
|
|
140
|
+
"with",
|
|
141
|
+
"yield"
|
|
142
|
+
]);
|
|
143
|
+
const DEFINE_MIGRATION_IMPORT = `import { defineMigration } from "@lunora/server";`;
|
|
144
|
+
const RUN_MIGRATION_OP = "__lunora_admin__:runMigration";
|
|
145
|
+
const MIGRATION_STATUS_OP = "__lunora_admin__:migrationStatus";
|
|
146
|
+
const MIGRATE_ENDPOINT_PATH = "/_lunora/migrate";
|
|
147
|
+
const kebabCase = (input) => trimChar(input.trim().toLowerCase().replaceAll(NON_ALPHANUMERIC, "-"), "-");
|
|
148
|
+
const camelCase = (slug) => slug.split("-").filter((part) => part.length > 0).map((part, index) => {
|
|
149
|
+
if (index === 0) {
|
|
150
|
+
return part;
|
|
151
|
+
}
|
|
152
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
153
|
+
}).join("");
|
|
154
|
+
const runMigrateCreateCommand = (options) => {
|
|
155
|
+
const cwd = options.cwd ?? process.cwd();
|
|
156
|
+
const slug = kebabCase(options.name);
|
|
157
|
+
if (slug === "") {
|
|
158
|
+
options.logger.error(`invalid migration name: "${options.name}" — must contain at least one alphanumeric character`);
|
|
159
|
+
return { code: 1, file: "" };
|
|
160
|
+
}
|
|
161
|
+
const exportName = camelCase(slug);
|
|
162
|
+
if (!IDENTIFIER_PATTERN.test(exportName) || RESERVED_WORDS.has(exportName)) {
|
|
163
|
+
options.logger.error(
|
|
164
|
+
`invalid migration name: "${options.name}" derives the export \`${exportName}\`, which is not a valid identifier — pick a name that starts with a letter and isn't a reserved word`
|
|
165
|
+
);
|
|
166
|
+
return { code: 1, file: "" };
|
|
167
|
+
}
|
|
168
|
+
const table = options.table ?? "TODO_table";
|
|
169
|
+
if (!IDENTIFIER_PATTERN.test(table)) {
|
|
170
|
+
options.logger.error(`invalid --table: "${table}" — must be a valid identifier ([A-Za-z_][A-Za-z0-9_]*)`);
|
|
171
|
+
return { code: 1, file: "" };
|
|
172
|
+
}
|
|
173
|
+
const lunoraDirectory = join(cwd, "lunora");
|
|
174
|
+
const file = join(lunoraDirectory, DATA_MIGRATIONS_FILENAME);
|
|
175
|
+
let content = existsSync(file) ? readFileSync(file, "utf8") : "";
|
|
176
|
+
if (content.includes(`id: "${slug}"`) || new RegExp(String.raw`\bexport const ${exportName}\b`, "u").test(content)) {
|
|
177
|
+
options.logger.error(`a migration with id "${slug}" (export \`${exportName}\`) already exists in ${file}`);
|
|
178
|
+
return { code: 1, file: "" };
|
|
179
|
+
}
|
|
180
|
+
if (content.trim() === "") {
|
|
181
|
+
content = `${DEFINE_MIGRATION_IMPORT}
|
|
182
|
+
`;
|
|
183
|
+
} else if (!content.includes(DEFINE_MIGRATION_IMPORT)) {
|
|
184
|
+
content = `${DEFINE_MIGRATION_IMPORT}
|
|
185
|
+
${content}`;
|
|
186
|
+
}
|
|
187
|
+
const block = `export const ${exportName} = defineMigration({
|
|
188
|
+
id: "${slug}",
|
|
189
|
+
table: "${table}",
|
|
190
|
+
up: (document) => document,
|
|
191
|
+
});`;
|
|
192
|
+
mkdirSync(lunoraDirectory, { recursive: true });
|
|
193
|
+
writeFileSync(file, `${content.trimEnd()}
|
|
194
|
+
|
|
195
|
+
${block}
|
|
196
|
+
`, "utf8");
|
|
197
|
+
options.logger.success(`scaffolded migration "${slug}" in ${file}`);
|
|
198
|
+
if (options.table === void 0) {
|
|
199
|
+
options.logger.warn(`set the \`table\` field on "${slug}" — it defaults to "${table}"`);
|
|
200
|
+
}
|
|
201
|
+
return { code: 0, file };
|
|
202
|
+
};
|
|
203
|
+
const resolveMigrationTable = (cwd, id) => {
|
|
204
|
+
const project = new Project({ skipAddingFilesFromTsConfig: true });
|
|
205
|
+
const migrations = discoverMigrations(project, join(cwd, "lunora"));
|
|
206
|
+
return migrations.find((migration) => migration.id === id)?.table;
|
|
207
|
+
};
|
|
208
|
+
const resolveValidatedTable = (cwd, options) => {
|
|
209
|
+
let table;
|
|
210
|
+
try {
|
|
211
|
+
table = resolveMigrationTable(cwd, options.id);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
options.logger.error(error instanceof Error ? error.message : String(error));
|
|
214
|
+
return void 0;
|
|
215
|
+
}
|
|
216
|
+
if (table === void 0) {
|
|
217
|
+
options.logger.error(`migration "${options.id}" not found under lunora/ — declare it with defineMigration({ id: "${options.id}", ... })`);
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
if (table === "") {
|
|
221
|
+
options.logger.error(`migration "${options.id}" must declare \`table\` as a static string literal`);
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
return table;
|
|
225
|
+
};
|
|
226
|
+
const resolveMigrateDataRequest = (options) => {
|
|
227
|
+
const cwd = options.cwd ?? process.cwd();
|
|
228
|
+
if (options.prod && options.url === void 0) {
|
|
229
|
+
options.logger.error("--prod requires an explicit --url (refusing to migrate the implicit localhost worker)");
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
if (options.prod && (options.subcommand === "up" || options.subcommand === "down") && !options.yes) {
|
|
233
|
+
options.logger.error(`migrate ${options.subcommand} --prod runs the migration against production. Re-run with --yes to confirm.`);
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
const token = options.token ?? process.env.LUNORA_ADMIN_TOKEN;
|
|
237
|
+
if (!token) {
|
|
238
|
+
options.logger.error("admin token required — pass --token or set LUNORA_ADMIN_TOKEN");
|
|
239
|
+
return void 0;
|
|
240
|
+
}
|
|
241
|
+
const table = resolveValidatedTable(cwd, options);
|
|
242
|
+
if (table === void 0) {
|
|
243
|
+
return void 0;
|
|
244
|
+
}
|
|
245
|
+
const baseUrl = resolveAdminBaseUrl(options.url, options.logger);
|
|
246
|
+
if (baseUrl === void 0) {
|
|
247
|
+
return void 0;
|
|
248
|
+
}
|
|
249
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
250
|
+
if (typeof fetchImpl !== "function") {
|
|
251
|
+
throw new TypeError("no fetch implementation available — pass fetchImpl or run on Node >= 18");
|
|
252
|
+
}
|
|
253
|
+
return { fetchImpl, requestUrl: `${baseUrl}${MIGRATE_ENDPOINT_PATH}`, table, token };
|
|
254
|
+
};
|
|
255
|
+
const buildMigrateArgs = (options) => {
|
|
256
|
+
const args = { id: options.id };
|
|
257
|
+
if (options.subcommand === "status") {
|
|
258
|
+
return args;
|
|
259
|
+
}
|
|
260
|
+
args.direction = options.subcommand;
|
|
261
|
+
if (options.dryRun) {
|
|
262
|
+
args.dryRun = true;
|
|
263
|
+
}
|
|
264
|
+
if (options.batchSize !== void 0) {
|
|
265
|
+
args.batchSize = options.batchSize;
|
|
266
|
+
}
|
|
267
|
+
if (options.maxBatches !== void 0) {
|
|
268
|
+
args.maxBatches = options.maxBatches;
|
|
269
|
+
}
|
|
270
|
+
return args;
|
|
271
|
+
};
|
|
272
|
+
const runMigrateDataCommand = async (options) => {
|
|
273
|
+
const request = resolveMigrateDataRequest(options);
|
|
274
|
+
if (request === void 0) {
|
|
275
|
+
return { body: void 0, code: 1, requestUrl: "" };
|
|
276
|
+
}
|
|
277
|
+
const { fetchImpl, requestUrl, table, token } = request;
|
|
278
|
+
const functionPath = options.subcommand === "status" ? MIGRATION_STATUS_OP : RUN_MIGRATION_OP;
|
|
279
|
+
const args = buildMigrateArgs(options);
|
|
280
|
+
options.logger.info(`POST ${requestUrl} -> ${options.subcommand} ${options.id} (table "${table}")`);
|
|
281
|
+
const response = await fetchImpl(requestUrl, {
|
|
282
|
+
body: JSON.stringify({ args, functionPath, table }),
|
|
283
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
284
|
+
method: "POST"
|
|
285
|
+
});
|
|
286
|
+
const text = await response.text();
|
|
287
|
+
let body;
|
|
288
|
+
try {
|
|
289
|
+
body = JSON.parse(text);
|
|
290
|
+
} catch {
|
|
291
|
+
body = text;
|
|
292
|
+
}
|
|
293
|
+
options.logger.info(JSON.stringify(body, void 0, 2));
|
|
294
|
+
return { body, code: response.ok ? 0 : 1, requestUrl };
|
|
295
|
+
};
|
|
296
|
+
const runMigrateToHyperdriveCommand = async (options) => {
|
|
297
|
+
const { logger } = options;
|
|
298
|
+
const fromUrl = options.fromUrl ?? options.toUrl;
|
|
299
|
+
const toUrl = options.toUrl ?? options.fromUrl;
|
|
300
|
+
if (fromUrl !== void 0 && fromUrl === toUrl) {
|
|
301
|
+
logger.error(
|
|
302
|
+
"source and target are the same deployment — pass distinct --from-url and --to-url so the D1 export and Hyperdrive import don't run against one database"
|
|
303
|
+
);
|
|
304
|
+
return { code: 1 };
|
|
305
|
+
}
|
|
306
|
+
const temporaryDirectory = options.out === void 0 ? mkdtempSync(join(tmpdir(), "lunora-d1ps-")) : void 0;
|
|
307
|
+
const dumpPath = options.out ?? join(temporaryDirectory, "dump.ndjson");
|
|
308
|
+
logger.info(`Exporting .global() data from the D1 source (${fromUrl ?? "http://localhost:8787"}) …`);
|
|
309
|
+
const exportResult = await runExportCommand({
|
|
310
|
+
fetchImpl: options.fetchImpl,
|
|
311
|
+
logger,
|
|
312
|
+
out: dumpPath,
|
|
313
|
+
prod: options.prod,
|
|
314
|
+
tables: options.tables,
|
|
315
|
+
token: options.fromToken,
|
|
316
|
+
url: fromUrl
|
|
317
|
+
});
|
|
318
|
+
if (exportResult.code !== 0) {
|
|
319
|
+
return { code: exportResult.code };
|
|
320
|
+
}
|
|
321
|
+
logger.info(`Exported ${String(exportResult.rows)} row(s) (${String(exportResult.bytes)} bytes).`);
|
|
322
|
+
logger.info(`Importing into the Hyperdrive target (${toUrl ?? "http://localhost:8787"}) …`);
|
|
323
|
+
const importResult = await runImportCommand({
|
|
324
|
+
batchSize: options.batchSize,
|
|
325
|
+
fetchImpl: options.fetchImpl,
|
|
326
|
+
file: dumpPath,
|
|
327
|
+
logger,
|
|
328
|
+
prod: options.prod,
|
|
329
|
+
token: options.toToken,
|
|
330
|
+
url: toUrl
|
|
331
|
+
});
|
|
332
|
+
if (temporaryDirectory !== void 0) {
|
|
333
|
+
rmSync(temporaryDirectory, { force: true, recursive: true });
|
|
334
|
+
}
|
|
335
|
+
if (importResult.code !== 0) {
|
|
336
|
+
return { code: importResult.code };
|
|
337
|
+
}
|
|
338
|
+
if (importResult.inserted === exportResult.rows) {
|
|
339
|
+
logger.info(`✓ Migrated ${String(exportResult.rows)} row(s) — counts match. Verify your app reads from Hyperdrive, then decommission the D1 binding.`);
|
|
340
|
+
} else {
|
|
341
|
+
logger.warn(
|
|
342
|
+
`Imported ${String(importResult.inserted)} of ${String(exportResult.rows)} exported row(s) — the remainder likely already existed in the target (see conflicts above). Re-run after resolving, or inspect the dump with --out.`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return { code: 0 };
|
|
346
|
+
};
|
|
347
|
+
const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
348
|
+
const sub = argument[0];
|
|
349
|
+
if (sub === "generate") {
|
|
350
|
+
return runMigrateGenerateCommand({ cwd, logger, name: argument[1] ?? options.name });
|
|
351
|
+
}
|
|
352
|
+
if (sub === "d1-to-hyperdrive") {
|
|
353
|
+
return runMigrateToHyperdriveCommand({
|
|
354
|
+
batchSize: options.batchSize,
|
|
355
|
+
fromToken: options.fromToken ?? options.token,
|
|
356
|
+
fromUrl: options.fromUrl ?? options.url,
|
|
357
|
+
logger,
|
|
358
|
+
out: options.out,
|
|
359
|
+
prod: options.prod === true,
|
|
360
|
+
tables: options.tables,
|
|
361
|
+
toToken: options.toToken ?? options.token,
|
|
362
|
+
toUrl: options.toUrl ?? options.url
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
if (sub === "create") {
|
|
366
|
+
const name = argument[1] ?? options.name;
|
|
367
|
+
if (!name) {
|
|
368
|
+
logger.error("migrate create requires a name. Usage: lunora migrate create <name> [--table <table>]");
|
|
369
|
+
return { code: 1 };
|
|
370
|
+
}
|
|
371
|
+
return runMigrateCreateCommand({ cwd, logger, name, table: options.table });
|
|
372
|
+
}
|
|
373
|
+
if (sub === "up" || sub === "down" || sub === "status") {
|
|
374
|
+
const id = argument[1] ?? options.name;
|
|
375
|
+
if (!id) {
|
|
376
|
+
logger.error(`migrate ${sub} requires a migration id. Usage: lunora migrate ${sub} <id>`);
|
|
377
|
+
return { code: 1 };
|
|
378
|
+
}
|
|
379
|
+
return runMigrateDataCommand({
|
|
380
|
+
batchSize: options.batchSize,
|
|
381
|
+
cwd,
|
|
382
|
+
dryRun: options.dryRun === true,
|
|
383
|
+
id,
|
|
384
|
+
logger,
|
|
385
|
+
maxBatches: options.steps,
|
|
386
|
+
prod: options.prod === true,
|
|
387
|
+
subcommand: sub,
|
|
388
|
+
token: options.token,
|
|
389
|
+
url: resolveProductionWorkerUrl({ cwd, prod: options.prod === true, url: options.url }),
|
|
390
|
+
yes: options.yes === true
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
logger.error(`unknown migrate subcommand: "${sub ?? ""}" — expected generate | create | up | down | status`);
|
|
394
|
+
return { code: 1 };
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
export { execute, runMigrateCreateCommand, runMigrateDataCommand, runMigrateGenerateCommand, runMigrateToHyperdriveCommand };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
2
|
+
import { promptYesNo } from '@lunora/config';
|
|
3
|
+
import { join } from '@visulima/path';
|
|
4
|
+
import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
|
|
5
|
+
|
|
6
|
+
const runResetCommand = async (options) => {
|
|
7
|
+
const cwd = options.cwd ?? process.cwd();
|
|
8
|
+
const targets = [join(cwd, ".wrangler", "state")];
|
|
9
|
+
if (options.all) {
|
|
10
|
+
targets.push(join(cwd, ".lunora-cache"));
|
|
11
|
+
}
|
|
12
|
+
if (!options.yes) {
|
|
13
|
+
const isTty = process.stdin.isTTY;
|
|
14
|
+
if (!isTty && options.confirm === void 0) {
|
|
15
|
+
options.logger.error("reset: stdin is not a TTY — re-run with --yes to confirm deleting .wrangler/state");
|
|
16
|
+
return { code: 1, removed: [] };
|
|
17
|
+
}
|
|
18
|
+
const confirmer = options.confirm ?? promptYesNo;
|
|
19
|
+
const confirmed = await confirmer("This will delete .wrangler/state. Continue? [y/N] ");
|
|
20
|
+
if (!confirmed) {
|
|
21
|
+
options.logger.info("reset: aborted");
|
|
22
|
+
return { code: 1, removed: [] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const removed = [];
|
|
26
|
+
for (const target of targets) {
|
|
27
|
+
if (existsSync(target)) {
|
|
28
|
+
rmSync(target, { force: true, recursive: true });
|
|
29
|
+
removed.push(target);
|
|
30
|
+
options.logger.success(`removed ${target}`);
|
|
31
|
+
} else {
|
|
32
|
+
options.logger.info(`skipped (not present): ${target}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { code: 0, removed };
|
|
36
|
+
};
|
|
37
|
+
const execute = defineHandler(
|
|
38
|
+
({ cwd, logger, options }) => runResetCommand({ all: options.all === true, cwd, logger, yes: options.yes === true })
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export { execute, runResetCommand };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
|
|
2
|
+
import { r as resolveWorkerUrl } from '../packem_shared/resolve-target-qbsJ_5sF.mjs';
|
|
3
|
+
|
|
4
|
+
const parseArgsJson = (raw) => {
|
|
5
|
+
if (raw === void 0 || raw.length === 0) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(raw);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
throw new Error(`failed to parse --args as JSON: ${message}`, { cause: error });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const TRAILING_SLASH = /\/$/u;
|
|
16
|
+
const runRpcCommand = async (options) => {
|
|
17
|
+
const resolvedUrl = resolveWorkerUrl({ cwd: options.cwd ?? process.cwd(), url: options.url });
|
|
18
|
+
const baseUrl = (resolvedUrl ?? "http://localhost:8787").replace(TRAILING_SLASH, "");
|
|
19
|
+
const requestUrl = `${baseUrl}/_lunora/rpc`;
|
|
20
|
+
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
21
|
+
if (typeof fetchImpl !== "function") {
|
|
22
|
+
throw new TypeError("no fetch implementation available — pass --fetch via dependency injection or run on Node >= 18");
|
|
23
|
+
}
|
|
24
|
+
let parsedArgs;
|
|
25
|
+
try {
|
|
26
|
+
parsedArgs = parseArgsJson(options.args);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
options.logger.error(message);
|
|
30
|
+
return { body: void 0, code: 1, requestUrl };
|
|
31
|
+
}
|
|
32
|
+
const payload = {
|
|
33
|
+
args: parsedArgs,
|
|
34
|
+
functionPath: options.functionPath
|
|
35
|
+
};
|
|
36
|
+
if (options.shard !== void 0) {
|
|
37
|
+
payload.shardKey = options.shard;
|
|
38
|
+
}
|
|
39
|
+
options.logger.info(`POST ${requestUrl} -> ${options.functionPath}`);
|
|
40
|
+
const response = await fetchImpl(requestUrl, {
|
|
41
|
+
body: JSON.stringify(payload),
|
|
42
|
+
headers: { "content-type": "application/json" },
|
|
43
|
+
method: "POST"
|
|
44
|
+
});
|
|
45
|
+
const text = await response.text();
|
|
46
|
+
let body;
|
|
47
|
+
try {
|
|
48
|
+
body = JSON.parse(text);
|
|
49
|
+
} catch {
|
|
50
|
+
body = text;
|
|
51
|
+
}
|
|
52
|
+
options.logger.info(JSON.stringify(body, void 0, 2));
|
|
53
|
+
return {
|
|
54
|
+
body,
|
|
55
|
+
code: response.ok ? 0 : 1,
|
|
56
|
+
requestUrl
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
60
|
+
const functionPath = argument[0];
|
|
61
|
+
if (!functionPath) {
|
|
62
|
+
logger.error("missing function path. Usage: lunora run <functionPath> [--args <json>]");
|
|
63
|
+
return { code: 1 };
|
|
64
|
+
}
|
|
65
|
+
return runRpcCommand({ args: options.args, cwd, functionPath, logger, shard: options.shard, url: options.url });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export { execute, runRpcCommand };
|