@umec/core 0.1.0-alpha.0 → 0.1.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 (53) hide show
  1. package/dist/catalog-5BPLMAW2.js +15 -0
  2. package/dist/catalog-5BPLMAW2.js.map +1 -0
  3. package/dist/checkout.d.ts +10 -3
  4. package/dist/checkout.js +1 -1
  5. package/dist/{chunk-2NYFCO5V.js → chunk-2EBXJT3M.js} +30 -30
  6. package/dist/chunk-2EBXJT3M.js.map +1 -0
  7. package/dist/{chunk-WZFM6R5J.js → chunk-4V4GYE7E.js} +4 -10
  8. package/dist/chunk-4V4GYE7E.js.map +1 -0
  9. package/dist/chunk-CYKUAS3D.js +9 -0
  10. package/dist/chunk-CYKUAS3D.js.map +1 -0
  11. package/dist/chunk-DN3J2NQC.js +298 -0
  12. package/dist/chunk-DN3J2NQC.js.map +1 -0
  13. package/dist/chunk-PNICEHSO.js +255 -0
  14. package/dist/chunk-PNICEHSO.js.map +1 -0
  15. package/dist/{chunk-QRFFBMFN.js → chunk-RFEX32LI.js} +12 -11
  16. package/dist/chunk-RFEX32LI.js.map +1 -0
  17. package/dist/chunk-T2PO3YLZ.js +64 -0
  18. package/dist/chunk-T2PO3YLZ.js.map +1 -0
  19. package/dist/{chunk-REUSLO5I.js → chunk-ZP7IGPJW.js} +26 -4
  20. package/dist/chunk-ZP7IGPJW.js.map +1 -0
  21. package/dist/cli.js +823 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/db/index.d.ts +130 -0
  24. package/dist/db/index.js +81 -0
  25. package/dist/db/index.js.map +1 -0
  26. package/dist/index.d.ts +6 -4
  27. package/dist/index.js +15 -4
  28. package/dist/middleware.d.ts +7 -2
  29. package/dist/middleware.js +1 -1
  30. package/dist/notify.d.ts +7 -8
  31. package/dist/notify.js +1 -1
  32. package/dist/orders-DRZvcL3s.d.ts +99 -0
  33. package/dist/schema-version-F-YYC1UG.d.ts +25 -0
  34. package/dist/sql-J4YBS7N7.js +7 -0
  35. package/dist/sql-J4YBS7N7.js.map +1 -0
  36. package/dist/webhook.d.ts +10 -2
  37. package/dist/webhook.js +1 -1
  38. package/package.json +10 -2
  39. package/schema/frozen-checksums.json +11 -0
  40. package/schema/migrations/0001_processed_events.sql +4 -0
  41. package/schema/migrations/0002_orders.sql +19 -0
  42. package/schema/migrations/0003_inventory.sql +5 -0
  43. package/schema/migrations/0004_admin_audit_log.sql +15 -0
  44. package/schema/migrations/0005_orders_tracking.sql +3 -0
  45. package/schema/migrations/0006_products.sql +15 -0
  46. package/schema/migrations/0007_orders_extras.sql +1 -0
  47. package/schema/snapshot.json +433 -0
  48. package/schema/upgrades/types.ts +16 -0
  49. package/schema/upgrades/v2-to-v6.ts +205 -0
  50. package/dist/chunk-2NYFCO5V.js.map +0 -1
  51. package/dist/chunk-QRFFBMFN.js.map +0 -1
  52. package/dist/chunk-REUSLO5I.js.map +0 -1
  53. package/dist/chunk-WZFM6R5J.js.map +0 -1
package/dist/cli.js ADDED
@@ -0,0 +1,823 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-CYKUAS3D.js";
3
+ import {
4
+ CORE_TABLES,
5
+ TRACKING_TABLES_SQL,
6
+ classifyDrift,
7
+ introspectSnapshot
8
+ } from "./chunk-PNICEHSO.js";
9
+ import {
10
+ currentSchemaVersion,
11
+ findPackageRoot,
12
+ listCoreMigrations,
13
+ loadSnapshotJson
14
+ } from "./chunk-T2PO3YLZ.js";
15
+
16
+ // src/cli/index.ts
17
+ import { readFileSync as readFileSync5 } from "fs";
18
+ import { join as join5 } from "path";
19
+
20
+ // src/cli/doctor.ts
21
+ import { existsSync } from "fs";
22
+ import { join } from "path";
23
+
24
+ // schema/upgrades/v2-to-v6.ts
25
+ var UPGRADE_PAGE_SIZE = 500;
26
+ var V6_ORDERS_DDL = `CREATE TABLE orders_new (
27
+ id TEXT PRIMARY KEY,
28
+ email TEXT NOT NULL,
29
+ name TEXT,
30
+ phone TEXT,
31
+ items_json TEXT NOT NULL,
32
+ amount INTEGER NOT NULL,
33
+ shipping INTEGER,
34
+ address_json TEXT,
35
+ payment_intent TEXT,
36
+ custom_fields_json TEXT,
37
+ status TEXT NOT NULL DEFAULT 'pending',
38
+ run_id TEXT,
39
+ email_status TEXT CHECK (email_status IN ('sent', 'failed')),
40
+ created_at INTEGER NOT NULL,
41
+ shipped_at INTEGER,
42
+ tracking_number TEXT,
43
+ carrier TEXT CHECK (carrier IN ('yamato', 'sagawa', 'japanpost', 'other') OR carrier IS NULL)
44
+ )`;
45
+ function asString(value, fallback = "") {
46
+ return typeof value === "string" ? value : fallback;
47
+ }
48
+ function asNullableString(value) {
49
+ return typeof value === "string" ? value : null;
50
+ }
51
+ function asNumber(value, fallback = 0) {
52
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
53
+ }
54
+ function asNullableNumber(value) {
55
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
56
+ }
57
+ async function detectLegacyOrdersPk(db) {
58
+ const columns = await db.all("PRAGMA table_info(orders)");
59
+ if (columns.length === 0) return false;
60
+ const eventId = columns.find((column) => column.name === "event_id");
61
+ return eventId != null && eventId.pk === 1;
62
+ }
63
+ function mapLegacyOrder(row) {
64
+ const sessionId = asNullableString(row.session_id);
65
+ const eventId = asString(row.event_id);
66
+ return {
67
+ id: sessionId && sessionId.length > 0 ? sessionId : eventId,
68
+ email: asString(row.customer_email ?? row.email),
69
+ name: asNullableString(row.name),
70
+ phone: asNullableString(row.phone),
71
+ items_json: asNullableString(row.items_json) ?? "[]",
72
+ amount: asNumber(row.amount_total ?? row.amount),
73
+ shipping: asNullableNumber(row.shipping),
74
+ address_json: asNullableString(row.address_json),
75
+ payment_intent: asNullableString(row.payment_intent),
76
+ custom_fields_json: asNullableString(row.custom_fields_json),
77
+ status: asNullableString(row.status) ?? "pending",
78
+ run_id: asNullableString(row.run_id),
79
+ email_status: asNullableString(row.email_status),
80
+ created_at: asNumber(row.created_at),
81
+ shipped_at: asNullableNumber(row.shipped_at),
82
+ tracking_number: asNullableString(row.tracking_number),
83
+ carrier: asNullableString(row.carrier)
84
+ };
85
+ }
86
+ async function applyV2toV6(db) {
87
+ if (!await detectLegacyOrdersPk(db)) {
88
+ throw new Error("v2-to-v6: orders.event_id primary key not found; refuse to rebuild");
89
+ }
90
+ await db.exec("DROP TABLE IF EXISTS orders_new");
91
+ await db.exec(V6_ORDERS_DDL);
92
+ let cursor = null;
93
+ for (; ; ) {
94
+ const pageSql = cursor ? "SELECT * FROM orders WHERE event_id > ? ORDER BY event_id LIMIT ?" : "SELECT * FROM orders ORDER BY event_id LIMIT ?";
95
+ const pageParams = cursor ? [cursor, UPGRADE_PAGE_SIZE] : [UPGRADE_PAGE_SIZE];
96
+ const rows = await db.all(pageSql, pageParams);
97
+ if (rows.length === 0) break;
98
+ for (const row of rows) {
99
+ const mapped = mapLegacyOrder(row);
100
+ await db.run(
101
+ `INSERT INTO orders_new (
102
+ id, email, name, phone, items_json, amount, shipping, address_json,
103
+ payment_intent, custom_fields_json, status, run_id, email_status,
104
+ created_at, shipped_at, tracking_number, carrier
105
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
106
+ [
107
+ mapped.id,
108
+ mapped.email,
109
+ mapped.name,
110
+ mapped.phone,
111
+ mapped.items_json,
112
+ mapped.amount,
113
+ mapped.shipping,
114
+ mapped.address_json,
115
+ mapped.payment_intent,
116
+ mapped.custom_fields_json,
117
+ mapped.status,
118
+ mapped.run_id,
119
+ mapped.email_status,
120
+ mapped.created_at,
121
+ mapped.shipped_at,
122
+ mapped.tracking_number,
123
+ mapped.carrier
124
+ ]
125
+ );
126
+ }
127
+ const last = rows[rows.length - 1];
128
+ if (!last) break;
129
+ cursor = last.event_id;
130
+ if (rows.length < UPGRADE_PAGE_SIZE) break;
131
+ }
132
+ const oldCount = await db.first("SELECT COUNT(*) AS n FROM orders");
133
+ const newCount = await db.first("SELECT COUNT(*) AS n FROM orders_new");
134
+ const expected = oldCount?.n ?? -1;
135
+ const copied = newCount?.n ?? -2;
136
+ if (expected !== copied) {
137
+ throw new Error(
138
+ `v2-to-v6: COUNT mismatch orders=${expected} orders_new=${copied}. DROP skipped. Restore from Time Travel bookmark.`
139
+ );
140
+ }
141
+ await db.exec("DROP TABLE orders");
142
+ await db.exec("ALTER TABLE orders_new RENAME TO orders");
143
+ await db.exec("CREATE INDEX IF NOT EXISTS orders_payment_intent_idx ON orders(payment_intent)");
144
+ }
145
+ var upgradeV2toV6 = {
146
+ name: "v2-to-v6",
147
+ fromVersion: 2,
148
+ toVersion: 6,
149
+ description: "Rebuild orders from event_id PK (v2) to id PK (v6)",
150
+ replacesChecksums: ["ca6ec5c885f715a3ae65a7101787d66750ed831370dc2ecde0636cfc3cf83e24"],
151
+ canApply: detectLegacyOrdersPk,
152
+ apply: applyV2toV6
153
+ };
154
+
155
+ // src/cli/checksum.ts
156
+ import { createHash } from "crypto";
157
+ import { readFileSync } from "fs";
158
+ function sha256Bytes(contents) {
159
+ return createHash("sha256").update(contents).digest("hex");
160
+ }
161
+ function sha256File(path) {
162
+ return sha256Bytes(readFileSync(path));
163
+ }
164
+
165
+ // src/cli/doctor.ts
166
+ async function runDoctor(options) {
167
+ const print = options.print ?? (() => {
168
+ });
169
+ const lines = [];
170
+ const installed = currentSchemaVersion();
171
+ let exitCode = 0;
172
+ const say = (line) => {
173
+ lines.push(line);
174
+ print(line);
175
+ };
176
+ say(`installed schema version: ${installed}`);
177
+ if (!options.executor) {
178
+ say("no database executor; offline checks only");
179
+ } else {
180
+ try {
181
+ const version = await options.executor.first(
182
+ "SELECT version, core_version FROM umec_schema_version WHERE id = 1"
183
+ );
184
+ if (!version) {
185
+ say("umec_schema_version: missing (run umec migrate)");
186
+ } else {
187
+ say(`database schema version: ${version.version} (core ${version.core_version})`);
188
+ if (version.version > installed) {
189
+ exitCode = 1;
190
+ say(
191
+ `DB \u306F schema v${version.version}\u3002\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u4E2D\u306E @umec/core \u306F v${installed} \u307E\u3067\u3057\u304B\u77E5\u3089\u306A\u3044\u3002\u623B\u3059\u306B\u306F wrangler d1 time-travel restore --bookmark=<\u8A18\u9332\u6E08\u307F> \u306E\u3042\u3068\u540C\u3058 core \u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u5165\u308C\u76F4\u3059\u3053\u3068\u3002down migration \u306F\u7121\u3044\u3002`
192
+ );
193
+ }
194
+ }
195
+ } catch {
196
+ say("umec_schema_version: unreadable (run umec migrate)");
197
+ }
198
+ const actual = await introspectViaExecutor(options.executor, options.snapshot.version);
199
+ const drift = classifyDrift(options.snapshot, actual);
200
+ for (const finding of drift.compatible) say(`warn: ${finding.message}`);
201
+ for (const finding of drift.incompatible) {
202
+ exitCode = 1;
203
+ say(`error: ${finding.message}`);
204
+ }
205
+ if (await upgradeV2toV6.canApply(options.executor)) {
206
+ exitCode = 1;
207
+ say("error: orders \u306F event_id PK \u306E\u65E7\u30B9\u30AD\u30FC\u30DE\u3067\u3059\u3002umec migrate --repair \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
208
+ }
209
+ const applied = await options.executor.all(
210
+ "SELECT name, checksum FROM umec_migrations"
211
+ ).catch(() => []);
212
+ const appliedByName = new Map(applied.map((row) => [row.name, row.checksum]));
213
+ for (const migration of listCoreMigrations()) {
214
+ const expected = sha256File(migration.path);
215
+ const got = appliedByName.get(migration.name);
216
+ if (got && got !== expected) {
217
+ exitCode = 1;
218
+ say(`error: checksum mismatch ${migration.name}`);
219
+ }
220
+ }
221
+ }
222
+ const extensionsPath = join(options.cwd, "src/config/extensions.ts");
223
+ if (!existsSync(extensionsPath)) {
224
+ say("warn: src/config/extensions.ts \u304C\u3042\u308A\u307E\u305B\u3093\u3002\u62E1\u5F35\u306F umec extend \u3067 extras_json \u306B\u8DB3\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
225
+ }
226
+ return { exitCode, lines };
227
+ }
228
+ async function introspectViaExecutor(executor, version) {
229
+ const db = {
230
+ prepare(query) {
231
+ return {
232
+ bind(...values) {
233
+ this._params = values;
234
+ return this;
235
+ },
236
+ _params: [],
237
+ async first() {
238
+ return executor.first(query, this._params);
239
+ },
240
+ async all() {
241
+ return { results: await executor.all(query, this._params) };
242
+ },
243
+ async run() {
244
+ const result = await executor.run(query, this._params);
245
+ return { meta: { changes: result.changes } };
246
+ }
247
+ };
248
+ },
249
+ async batch() {
250
+ return [];
251
+ }
252
+ };
253
+ return introspectSnapshot(db, version);
254
+ }
255
+
256
+ // src/cli/extend.ts
257
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
258
+ import { dirname, join as join2 } from "path";
259
+ var EXTENSION_TYPES = {
260
+ boolean: "z.boolean().optional()",
261
+ string: "z.string().optional()",
262
+ number: "z.number().optional()"
263
+ };
264
+ var STUB = `import { z } from "zod";
265
+
266
+ export const orderExtensionSchema = z.object({
267
+ });
268
+
269
+ export type OrderExtension = z.infer<typeof orderExtensionSchema>;
270
+ `;
271
+ function isExtendType(value) {
272
+ return value in EXTENSION_TYPES;
273
+ }
274
+ function runExtend(options) {
275
+ const print = options.print ?? (() => {
276
+ });
277
+ const path = join2(options.cwd, "src/config/extensions.ts");
278
+ mkdirSync(dirname(path), { recursive: true });
279
+ if (!existsSync2(path)) {
280
+ writeFileSync(path, STUB);
281
+ print(`created ${path}`);
282
+ }
283
+ const key = options.key;
284
+ if (!key) {
285
+ print("\u62E1\u5F35\u306F orders.extras_json \u306B Zod \u3067\u8DB3\u3059\u3002orders \u3092 ALTER \u3057\u306A\u3044\u3053\u3068\u3002");
286
+ print(`\u7DE8\u96C6\u30D5\u30A1\u30A4\u30EB: ${path}`);
287
+ return { exitCode: 0, path };
288
+ }
289
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
290
+ return { exitCode: 1, path, error: `invalid extras key: ${key}` };
291
+ }
292
+ const typeName = options.type ?? "string";
293
+ if (!isExtendType(typeName)) {
294
+ return { exitCode: 1, path, error: `unsupported type ${typeName} (boolean|string|number)` };
295
+ }
296
+ let source = readFileSync2(path, "utf8");
297
+ if (new RegExp(`\\b${key}:`).test(source)) {
298
+ print(`${key} already exists in ${path}`);
299
+ return { exitCode: 0, path };
300
+ }
301
+ if (!source.includes("orderExtensionSchema")) {
302
+ return { exitCode: 1, path, error: "orderExtensionSchema not found in extensions.ts" };
303
+ }
304
+ source = source.replace(
305
+ /export const orderExtensionSchema = z\.object\(\{\n/,
306
+ `export const orderExtensionSchema = z.object({
307
+ ${key}: ${EXTENSION_TYPES[typeName]},
308
+ `
309
+ );
310
+ writeFileSync(path, source);
311
+ print(`added ${key}: ${typeName} to extras_json via ${path}`);
312
+ print("orders \u30C6\u30FC\u30D6\u30EB\u3092 ALTER \u3057\u306A\u3044\u3053\u3068\u3002\u4E88\u7D04\u5217 extras_json \u3060\u3051\u3092\u4F7F\u3046\u3002");
313
+ return { exitCode: 0, path };
314
+ }
315
+
316
+ // src/cli/migrate.ts
317
+ import { readdirSync, readFileSync as readFileSync3 } from "fs";
318
+ import { join as join3 } from "path";
319
+ var CORE_UPGRADES = [upgradeV2toV6];
320
+ async function ensureTracking(executor, dryRun, print) {
321
+ print("ensure umec_schema_version / umec_migrations");
322
+ if (dryRun) return;
323
+ await executor.applySql(TRACKING_TABLES_SQL);
324
+ }
325
+ async function readApplied(executor) {
326
+ try {
327
+ return await executor.all("SELECT name, checksum, kind FROM umec_migrations");
328
+ } catch {
329
+ return [];
330
+ }
331
+ }
332
+ async function readVersion(executor) {
333
+ try {
334
+ const row = await executor.first(
335
+ "SELECT version FROM umec_schema_version WHERE id = 1"
336
+ );
337
+ return row?.version ?? null;
338
+ } catch {
339
+ return null;
340
+ }
341
+ }
342
+ async function recordMigration(executor, name, checksum, kind, now) {
343
+ await executor.run(
344
+ `INSERT INTO umec_migrations (name, checksum, kind, applied_at) VALUES (?, ?, ?, ?)
345
+ ON CONFLICT(name) DO UPDATE SET checksum = excluded.checksum, kind = excluded.kind, applied_at = excluded.applied_at`,
346
+ [name, checksum, kind, now]
347
+ );
348
+ }
349
+ async function writeVersion(executor, version, coreVersion, now, bookmark) {
350
+ await executor.run(
351
+ `INSERT INTO umec_schema_version (id, version, core_version, applied_at, time_travel_bookmark)
352
+ VALUES (1, ?, ?, ?, ?)
353
+ ON CONFLICT(id) DO UPDATE SET
354
+ version = excluded.version,
355
+ core_version = excluded.core_version,
356
+ applied_at = excluded.applied_at,
357
+ time_travel_bookmark = excluded.time_travel_bookmark`,
358
+ [version, coreVersion, now, bookmark]
359
+ );
360
+ }
361
+ function listLocalMigrations(cwd) {
362
+ const dir = join3(cwd, "migrations", "local");
363
+ try {
364
+ return readdirSync(dir).filter((name) => name.endsWith(".sql")).sort().map((name) => ({
365
+ name: `local/${name}`,
366
+ path: join3(dir, name),
367
+ sql: readFileSync3(join3(dir, name), "utf8")
368
+ }));
369
+ } catch {
370
+ return [];
371
+ }
372
+ }
373
+ async function adoptD1History(executor, now, print) {
374
+ const warnings = [];
375
+ let names = [];
376
+ try {
377
+ const rows = await executor.all("SELECT name FROM d1_migrations");
378
+ names = rows.map((row) => row.name);
379
+ } catch {
380
+ throw new Error("d1_migrations \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002--adopt \u306F wrangler \u7BA1\u7406\u4E0B\u306E\u65E2\u5B58 D1 \u5411\u3051\u3067\u3059\u3002");
381
+ }
382
+ const core = listCoreMigrations();
383
+ const byFile = new Map(core.map((migration) => [migration.name, migration]));
384
+ const adopted = [];
385
+ const legacy = await upgradeV2toV6.canApply(executor);
386
+ for (const rawName of names) {
387
+ const fileName = rawName.split("/").pop() ?? rawName;
388
+ const migration = byFile.get(fileName);
389
+ if (!migration) {
390
+ warnings.push(`d1_migrations \u306E ${rawName} \u306F core \u306B\u7121\u3044\u306E\u3067\u30B9\u30AD\u30C3\u30D7`);
391
+ continue;
392
+ }
393
+ const checksum = fileName === "0002_orders.sql" && legacy ? upgradeV2toV6.replacesChecksums[0] ?? sha256File(migration.path) : sha256File(migration.path);
394
+ await recordMigration(executor, migration.name, checksum, "sql", now);
395
+ adopted.push(migration.name);
396
+ print(`adopt ${migration.name}`);
397
+ }
398
+ return { adopted, warnings };
399
+ }
400
+ async function snapshotFromExecutor(executor, version) {
401
+ const db = {
402
+ prepare(query) {
403
+ return {
404
+ bind(...values) {
405
+ this._params = values;
406
+ return this;
407
+ },
408
+ _params: [],
409
+ async first() {
410
+ return executor.first(query, this._params);
411
+ },
412
+ async all() {
413
+ const results = await executor.all(query, this._params);
414
+ return { results };
415
+ },
416
+ async run() {
417
+ const result = await executor.run(query, this._params);
418
+ return { meta: { changes: result.changes } };
419
+ }
420
+ };
421
+ },
422
+ async batch() {
423
+ return [];
424
+ }
425
+ };
426
+ return introspectSnapshot(db, version);
427
+ }
428
+ async function runMigrate(options) {
429
+ const print = options.print ?? (() => {
430
+ });
431
+ const dryRun = options.dryRun === true;
432
+ const now = options.now ?? Date.now();
433
+ const applied = [];
434
+ const warnings = [];
435
+ const installedVersion = currentSchemaVersion();
436
+ try {
437
+ await ensureTracking(options.executor, dryRun, print);
438
+ const dbVersion = dryRun ? null : await readVersion(options.executor);
439
+ if (dbVersion != null && dbVersion > installedVersion) {
440
+ return {
441
+ exitCode: 1,
442
+ applied,
443
+ warnings,
444
+ error: `DB \u306F schema v${dbVersion}\u3002\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u4E2D\u306E @umec/core \u306F v${installedVersion} \u307E\u3067\u3057\u304B\u77E5\u3089\u306A\u3044\u3002\u623B\u3059\u306B\u306F wrangler d1 time-travel restore --bookmark=<\u8A18\u9332\u6E08\u307F> \u306E\u3042\u3068\u540C\u3058 core \u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u5165\u308C\u76F4\u3059\u3053\u3068\u3002down migration \u306F\u7121\u3044\u3002`
445
+ };
446
+ }
447
+ if (options.adopt) {
448
+ print("adopt d1_migrations \u2192 umec_migrations");
449
+ if (dryRun) {
450
+ return { exitCode: 0, applied: ["--adopt"], warnings };
451
+ }
452
+ const result = await adoptD1History(options.executor, now, print);
453
+ applied.push(...result.adopted);
454
+ warnings.push(...result.warnings);
455
+ const bookmark2 = await options.executor.captureBookmark();
456
+ const maxAdopted = result.adopted.reduce((max, name) => Math.max(max, Number(name.slice(0, 4)) || 0), 0);
457
+ await writeVersion(options.executor, maxAdopted, options.coreVersion, now, bookmark2);
458
+ return { exitCode: 0, applied, warnings, bookmark: bookmark2 };
459
+ }
460
+ if (!dryRun) {
461
+ const actual = await snapshotFromExecutor(options.executor, options.snapshot.version);
462
+ const fresh = CORE_TABLES.every((table) => !actual.tables[table]);
463
+ if (!fresh) {
464
+ const alreadyForDrift = await readApplied(options.executor);
465
+ const pendingSql = listCoreMigrations().filter((migration) => !alreadyForDrift.some((row) => row.name === migration.name)).map((migration) => migration.sql).join("\n");
466
+ const drift = classifyDrift(options.snapshot, actual);
467
+ for (const finding of drift.compatible) {
468
+ warnings.push(finding.message);
469
+ print(`warn: ${finding.message}`);
470
+ }
471
+ const blocking = drift.incompatible.filter((finding) => !pendingMigrationFixes(pendingSql, finding));
472
+ const repairable = options.repair && await upgradeV2toV6.canApply(options.executor);
473
+ if (blocking.length > 0 && !repairable) {
474
+ const details = blocking.map((finding) => finding.message).join("; ");
475
+ const hint = await upgradeV2toV6.canApply(options.executor) ? " umec migrate --repair \u3067 v2-to-v6 \u3092\u63D0\u6848\u3067\u304D\u307E\u3059\u3002" : "";
476
+ return {
477
+ exitCode: 1,
478
+ applied,
479
+ warnings,
480
+ error: `\u975E\u4E92\u63DB drift \u306E\u305F\u3081 migrate \u3092\u62D2\u5426: ${details}.${hint}`
481
+ };
482
+ }
483
+ }
484
+ }
485
+ const already = dryRun ? [] : await readApplied(options.executor);
486
+ const alreadyByName = new Map(already.map((row) => [row.name, row]));
487
+ const coreMigrations = listCoreMigrations();
488
+ for (const migration of coreMigrations) {
489
+ const currentHash = sha256File(migration.path);
490
+ const recorded = alreadyByName.get(migration.name);
491
+ if (recorded && recorded.checksum !== currentHash) {
492
+ const knownLegacy = CORE_UPGRADES.some((upgrade) => upgrade.replacesChecksums.includes(recorded.checksum));
493
+ const canRepair = options.repair && knownLegacy && await upgradeV2toV6.canApply(options.executor);
494
+ if (!canRepair) {
495
+ return {
496
+ exitCode: 1,
497
+ applied,
498
+ warnings,
499
+ error: `\u516C\u958B\u6E08\u307F migration ${migration.name} \u306E checksum \u304C\u98DF\u3044\u9055\u3063\u3066\u3044\u307E\u3059\u3002in-place \u6539\u5909\u306F\u7981\u6B62\u3067\u3059\u3002${knownLegacy ? "umec migrate --repair \u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044\u3002" : ""}`
500
+ };
501
+ }
502
+ }
503
+ }
504
+ let bookmark = null;
505
+ if (!dryRun) {
506
+ bookmark = await options.executor.captureBookmark();
507
+ print(bookmark ? `time-travel bookmark ${bookmark}` : "time-travel bookmark unavailable");
508
+ }
509
+ if (options.repair) {
510
+ for (const upgrade of CORE_UPGRADES) {
511
+ const needed = dryRun ? true : await upgrade.canApply(options.executor);
512
+ if (!needed) continue;
513
+ print(`upgrade ${upgrade.name} (${upgrade.fromVersion} \u2192 ${upgrade.toVersion})`);
514
+ if (dryRun) {
515
+ applied.push(upgrade.name);
516
+ continue;
517
+ }
518
+ await upgrade.apply(options.executor);
519
+ await recordMigration(options.executor, upgrade.name, `upgrade:${upgrade.name}`, "upgrade", now);
520
+ applied.push(upgrade.name);
521
+ alreadyByName.set(upgrade.name, { name: upgrade.name, checksum: `upgrade:${upgrade.name}`, kind: "upgrade" });
522
+ for (const supersededName of ["0002_orders.sql", "0005_orders_tracking.sql"]) {
523
+ const superseded = coreMigrations.find((migration) => migration.name === supersededName);
524
+ if (!superseded) {
525
+ throw new Error(`upgradeV2toV6 supersedes unknown migration ${supersededName} \u2014 schema/migrations/ drifted`);
526
+ }
527
+ const checksum = sha256File(superseded.path);
528
+ await recordMigration(options.executor, superseded.name, checksum, "sql", now);
529
+ alreadyByName.set(superseded.name, { name: superseded.name, checksum, kind: "sql" });
530
+ }
531
+ }
532
+ }
533
+ for (const migration of coreMigrations) {
534
+ if (alreadyByName.get(migration.name)?.checksum === sha256File(migration.path)) continue;
535
+ if (alreadyByName.has(migration.name) && !options.repair) continue;
536
+ print(`apply core/${migration.name}`);
537
+ if (dryRun) {
538
+ applied.push(migration.name);
539
+ continue;
540
+ }
541
+ await options.executor.applySql(migration.sql);
542
+ await recordMigration(options.executor, migration.name, sha256File(migration.path), "sql", now);
543
+ applied.push(migration.name);
544
+ }
545
+ for (const local of listLocalMigrations(options.cwd)) {
546
+ const hash = sha256File(local.path);
547
+ if (alreadyByName.get(local.name)?.checksum === hash) continue;
548
+ print(`apply ${local.name}`);
549
+ if (dryRun) {
550
+ applied.push(local.name);
551
+ continue;
552
+ }
553
+ await options.executor.applySql(local.sql);
554
+ await recordMigration(options.executor, local.name, hash, "sql", now);
555
+ applied.push(local.name);
556
+ }
557
+ if (!dryRun) {
558
+ await writeVersion(options.executor, installedVersion, options.coreVersion, now, bookmark);
559
+ } else {
560
+ print(`would set umec_schema_version.version = ${installedVersion}`);
561
+ }
562
+ return { exitCode: 0, applied, warnings, bookmark };
563
+ } catch (error) {
564
+ const message = error instanceof Error ? error.message : String(error);
565
+ return { exitCode: 1, applied, warnings, error: message };
566
+ }
567
+ }
568
+ function pendingMigrationFixes(pendingSql, finding) {
569
+ if (finding.message.includes(`required table "${finding.table}" is missing`)) {
570
+ return new RegExp(`CREATE TABLE(?: IF NOT EXISTS)?\\s+${finding.table}\\b`, "i").test(pendingSql);
571
+ }
572
+ const missingColumn = finding.message.match(/required column "([^."]+)\.([^"]+)" is missing/);
573
+ if (missingColumn?.[1] && missingColumn[2]) {
574
+ const [, table, column] = missingColumn;
575
+ return new RegExp(`ALTER TABLE\\s+${table}\\s+ADD COLUMN\\s+${column}\\b`, "i").test(pendingSql);
576
+ }
577
+ return false;
578
+ }
579
+ function coreVersionFromPackage(root = findPackageRoot()) {
580
+ const pkg = JSON.parse(readFileSync3(join3(root, "package.json"), "utf8"));
581
+ return pkg.version;
582
+ }
583
+
584
+ // src/cli/wrangler.ts
585
+ import { spawn } from "child_process";
586
+ import { readFileSync as readFileSync4 } from "fs";
587
+ import { join as join4 } from "path";
588
+
589
+ // src/cli/executor.ts
590
+ function sqlLiteral(value) {
591
+ if (value === null || value === void 0) return "NULL";
592
+ if (typeof value === "boolean") return value ? "1" : "0";
593
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
594
+ if (typeof value === "string") return `'${value.replaceAll("'", "''")}'`;
595
+ throw new Error(`Unsupported SQL bind: ${typeof value}`);
596
+ }
597
+ function bindSql(sql, params = []) {
598
+ let index = 0;
599
+ return sql.replaceAll("?", () => {
600
+ if (index >= params.length) throw new Error("Not enough SQL bind parameters");
601
+ return sqlLiteral(params[index++]);
602
+ });
603
+ }
604
+
605
+ // src/cli/wrangler.ts
606
+ function defaultRunCommand(command, args, cwd) {
607
+ return new Promise((resolve, reject) => {
608
+ const child = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
609
+ let stdout = "";
610
+ let stderr = "";
611
+ child.stdout.on("data", (chunk) => {
612
+ stdout += chunk.toString();
613
+ });
614
+ child.stderr.on("data", (chunk) => {
615
+ stderr += chunk.toString();
616
+ });
617
+ child.on("error", reject);
618
+ child.on("close", (code) => {
619
+ resolve({ code: code ?? 1, stdout, stderr });
620
+ });
621
+ });
622
+ }
623
+ function parseJsonc(text) {
624
+ const stripped = text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
625
+ return JSON.parse(stripped);
626
+ }
627
+ function readD1DatabaseName(cwd, override) {
628
+ if (override) return override;
629
+ const path = join4(cwd, "wrangler.jsonc");
630
+ try {
631
+ const parsed = parseJsonc(readFileSync4(path, "utf8"));
632
+ return parsed.d1_databases?.[0]?.database_name ?? null;
633
+ } catch {
634
+ return null;
635
+ }
636
+ }
637
+ function parseExecuteJson(stdout) {
638
+ const trimmed = stdout.trim();
639
+ if (!trimmed) return { results: [], changes: 0 };
640
+ const start = trimmed.indexOf("[");
641
+ const objStart = trimmed.indexOf("{");
642
+ const cut = start >= 0 && (objStart < 0 || start < objStart) ? trimmed.slice(start) : objStart >= 0 ? trimmed.slice(objStart) : trimmed;
643
+ const parsed = JSON.parse(cut);
644
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
645
+ const first = rows[0];
646
+ return {
647
+ results: first?.results ?? [],
648
+ changes: first?.meta?.changes ?? 0
649
+ };
650
+ }
651
+ function createWranglerExecutor(options) {
652
+ const runCommand = options.runCommand ?? defaultRunCommand;
653
+ const location = options.remote ? "--remote" : "--local";
654
+ const execute = async (sql) => {
655
+ const result = await runCommand(
656
+ "wrangler",
657
+ ["d1", "execute", options.database, location, "--json", "--command", sql],
658
+ options.cwd
659
+ );
660
+ if (result.code !== 0) {
661
+ throw new Error(result.stderr || result.stdout || `wrangler d1 execute failed (${result.code})`);
662
+ }
663
+ return parseExecuteJson(result.stdout);
664
+ };
665
+ return {
666
+ async exec(sql) {
667
+ await execute(sql);
668
+ },
669
+ async applySql(sql) {
670
+ const { splitSqlStatements: splitSqlStatements2 } = await import("./sql-J4YBS7N7.js");
671
+ for (const statement of splitSqlStatements2(sql)) {
672
+ await execute(statement);
673
+ }
674
+ },
675
+ async all(sql, params = []) {
676
+ const result = await execute(bindSql(sql, params));
677
+ return result.results;
678
+ },
679
+ async first(sql, params = []) {
680
+ const result = await execute(bindSql(sql, params));
681
+ return result.results[0] ?? null;
682
+ },
683
+ async run(sql, params = []) {
684
+ const result = await execute(bindSql(sql, params));
685
+ return { changes: result.changes };
686
+ },
687
+ async captureBookmark() {
688
+ const result = await runCommand(
689
+ "wrangler",
690
+ ["d1", "time-travel", "info", options.database, "--json"],
691
+ options.cwd
692
+ );
693
+ if (result.code !== 0) return null;
694
+ try {
695
+ const parsed = JSON.parse(result.stdout);
696
+ return parsed.bookmark ?? null;
697
+ } catch {
698
+ return null;
699
+ }
700
+ }
701
+ };
702
+ }
703
+
704
+ // src/cli/index.ts
705
+ function parseArgs(argv) {
706
+ const flags = {};
707
+ let command = null;
708
+ for (let i = 0; i < argv.length; i += 1) {
709
+ const arg = argv[i];
710
+ if (!arg) continue;
711
+ if (arg.startsWith("--")) {
712
+ const eq = arg.indexOf("=");
713
+ if (eq > 0) {
714
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
715
+ continue;
716
+ }
717
+ const name = arg.slice(2);
718
+ const next = argv[i + 1];
719
+ if (next && !next.startsWith("--") && name !== "dry-run" && name !== "adopt" && name !== "repair" && name !== "remote" && name !== "local") {
720
+ flags[name] = next;
721
+ i += 1;
722
+ } else {
723
+ flags[name] = true;
724
+ }
725
+ continue;
726
+ }
727
+ if (!command) command = arg;
728
+ }
729
+ return { command, flags };
730
+ }
731
+ function usage() {
732
+ return `Usage:
733
+ umec migrate [--dry-run] [--local|--remote] [--adopt] [--repair] [--database NAME]
734
+ umec doctor [--local|--remote] [--database NAME]
735
+ umec extend [--key NAME] [--type boolean|string|number]`;
736
+ }
737
+ async function runCli(argv, io = {}) {
738
+ const cwd = io.cwd ?? process.cwd();
739
+ const stdout = io.stdout ?? ((message) => console.log(message));
740
+ const stderr = io.stderr ?? ((message) => console.error(message));
741
+ const { command, flags } = parseArgs(argv);
742
+ if (!command || command === "help" || flags.help) {
743
+ stdout(usage());
744
+ return command ? 0 : 1;
745
+ }
746
+ const snapshot = loadSnapshotJson();
747
+ const remote = flags.remote === true;
748
+ const database = typeof flags.database === "string" ? flags.database : readD1DatabaseName(cwd);
749
+ const executor = io.executor ?? (command === "extend" || command === "migrate" && flags["dry-run"] === true ? void 0 : database ? createWranglerExecutor({ cwd, database, remote, runCommand: io.runCommand }) : void 0);
750
+ if (command === "migrate") {
751
+ if (flags["dry-run"] === true && !io.executor) {
752
+ const migrations = (await import("./catalog-5BPLMAW2.js")).listCoreMigrations();
753
+ stdout("dry-run (no D1 connection)");
754
+ stdout("ensure umec_schema_version / umec_migrations");
755
+ for (const migration of migrations) stdout(`apply core/${migration.name}`);
756
+ stdout(`would set umec_schema_version.version = ${snapshot.version}`);
757
+ return 0;
758
+ }
759
+ if (!executor) {
760
+ stderr("wrangler.jsonc \u306E d1_databases[0].database_name \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002--database \u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
761
+ return 1;
762
+ }
763
+ const options = {
764
+ cwd,
765
+ executor,
766
+ snapshot,
767
+ dryRun: flags["dry-run"] === true,
768
+ adopt: flags.adopt === true,
769
+ repair: flags.repair === true,
770
+ coreVersion: coreVersionFromPackage(),
771
+ print: stdout
772
+ };
773
+ const result = await runMigrate(options);
774
+ for (const warning of result.warnings) stdout(`warn: ${warning}`);
775
+ if (result.error) stderr(result.error);
776
+ return result.exitCode;
777
+ }
778
+ if (command === "doctor") {
779
+ const result = await runDoctor({
780
+ cwd,
781
+ executor,
782
+ snapshot,
783
+ print: stdout
784
+ });
785
+ return result.exitCode;
786
+ }
787
+ if (command === "extend") {
788
+ const result = runExtend({
789
+ cwd,
790
+ key: typeof flags.key === "string" ? flags.key : void 0,
791
+ type: typeof flags.type === "string" ? flags.type : void 0,
792
+ print: stdout
793
+ });
794
+ if (result.error) stderr(result.error);
795
+ return result.exitCode;
796
+ }
797
+ stderr(usage());
798
+ return 1;
799
+ }
800
+ function isDirectRun() {
801
+ const invoked = process.argv[1];
802
+ if (!invoked) return false;
803
+ return /(?:^|[\\/])cli(?:\.js)?$/.test(invoked) || invoked.includes(`${join5("dist", "cli")}`);
804
+ }
805
+ if (isDirectRun()) {
806
+ const pkg = JSON.parse(readFileSync5(join5(findPackageRoot(), "package.json"), "utf8"));
807
+ if (process.argv.includes("--version") || process.argv.includes("-V")) {
808
+ console.log(pkg.version);
809
+ process.exit(0);
810
+ }
811
+ runCli(process.argv.slice(2)).then(
812
+ (code) => process.exit(code),
813
+ (error) => {
814
+ console.error(error instanceof Error ? error.message : error);
815
+ process.exit(1);
816
+ }
817
+ );
818
+ }
819
+ export {
820
+ parseArgs,
821
+ runCli
822
+ };
823
+ //# sourceMappingURL=cli.js.map