@korajs/cli 1.0.0-beta.12 → 1.0.0-beta.14
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/README.md +25 -107
- package/dist/bin.cjs +808 -340
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +588 -174
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-G2PBYMMA.js → chunk-7Y4J2GXP.js} +168 -153
- package/dist/chunk-7Y4J2GXP.js.map +1 -0
- package/dist/{chunk-Z7GCXMGW.js → chunk-BURFQVMV.js} +44 -9
- package/dist/chunk-BURFQVMV.js.map +1 -0
- package/dist/{chunk-4IKCHRJ6.js → chunk-TVIRMDER.js} +122 -112
- package/dist/chunk-TVIRMDER.js.map +1 -0
- package/dist/create.cjs +63 -28
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +41 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +8 -2
- package/dist/{studio-server-NIFKZI4F.js → studio-server-XLGZNGG5.js} +11 -2
- package/dist/studio-server-XLGZNGG5.js.map +1 -0
- package/dist/vite.cjs +351 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +161 -0
- package/dist/vite.d.ts +161 -0
- package/dist/vite.js +321 -0
- package/dist/vite.js.map +1 -0
- package/package.json +18 -8
- package/templates/react-basic/AGENTS.md +5 -5
- package/templates/react-basic/vite.config.ts +4 -1
- package/templates/react-sync/.env.example +1 -1
- package/templates/react-sync/AGENTS.md +5 -5
- package/templates/react-sync/server.ts +8 -0
- package/templates/react-sync/src/vite-env.d.ts +1 -0
- package/templates/react-sync/vite.config.ts +4 -1
- package/templates/react-tailwind/AGENTS.md +5 -5
- package/templates/react-tailwind/vite.config.ts +10 -1
- package/templates/react-tailwind-sync/.env.example +1 -1
- package/templates/react-tailwind-sync/AGENTS.md +9 -7
- package/templates/react-tailwind-sync/server.ts +8 -0
- package/templates/react-tailwind-sync/src/App.tsx +1 -1
- package/templates/react-tailwind-sync/src/kora.ts +32 -0
- package/templates/react-tailwind-sync/src/main.tsx +1 -23
- package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +4 -8
- package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +2 -2
- package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +1 -1
- package/templates/react-tailwind-sync/src/vite-env.d.ts +1 -0
- package/templates/react-tailwind-sync/vite.config.ts +10 -1
- package/templates/svelte-basic/AGENTS.md +4 -4
- package/templates/svelte-basic/package.json.hbs +6 -0
- package/templates/svelte-basic/src/App.svelte +2 -1
- package/templates/svelte-basic/vite.config.ts +4 -1
- package/templates/svelte-sync/.env.example +1 -1
- package/templates/svelte-sync/AGENTS.md +4 -4
- package/templates/svelte-sync/server.ts +8 -0
- package/templates/svelte-sync/src/App.svelte +2 -1
- package/templates/svelte-sync/vite.config.ts +4 -1
- package/templates/svelte-tailwind/AGENTS.md +4 -4
- package/templates/svelte-tailwind/package.json.hbs +7 -1
- package/templates/svelte-tailwind/src/App.svelte +2 -1
- package/templates/svelte-tailwind/vite.config.ts +10 -1
- package/templates/svelte-tailwind-sync/.env.example +1 -1
- package/templates/svelte-tailwind-sync/AGENTS.md +4 -4
- package/templates/svelte-tailwind-sync/package.json.hbs +1 -1
- package/templates/svelte-tailwind-sync/server.ts +8 -0
- package/templates/svelte-tailwind-sync/src/App.svelte +2 -1
- package/templates/svelte-tailwind-sync/vite.config.ts +10 -1
- package/templates/tauri-react/.env.example +1 -1
- package/templates/tauri-react/AGENTS.md +5 -5
- package/templates/tauri-react/README.md.hbs +2 -2
- package/templates/tauri-react/package.json.hbs +1 -1
- package/templates/tauri-react/server.ts +10 -2
- package/templates/tauri-react/src/vite-env.d.ts +1 -0
- package/templates/vue-basic/AGENTS.md +4 -4
- package/templates/vue-basic/package.json.hbs +6 -0
- package/templates/vue-basic/vite.config.ts +4 -1
- package/templates/vue-sync/.env.example +1 -1
- package/templates/vue-sync/AGENTS.md +4 -4
- package/templates/vue-sync/server.ts +8 -0
- package/templates/vue-sync/vite.config.ts +4 -1
- package/templates/vue-tailwind/AGENTS.md +4 -4
- package/templates/vue-tailwind/package.json.hbs +6 -0
- package/templates/vue-tailwind/vite.config.ts +4 -1
- package/templates/vue-tailwind-sync/.env.example +1 -1
- package/templates/vue-tailwind-sync/AGENTS.md +4 -4
- package/templates/vue-tailwind-sync/server.ts +8 -0
- package/templates/vue-tailwind-sync/vite.config.ts +4 -1
- package/dist/chunk-4IKCHRJ6.js.map +0 -1
- package/dist/chunk-G2PBYMMA.js.map +0 -1
- package/dist/chunk-Z7GCXMGW.js.map +0 -1
- package/dist/studio-server-NIFKZI4F.js.map +0 -1
package/dist/bin.cjs
CHANGED
|
@@ -251,6 +251,15 @@ function parseJsonColumn(raw) {
|
|
|
251
251
|
return null;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
|
+
function stripOpMetadata(data) {
|
|
255
|
+
if (data === null) return null;
|
|
256
|
+
const fields = {};
|
|
257
|
+
for (const [key, value] of Object.entries(data)) {
|
|
258
|
+
if (key.startsWith("__kora_") && key.endsWith("__")) continue;
|
|
259
|
+
fields[key] = value;
|
|
260
|
+
}
|
|
261
|
+
return Object.keys(fields).length > 0 ? fields : null;
|
|
262
|
+
}
|
|
254
263
|
function parseJsonArrayColumn(raw) {
|
|
255
264
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
256
265
|
return [];
|
|
@@ -464,7 +473,7 @@ var init_db_reader = __esm({
|
|
|
464
473
|
nodeId: String(row.node_id),
|
|
465
474
|
type: String(row.type),
|
|
466
475
|
recordId: String(row.record_id),
|
|
467
|
-
data: parseJsonColumn(row.data),
|
|
476
|
+
data: stripOpMetadata(parseJsonColumn(row.data)),
|
|
468
477
|
previousData: parseJsonColumn(row.previous_data),
|
|
469
478
|
timestamp: parseVersionStamp(row.timestamp),
|
|
470
479
|
sequenceNumber: Number(row.sequence_number),
|
|
@@ -1981,13 +1990,13 @@ var spectator_manager_exports = {};
|
|
|
1981
1990
|
__export(spectator_manager_exports, {
|
|
1982
1991
|
SpectatorManager: () => SpectatorManager
|
|
1983
1992
|
});
|
|
1984
|
-
var import_node_fs7, import_node_os,
|
|
1993
|
+
var import_node_fs7, import_node_os, import_node_path29, FORWARDED_EVENTS, MAX_EVENT_BUFFER, SpectatorManager;
|
|
1985
1994
|
var init_spectator_manager = __esm({
|
|
1986
1995
|
"src/commands/studio/spectator-manager.ts"() {
|
|
1987
1996
|
"use strict";
|
|
1988
1997
|
import_node_fs7 = require("fs");
|
|
1989
1998
|
import_node_os = require("os");
|
|
1990
|
-
|
|
1999
|
+
import_node_path29 = require("path");
|
|
1991
2000
|
FORWARDED_EVENTS = [
|
|
1992
2001
|
"merge:started",
|
|
1993
2002
|
"merge:conflict",
|
|
@@ -2001,8 +2010,8 @@ var init_spectator_manager = __esm({
|
|
|
2001
2010
|
SpectatorManager = class {
|
|
2002
2011
|
constructor(options) {
|
|
2003
2012
|
this.options = options;
|
|
2004
|
-
this.tmpDir = (0, import_node_fs7.mkdtempSync)((0,
|
|
2005
|
-
this.dbPath = (0,
|
|
2013
|
+
this.tmpDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path29.join)((0, import_node_os.tmpdir)(), "kora-studio-spectator-"));
|
|
2014
|
+
this.dbPath = (0, import_node_path29.join)(this.tmpDir, "spectator-replica.db");
|
|
2006
2015
|
}
|
|
2007
2016
|
options;
|
|
2008
2017
|
runtime = null;
|
|
@@ -2185,13 +2194,13 @@ function summarizeEvent(type, payload) {
|
|
|
2185
2194
|
return type.replace("lab:", "");
|
|
2186
2195
|
}
|
|
2187
2196
|
}
|
|
2188
|
-
var import_node_fs8, import_node_os2,
|
|
2197
|
+
var import_node_fs8, import_node_os2, import_node_path30, import_core3, DEFAULT_CHAOS, FORWARDED_EVENTS2, MAX_EVENT_BUFFER2, LabManager;
|
|
2189
2198
|
var init_lab_manager = __esm({
|
|
2190
2199
|
"src/commands/studio/lab-manager.ts"() {
|
|
2191
2200
|
"use strict";
|
|
2192
2201
|
import_node_fs8 = require("fs");
|
|
2193
2202
|
import_node_os2 = require("os");
|
|
2194
|
-
|
|
2203
|
+
import_node_path30 = require("path");
|
|
2195
2204
|
import_core3 = require("@korajs/core");
|
|
2196
2205
|
DEFAULT_CHAOS = {
|
|
2197
2206
|
dropRate: 0,
|
|
@@ -2249,7 +2258,7 @@ var init_lab_manager = __esm({
|
|
|
2249
2258
|
}
|
|
2250
2259
|
async start(initialDevices = 2) {
|
|
2251
2260
|
const harness = await this.loadHarness();
|
|
2252
|
-
this.tmpDir = (0, import_node_fs8.mkdtempSync)((0,
|
|
2261
|
+
this.tmpDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path30.join)((0, import_node_os2.tmpdir)(), "kora-studio-lab-"));
|
|
2253
2262
|
this.server = new harness.TestServer(this.schema);
|
|
2254
2263
|
for (let i = 0; i < initialDevices; i++) {
|
|
2255
2264
|
await this.addDevice();
|
|
@@ -2301,7 +2310,7 @@ var init_lab_manager = __esm({
|
|
|
2301
2310
|
nodeId: entry.device.getNodeId(),
|
|
2302
2311
|
connected: entry.device.isConnected(),
|
|
2303
2312
|
chaos: { ...entry.chaos },
|
|
2304
|
-
dbPath: (0,
|
|
2313
|
+
dbPath: (0, import_node_path30.join)(this.tmpDir ?? "", `test-device-${name}.db`),
|
|
2305
2314
|
pendingOperations: entry.device.getSyncEngine()?.getStatus().pendingOperations ?? 0
|
|
2306
2315
|
};
|
|
2307
2316
|
}
|
|
@@ -2502,7 +2511,7 @@ await addTodo.mutateAsync({ title: 'x' }) // resolves with the result; throw
|
|
|
2502
2511
|
const status = useSyncStatus() // status.status, status.pendingOperations
|
|
2503
2512
|
\`\`\`
|
|
2504
2513
|
|
|
2505
|
-
Rules: always render \`mutation.error\` somewhere. Keep \`<StrictMode>\` in place; the hooks are StrictMode-safe. \`useQuery\` returns
|
|
2514
|
+
Rules: always render \`mutation.error\` somewhere. Keep \`<StrictMode>\` in place; the hooks are StrictMode-safe. \`useQuery\` reads the local store: no network, no spinner. Its very first render returns \`[]\` before the local query has run; use \`useQueryState\` when you must tell "loading" from "no rows".`,
|
|
2506
2515
|
vue: `## Vue bindings
|
|
2507
2516
|
|
|
2508
2517
|
\`\`\`ts
|
|
@@ -2515,7 +2524,7 @@ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in
|
|
|
2515
2524
|
const status = useSyncStatus() // status.status, status.pendingOperations
|
|
2516
2525
|
\`\`\`
|
|
2517
2526
|
|
|
2518
|
-
Rules: surface the mutation's error state in the UI. \`useQuery\` reads
|
|
2527
|
+
Rules: surface the mutation's error state in the UI. \`useQuery\` reads the local store (no network, no spinner); the ref holds \`[]\` until the first local result. The Kora context comes from \`KoraProvider\`, wired in your app entry (for example \`src/main.ts\`).`,
|
|
2519
2528
|
svelte: `## Svelte bindings
|
|
2520
2529
|
|
|
2521
2530
|
Kora's Svelte bindings from \`@korajs/svelte\` expose \`useCollection\`, \`useQuery\`, \`useMutation\`, and \`useSyncStatus\`, wired through the provider set up in your app entry.
|
|
@@ -2530,7 +2539,7 @@ addTodo.mutate({ title: 'x' }) // fire-and-forget; errors land in
|
|
|
2530
2539
|
const status = useSyncStatus() // readable store; read $status.status in markup
|
|
2531
2540
|
\`\`\`
|
|
2532
2541
|
|
|
2533
|
-
Rules: queries are reactive stores
|
|
2542
|
+
Rules: queries are reactive stores over the local database (no network, no spinner; \`[]\` until the first local result). Mutations expose an \`error\` state that must be surfaced in the UI.`
|
|
2534
2543
|
};
|
|
2535
2544
|
function generateAgentsMd(framework) {
|
|
2536
2545
|
return `# AGENTS.md
|
|
@@ -2547,9 +2556,9 @@ This project uses **Kora.js**, an offline-first application framework. All appli
|
|
|
2547
2556
|
2. **Never fetch application data over HTTP.** Do not add REST or GraphQL calls for app data, and do not talk to the sync server directly. Read and write through Kora collections only; sync happens automatically in the background.
|
|
2548
2557
|
3. **Await readiness before direct collection access.** Outside the UI bindings, \`await app.ready\` before calling \`app.<collection>\` methods. The framework bindings handle this for you inside components.
|
|
2549
2558
|
4. **Offline must keep working.** Any feature you add must function with the network off. Never gate a read or write on connectivity. If you are checking \`navigator.onLine\` before a data operation, you are doing it wrong.
|
|
2550
|
-
5. **Surface mutation errors.** Fire-and-forget \`mutate\` calls
|
|
2559
|
+
5. **Surface mutation errors.** Fire-and-forget \`mutate\` calls put errors into the mutation state. Always render the mutation's \`error\`, or handle the promise from the \`mutateAsync\` variant. Silent failure is the worst failure.
|
|
2551
2560
|
6. **Do not add a state library for server or app data.** Kora's reactive queries are the store. Do not reach for react-query, SWR, Redux, Zustand, or similar for data that lives in a collection. Local UI state (form inputs, toggles) can use your framework's normal state tools.
|
|
2552
|
-
7. **Do not add loading
|
|
2561
|
+
7. **Do not add network-style loading states for local reads.** \`useQuery\` reads the local database, which answers within a frame; its very first render returns an empty result. Use \`useQueryState\` only where "loading" and "no rows" must look different.
|
|
2553
2562
|
|
|
2554
2563
|
## Data API cheat sheet
|
|
2555
2564
|
|
|
@@ -2587,15 +2596,15 @@ export default defineSchema({
|
|
|
2587
2596
|
})
|
|
2588
2597
|
\`\`\`
|
|
2589
2598
|
|
|
2590
|
-
If you change collection
|
|
2599
|
+
If you change a collection's shape, increment \`version\` and add a \`migrations\` entry for it (\`migrate().addField(...)\`): each device migrates its local database on its next open. \`npx kora migrate\` generates the matching migration for server databases and a transform stub for clients still on the older version.
|
|
2591
2600
|
|
|
2592
2601
|
## Conflict handling
|
|
2593
2602
|
|
|
2594
|
-
Concurrent edits merge automatically: last-write-wins per field,
|
|
2603
|
+
Concurrent edits merge automatically: last-write-wins per field, arrays as multisets that keep every device's additions and removals, objects per top-level key, and character-level CRDT for \`t.richtext()\` fields. When a field needs domain-specific merging (counters, quantities), add a \`resolve\` function in the schema rather than writing sync logic by hand. Never write your own conflict or merge code.
|
|
2595
2604
|
|
|
2596
2605
|
## Sync and auth
|
|
2597
2606
|
|
|
2598
|
-
Enable sync
|
|
2607
|
+
Enable sync with one option on \`createApp\`: \`createApp({ schema, sync: { url, autoConnect: true } })\` (without \`autoConnect\`, call \`await app.sync?.connect()\` after \`app.ready\`). When the app is offline, writes queue locally and sync when connectivity returns. If the project uses \`@korajs/auth\`, pass its binding as \`createApp({ schema, sync: { url, authClient: createKoraAuthSync({ authClient, schema }) } })\`; the server grants what each user may sync. Local writes work without sign-in; sync requires the server to accept the connection.
|
|
2599
2608
|
|
|
2600
2609
|
${FRAMEWORK_BINDINGS[framework]}
|
|
2601
2610
|
|
|
@@ -2603,7 +2612,7 @@ ${FRAMEWORK_BINDINGS[framework]}
|
|
|
2603
2612
|
|
|
2604
2613
|
- \`npx kora dev\` runs the dev environment (app server, local sync server when configured, schema watcher).
|
|
2605
2614
|
- \`npx kora doctor\` diagnoses a broken setup.
|
|
2606
|
-
- \`npx kora migrate\`
|
|
2615
|
+
- \`npx kora migrate\` generates a migration from a schema change (\`--apply\` runs it against a SQLite or Postgres database).
|
|
2607
2616
|
- In a running app, the DevTools overlay (Ctrl+Shift+K, Cmd+Shift+K on macOS) inspects operations, merges, and sync status.
|
|
2608
2617
|
`;
|
|
2609
2618
|
}
|
|
@@ -2844,6 +2853,20 @@ var import_citty3 = require("citty");
|
|
|
2844
2853
|
|
|
2845
2854
|
// src/errors.ts
|
|
2846
2855
|
var import_core = require("@korajs/core");
|
|
2856
|
+
var DeployPlatformUnavailableError = class extends import_core.KoraError {
|
|
2857
|
+
constructor(platform, available) {
|
|
2858
|
+
super(
|
|
2859
|
+
`Deploying to "${platform}" is coming soon and not available yet. Choose one of: ${available.join(", ")}. To self-host, run the production server (\`pnpm build && pnpm start\`) in any Node 20+ environment.`,
|
|
2860
|
+
"DEPLOY_PLATFORM_UNAVAILABLE",
|
|
2861
|
+
{ platform, available }
|
|
2862
|
+
);
|
|
2863
|
+
this.platform = platform;
|
|
2864
|
+
this.available = available;
|
|
2865
|
+
this.name = "DeployPlatformUnavailableError";
|
|
2866
|
+
}
|
|
2867
|
+
platform;
|
|
2868
|
+
available;
|
|
2869
|
+
};
|
|
2847
2870
|
var ProjectExistsError = class extends import_core.KoraError {
|
|
2848
2871
|
constructor(directory) {
|
|
2849
2872
|
super(
|
|
@@ -3065,7 +3088,7 @@ var compactCommand = (0, import_citty3.defineCommand)({
|
|
|
3065
3088
|
// src/commands/create/create-command.ts
|
|
3066
3089
|
var import_node_child_process4 = require("child_process");
|
|
3067
3090
|
var import_node_fs2 = require("fs");
|
|
3068
|
-
var
|
|
3091
|
+
var import_node_path11 = require("path");
|
|
3069
3092
|
var import_node_url4 = require("url");
|
|
3070
3093
|
var import_citty4 = require("citty");
|
|
3071
3094
|
|
|
@@ -3475,6 +3498,32 @@ function isSupportedWebFramework(framework) {
|
|
|
3475
3498
|
return framework === "react" || framework === "vue" || framework === "svelte";
|
|
3476
3499
|
}
|
|
3477
3500
|
|
|
3501
|
+
// src/commands/create/pnpm-workspace.ts
|
|
3502
|
+
var import_promises6 = require("fs/promises");
|
|
3503
|
+
var import_node_path8 = require("path");
|
|
3504
|
+
async function writePnpmWorkspaceSettings(targetDir) {
|
|
3505
|
+
const manifest = JSON.parse(await (0, import_promises6.readFile)((0, import_node_path8.join)(targetDir, "package.json"), "utf-8"));
|
|
3506
|
+
const listed = manifest.pnpm?.onlyBuiltDependencies;
|
|
3507
|
+
const allowed = Array.isArray(listed) ? [...new Set(listed.filter((name) => typeof name === "string"))].sort() : [];
|
|
3508
|
+
await (0, import_promises6.writeFile)((0, import_node_path8.join)(targetDir, "pnpm-workspace.yaml"), renderPnpmWorkspace(allowed), "utf-8");
|
|
3509
|
+
return allowed;
|
|
3510
|
+
}
|
|
3511
|
+
function renderPnpmWorkspace(allowed) {
|
|
3512
|
+
const quote = (name) => `'${name.replace(/'/g, "''")}'`;
|
|
3513
|
+
const lines = [
|
|
3514
|
+
"# pnpm settings for this app. Dependencies listed here may run their install",
|
|
3515
|
+
"# (build) scripts: pnpm 10 reads onlyBuiltDependencies, pnpm 11+ reads allowBuilds.",
|
|
3516
|
+
"packages:",
|
|
3517
|
+
" - '.'"
|
|
3518
|
+
];
|
|
3519
|
+
if (allowed.length > 0) {
|
|
3520
|
+
lines.push("onlyBuiltDependencies:", ...allowed.map((name) => ` - ${quote(name)}`));
|
|
3521
|
+
lines.push("allowBuilds:", ...allowed.map((name) => ` ${quote(name)}: true`));
|
|
3522
|
+
}
|
|
3523
|
+
return `${lines.join("\n")}
|
|
3524
|
+
`;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3478
3527
|
// src/commands/create/preferences-flow.ts
|
|
3479
3528
|
async function resolveCreatePreferencesFlow(params) {
|
|
3480
3529
|
const { flags, prompts, store } = params;
|
|
@@ -3705,8 +3754,8 @@ function validateProjectName(name) {
|
|
|
3705
3754
|
}
|
|
3706
3755
|
|
|
3707
3756
|
// src/commands/create/sync-provider-preset.ts
|
|
3708
|
-
var
|
|
3709
|
-
var
|
|
3757
|
+
var import_promises7 = require("fs/promises");
|
|
3758
|
+
var import_node_path9 = require("path");
|
|
3710
3759
|
async function applySyncProviderPreset(options) {
|
|
3711
3760
|
if (!isSyncTemplate(options.template)) {
|
|
3712
3761
|
return;
|
|
@@ -3716,10 +3765,10 @@ async function applySyncProviderPreset(options) {
|
|
|
3716
3765
|
}
|
|
3717
3766
|
const providerName = getProviderDisplayName(options.dbProvider);
|
|
3718
3767
|
const providerConnectionString = getProviderConnectionStringExample(options.dbProvider);
|
|
3719
|
-
const envPath = (0,
|
|
3720
|
-
const readmePath = (0,
|
|
3721
|
-
const existingReadme = await (0,
|
|
3722
|
-
const existingEnv = await (0,
|
|
3768
|
+
const envPath = (0, import_node_path9.join)(options.targetDir, ".env.example");
|
|
3769
|
+
const readmePath = (0, import_node_path9.join)(options.targetDir, "README.md");
|
|
3770
|
+
const existingReadme = await (0, import_promises7.readFile)(readmePath, "utf-8");
|
|
3771
|
+
const existingEnv = await (0, import_promises7.readFile)(envPath, "utf-8");
|
|
3723
3772
|
const trimmedReadme = existingReadme.trimEnd();
|
|
3724
3773
|
const trimmedEnv = existingEnv.trimEnd();
|
|
3725
3774
|
const envSuffix = [
|
|
@@ -3739,9 +3788,9 @@ async function applySyncProviderPreset(options) {
|
|
|
3739
3788
|
""
|
|
3740
3789
|
].join("\n");
|
|
3741
3790
|
const readmeTemplate = `${trimmedReadme}${readmeSuffix}`;
|
|
3742
|
-
await (0,
|
|
3791
|
+
await (0, import_promises7.writeFile)(envPath, `${trimmedEnv}${envSuffix}
|
|
3743
3792
|
`, "utf-8");
|
|
3744
|
-
await (0,
|
|
3793
|
+
await (0, import_promises7.writeFile)(readmePath, readmeTemplate, "utf-8");
|
|
3745
3794
|
}
|
|
3746
3795
|
function isSyncTemplate(template) {
|
|
3747
3796
|
return template === "react-sync" || template === "react-tailwind-sync" || template === "vue-sync" || template === "vue-tailwind-sync" || template === "svelte-sync" || template === "svelte-tailwind-sync" || template === "tauri-react";
|
|
@@ -3785,9 +3834,14 @@ function getProviderConnectionStringExample(provider) {
|
|
|
3785
3834
|
|
|
3786
3835
|
// src/templates/composer.ts
|
|
3787
3836
|
var import_node_fs = require("fs");
|
|
3788
|
-
var
|
|
3789
|
-
var
|
|
3837
|
+
var import_promises8 = require("fs/promises");
|
|
3838
|
+
var import_node_path10 = require("path");
|
|
3790
3839
|
var import_node_url3 = require("url");
|
|
3840
|
+
|
|
3841
|
+
// src/templates/tauri-version.ts
|
|
3842
|
+
var KORA_TAURI_TEMPLATE_VERSION = "0.4.3-beta.13";
|
|
3843
|
+
|
|
3844
|
+
// src/templates/composer.ts
|
|
3791
3845
|
var import_meta = {};
|
|
3792
3846
|
function substituteVariables(template, context) {
|
|
3793
3847
|
return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
@@ -3796,16 +3850,16 @@ function substituteVariables(template, context) {
|
|
|
3796
3850
|
});
|
|
3797
3851
|
}
|
|
3798
3852
|
function getTemplatePath(templateName) {
|
|
3799
|
-
let dir = (0,
|
|
3853
|
+
let dir = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
|
|
3800
3854
|
for (let i = 0; i < 7; i++) {
|
|
3801
|
-
const candidate = (0,
|
|
3855
|
+
const candidate = (0, import_node_path10.resolve)(dir, "templates", templateName);
|
|
3802
3856
|
if ((0, import_node_fs.existsSync)(candidate)) {
|
|
3803
3857
|
return candidate;
|
|
3804
3858
|
}
|
|
3805
|
-
dir = (0,
|
|
3859
|
+
dir = (0, import_node_path10.dirname)(dir);
|
|
3806
3860
|
}
|
|
3807
|
-
const currentDir = (0,
|
|
3808
|
-
return (0,
|
|
3861
|
+
const currentDir = (0, import_node_path10.dirname)((0, import_node_url3.fileURLToPath)(import_meta.url));
|
|
3862
|
+
return (0, import_node_path10.resolve)(currentDir, "..", "..", "..", "templates", templateName);
|
|
3809
3863
|
}
|
|
3810
3864
|
function createCompatibilityLayerPlan(templateName) {
|
|
3811
3865
|
const baseLayer = { category: "base", name: "base", sourceTemplate: "react-basic" };
|
|
@@ -3927,6 +3981,7 @@ async function composeTemplateLayers(plan, targetDir, context) {
|
|
|
3927
3981
|
projectName: context.projectName,
|
|
3928
3982
|
packageManager: context.packageManager,
|
|
3929
3983
|
koraVersion: context.koraVersion,
|
|
3984
|
+
tauriVersion: context.tauriVersion ?? KORA_TAURI_TEMPLATE_VERSION,
|
|
3930
3985
|
dbProvider: context.dbProvider ?? "none"
|
|
3931
3986
|
};
|
|
3932
3987
|
for (const layer of plan.layers) {
|
|
@@ -3938,22 +3993,22 @@ async function composeTemplateLayers(plan, targetDir, context) {
|
|
|
3938
3993
|
}
|
|
3939
3994
|
}
|
|
3940
3995
|
async function copyDirectory(src, dest, vars) {
|
|
3941
|
-
await (0,
|
|
3942
|
-
const entries = await (0,
|
|
3996
|
+
await (0, import_promises8.mkdir)(dest, { recursive: true });
|
|
3997
|
+
const entries = await (0, import_promises8.readdir)(src);
|
|
3943
3998
|
for (const entry of entries) {
|
|
3944
|
-
const srcPath = (0,
|
|
3945
|
-
const srcStat = await (0,
|
|
3999
|
+
const srcPath = (0, import_node_path10.join)(src, entry);
|
|
4000
|
+
const srcStat = await (0, import_promises8.stat)(srcPath);
|
|
3946
4001
|
if (srcStat.isDirectory()) {
|
|
3947
|
-
await copyDirectory(srcPath, (0,
|
|
4002
|
+
await copyDirectory(srcPath, (0, import_node_path10.join)(dest, entry), vars);
|
|
3948
4003
|
continue;
|
|
3949
4004
|
}
|
|
3950
4005
|
if (entry.endsWith(".hbs")) {
|
|
3951
|
-
const content = await (0,
|
|
4006
|
+
const content = await (0, import_promises8.readFile)(srcPath, "utf-8");
|
|
3952
4007
|
const outputName = entry.slice(0, -4);
|
|
3953
|
-
await (0,
|
|
4008
|
+
await (0, import_promises8.writeFile)((0, import_node_path10.join)(dest, outputName), substituteVariables(content, vars), "utf-8");
|
|
3954
4009
|
continue;
|
|
3955
4010
|
}
|
|
3956
|
-
await (0,
|
|
4011
|
+
await (0, import_promises8.copyFile)(srcPath, (0, import_node_path10.join)(dest, entry));
|
|
3957
4012
|
}
|
|
3958
4013
|
}
|
|
3959
4014
|
|
|
@@ -4106,7 +4161,7 @@ var createCommand = (0, import_citty4.defineCommand)({
|
|
|
4106
4161
|
const editorDetection = detectEditorFromEnvironment();
|
|
4107
4162
|
const gitContext = await detectGitContext(process.cwd());
|
|
4108
4163
|
const monorepoContext = await detectMonorepoContext(process.cwd());
|
|
4109
|
-
let targetDir = (0,
|
|
4164
|
+
let targetDir = (0, import_node_path11.resolve)(process.cwd(), projectName);
|
|
4110
4165
|
if (!useDefaults && monorepoContext.isMonorepo && monorepoContext.root !== null) {
|
|
4111
4166
|
const useWorkspaceTarget = await prompts.confirm(
|
|
4112
4167
|
`Detected ${formatMonorepoKind(monorepoContext.kind)} at ${monorepoContext.root}. Create app under packages/${projectName}?`,
|
|
@@ -4133,6 +4188,9 @@ var createCommand = (0, import_citty4.defineCommand)({
|
|
|
4133
4188
|
db: selection.db,
|
|
4134
4189
|
dbProvider: selection.dbProvider
|
|
4135
4190
|
});
|
|
4191
|
+
if (pm === "pnpm") {
|
|
4192
|
+
await writePnpmWorkspaceSettings(targetDir);
|
|
4193
|
+
}
|
|
4136
4194
|
if (!useDefaults && editorDetection.editor !== "unknown") {
|
|
4137
4195
|
const shouldApplyEditorPreset = await prompts.confirm(
|
|
4138
4196
|
`Detected ${formatEditor(editorDetection.editor)}. Add workspace recommendations for ${formatEditor(editorDetection.editor)}?`,
|
|
@@ -4276,16 +4334,16 @@ function relativeToTarget(targetDir, filePath) {
|
|
|
4276
4334
|
}
|
|
4277
4335
|
function resolveKoraVersion() {
|
|
4278
4336
|
try {
|
|
4279
|
-
let dir = (0,
|
|
4337
|
+
let dir = (0, import_node_path11.dirname)((0, import_node_url4.fileURLToPath)(import_meta2.url));
|
|
4280
4338
|
for (let i = 0; i < 5; i++) {
|
|
4281
|
-
const pkgPath = (0,
|
|
4339
|
+
const pkgPath = (0, import_node_path11.resolve)(dir, "package.json");
|
|
4282
4340
|
if ((0, import_node_fs2.existsSync)(pkgPath)) {
|
|
4283
4341
|
const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf-8"));
|
|
4284
4342
|
if (pkg.name === "@korajs/cli") {
|
|
4285
4343
|
return deriveKoraTemplateVersion(pkg.version);
|
|
4286
4344
|
}
|
|
4287
4345
|
}
|
|
4288
|
-
dir = (0,
|
|
4346
|
+
dir = (0, import_node_path11.dirname)(dir);
|
|
4289
4347
|
}
|
|
4290
4348
|
return "latest";
|
|
4291
4349
|
} catch {
|
|
@@ -4301,7 +4359,7 @@ function deriveKoraTemplateVersion(version) {
|
|
|
4301
4359
|
|
|
4302
4360
|
// src/commands/deploy/deploy-command.ts
|
|
4303
4361
|
var import_node_fs4 = require("fs");
|
|
4304
|
-
var
|
|
4362
|
+
var import_node_path22 = require("path");
|
|
4305
4363
|
var import_citty5 = require("citty");
|
|
4306
4364
|
init_fs_helpers();
|
|
4307
4365
|
|
|
@@ -4315,19 +4373,23 @@ var DEPLOY_PLATFORMS = [
|
|
|
4315
4373
|
"docker",
|
|
4316
4374
|
"kora-cloud"
|
|
4317
4375
|
];
|
|
4376
|
+
var IMPLEMENTED_DEPLOY_PLATFORMS = ["fly", "railway", "aws-ecs", "aws-lightsail"];
|
|
4377
|
+
function isImplementedDeployPlatform(platform) {
|
|
4378
|
+
return IMPLEMENTED_DEPLOY_PLATFORMS.includes(platform);
|
|
4379
|
+
}
|
|
4318
4380
|
function isDeployPlatform(value) {
|
|
4319
4381
|
return DEPLOY_PLATFORMS.includes(value);
|
|
4320
4382
|
}
|
|
4321
4383
|
|
|
4322
4384
|
// src/commands/deploy/adapters/aws-ecs-adapter.ts
|
|
4323
4385
|
var import_node_child_process6 = require("child_process");
|
|
4324
|
-
var
|
|
4386
|
+
var import_node_path14 = require("path");
|
|
4325
4387
|
|
|
4326
4388
|
// src/commands/deploy/builder/client-builder.ts
|
|
4327
4389
|
var import_node_child_process5 = require("child_process");
|
|
4328
4390
|
var import_node_fs3 = require("fs");
|
|
4329
|
-
var
|
|
4330
|
-
var
|
|
4391
|
+
var import_promises9 = require("fs/promises");
|
|
4392
|
+
var import_node_path12 = require("path");
|
|
4331
4393
|
init_fs_helpers();
|
|
4332
4394
|
async function buildClient(options) {
|
|
4333
4395
|
const viteEntryPoint = await resolveProjectBinaryEntryPoint(options.projectRoot, "vite", "vite");
|
|
@@ -4349,15 +4411,15 @@ async function buildClient(options) {
|
|
|
4349
4411
|
return { outDir: options.outDir };
|
|
4350
4412
|
}
|
|
4351
4413
|
async function patchSqliteWasmAssets(projectRoot, outDir) {
|
|
4352
|
-
const assetsDir = (0,
|
|
4414
|
+
const assetsDir = (0, import_node_path12.join)(outDir, "assets");
|
|
4353
4415
|
if (!(0, import_node_fs3.existsSync)(assetsDir)) return;
|
|
4354
|
-
const files = await (0,
|
|
4416
|
+
const files = await (0, import_promises9.readdir)(assetsDir);
|
|
4355
4417
|
const hashedWasm = files.find((f) => /^sqlite3-.+\.wasm$/.test(f));
|
|
4356
4418
|
if (hashedWasm && !files.includes("sqlite3.wasm")) {
|
|
4357
|
-
await (0,
|
|
4419
|
+
await (0, import_promises9.copyFile)((0, import_node_path12.join)(assetsDir, hashedWasm), (0, import_node_path12.join)(assetsDir, "sqlite3.wasm"));
|
|
4358
4420
|
}
|
|
4359
4421
|
if (!files.includes("sqlite3-opfs-async-proxy.js")) {
|
|
4360
|
-
const proxyFile = (0,
|
|
4422
|
+
const proxyFile = (0, import_node_path12.resolve)(
|
|
4361
4423
|
projectRoot,
|
|
4362
4424
|
"node_modules",
|
|
4363
4425
|
"@sqlite.org",
|
|
@@ -4367,7 +4429,7 @@ async function patchSqliteWasmAssets(projectRoot, outDir) {
|
|
|
4367
4429
|
"sqlite3-opfs-async-proxy.js"
|
|
4368
4430
|
);
|
|
4369
4431
|
if ((0, import_node_fs3.existsSync)(proxyFile)) {
|
|
4370
|
-
await (0,
|
|
4432
|
+
await (0, import_promises9.copyFile)(proxyFile, (0, import_node_path12.join)(assetsDir, "sqlite3-opfs-async-proxy.js"));
|
|
4371
4433
|
}
|
|
4372
4434
|
}
|
|
4373
4435
|
}
|
|
@@ -4392,9 +4454,9 @@ async function runProcess(command, args, cwd) {
|
|
|
4392
4454
|
}
|
|
4393
4455
|
|
|
4394
4456
|
// src/commands/deploy/builder/server-bundler.ts
|
|
4395
|
-
var import_promises9 = require("fs/promises");
|
|
4396
4457
|
var import_promises10 = require("fs/promises");
|
|
4397
|
-
var
|
|
4458
|
+
var import_promises11 = require("fs/promises");
|
|
4459
|
+
var import_node_path13 = require("path");
|
|
4398
4460
|
var import_esbuild = require("esbuild");
|
|
4399
4461
|
var DEFAULT_ENTRY_CANDIDATES = [
|
|
4400
4462
|
"server.ts",
|
|
@@ -4410,8 +4472,8 @@ async function bundleServer(options) {
|
|
|
4410
4472
|
`Could not find a server entry file in ${options.projectRoot}. Looked for: ${candidates.join(", ")}`
|
|
4411
4473
|
);
|
|
4412
4474
|
}
|
|
4413
|
-
await (0,
|
|
4414
|
-
const outputFilePath = (0,
|
|
4475
|
+
await (0, import_promises10.mkdir)(options.deployDirectory, { recursive: true });
|
|
4476
|
+
const outputFilePath = (0, import_node_path13.join)(options.deployDirectory, "server-bundled.js");
|
|
4415
4477
|
await (0, import_esbuild.build)({
|
|
4416
4478
|
entryPoints: [entryFilePath],
|
|
4417
4479
|
outfile: outputFilePath,
|
|
@@ -4433,9 +4495,9 @@ async function bundleServer(options) {
|
|
|
4433
4495
|
}
|
|
4434
4496
|
async function resolveServerEntry(projectRoot, candidates) {
|
|
4435
4497
|
for (const candidate of candidates) {
|
|
4436
|
-
const fullPath = (0,
|
|
4498
|
+
const fullPath = (0, import_node_path13.join)(projectRoot, candidate);
|
|
4437
4499
|
try {
|
|
4438
|
-
await (0,
|
|
4500
|
+
await (0, import_promises11.access)(fullPath);
|
|
4439
4501
|
return fullPath;
|
|
4440
4502
|
} catch {
|
|
4441
4503
|
}
|
|
@@ -4537,19 +4599,19 @@ var AwsEcsAdapter = class {
|
|
|
4537
4599
|
appName: config.appName,
|
|
4538
4600
|
region: config.region
|
|
4539
4601
|
};
|
|
4540
|
-
const deployDirectory = (0,
|
|
4602
|
+
const deployDirectory = (0, import_node_path14.join)(config.projectRoot, ".kora", "deploy");
|
|
4541
4603
|
await bundleServer({
|
|
4542
4604
|
projectRoot: config.projectRoot,
|
|
4543
4605
|
deployDirectory
|
|
4544
4606
|
});
|
|
4545
4607
|
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
4546
4608
|
projectRoot: config.projectRoot,
|
|
4547
|
-
outDir: (0,
|
|
4609
|
+
outDir: (0, import_node_path14.join)(deployDirectory, "dist"),
|
|
4548
4610
|
mode: "production"
|
|
4549
4611
|
});
|
|
4550
4612
|
return {
|
|
4551
4613
|
clientDirectory: client?.outDir ?? null,
|
|
4552
|
-
serverBundlePath: (0,
|
|
4614
|
+
serverBundlePath: (0, import_node_path14.join)(deployDirectory, "server-bundled.js"),
|
|
4553
4615
|
deployDirectory
|
|
4554
4616
|
};
|
|
4555
4617
|
}
|
|
@@ -4815,7 +4877,7 @@ function inferLogLevel(line) {
|
|
|
4815
4877
|
|
|
4816
4878
|
// src/commands/deploy/adapters/aws-lightsail-adapter.ts
|
|
4817
4879
|
var import_node_child_process7 = require("child_process");
|
|
4818
|
-
var
|
|
4880
|
+
var import_node_path15 = require("path");
|
|
4819
4881
|
var AwsLightsailAdapter = class {
|
|
4820
4882
|
name = "aws-lightsail";
|
|
4821
4883
|
logger = createLogger();
|
|
@@ -4888,19 +4950,19 @@ var AwsLightsailAdapter = class {
|
|
|
4888
4950
|
appName: config.appName,
|
|
4889
4951
|
region: config.region
|
|
4890
4952
|
};
|
|
4891
|
-
const deployDirectory = (0,
|
|
4953
|
+
const deployDirectory = (0, import_node_path15.join)(config.projectRoot, ".kora", "deploy");
|
|
4892
4954
|
await bundleServer({
|
|
4893
4955
|
projectRoot: config.projectRoot,
|
|
4894
4956
|
deployDirectory
|
|
4895
4957
|
});
|
|
4896
4958
|
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
4897
4959
|
projectRoot: config.projectRoot,
|
|
4898
|
-
outDir: (0,
|
|
4960
|
+
outDir: (0, import_node_path15.join)(deployDirectory, "dist"),
|
|
4899
4961
|
mode: "production"
|
|
4900
4962
|
});
|
|
4901
4963
|
return {
|
|
4902
4964
|
clientDirectory: client?.outDir ?? null,
|
|
4903
|
-
serverBundlePath: (0,
|
|
4965
|
+
serverBundlePath: (0, import_node_path15.join)(deployDirectory, "server-bundled.js"),
|
|
4904
4966
|
deployDirectory
|
|
4905
4967
|
};
|
|
4906
4968
|
}
|
|
@@ -5200,11 +5262,11 @@ function inferLogLevel2(line) {
|
|
|
5200
5262
|
|
|
5201
5263
|
// src/commands/deploy/adapters/fly-adapter.ts
|
|
5202
5264
|
var import_node_child_process8 = require("child_process");
|
|
5203
|
-
var
|
|
5265
|
+
var import_node_path17 = require("path");
|
|
5204
5266
|
|
|
5205
5267
|
// src/commands/deploy/artifacts/fly-toml-generator.ts
|
|
5206
|
-
var
|
|
5207
|
-
var
|
|
5268
|
+
var import_promises12 = require("fs/promises");
|
|
5269
|
+
var import_node_path16 = require("path");
|
|
5208
5270
|
var GENERATED_HEADER = "# Generated by kora deploy \u2014 do not edit manually";
|
|
5209
5271
|
function generateFlyToml(options) {
|
|
5210
5272
|
const internalPort = options.internalPort ?? 3e3;
|
|
@@ -5234,9 +5296,9 @@ function generateFlyToml(options) {
|
|
|
5234
5296
|
].join("\n");
|
|
5235
5297
|
}
|
|
5236
5298
|
async function writeFlyTomlArtifact(deployDirectory, options) {
|
|
5237
|
-
await (0,
|
|
5238
|
-
const filePath = (0,
|
|
5239
|
-
await (0,
|
|
5299
|
+
await (0, import_promises12.mkdir)(deployDirectory, { recursive: true });
|
|
5300
|
+
const filePath = (0, import_node_path16.join)(deployDirectory, "fly.toml");
|
|
5301
|
+
await (0, import_promises12.writeFile)(filePath, generateFlyToml(options), "utf-8");
|
|
5240
5302
|
return filePath;
|
|
5241
5303
|
}
|
|
5242
5304
|
|
|
@@ -5330,7 +5392,7 @@ var FlyAdapter = class {
|
|
|
5330
5392
|
appName: config.appName,
|
|
5331
5393
|
region: config.region
|
|
5332
5394
|
};
|
|
5333
|
-
const deployDirectory = (0,
|
|
5395
|
+
const deployDirectory = (0, import_node_path17.join)(config.projectRoot, ".kora", "deploy");
|
|
5334
5396
|
await writeFlyTomlArtifact(deployDirectory, {
|
|
5335
5397
|
appName: config.appName,
|
|
5336
5398
|
region: config.region ?? "iad"
|
|
@@ -5341,12 +5403,12 @@ var FlyAdapter = class {
|
|
|
5341
5403
|
});
|
|
5342
5404
|
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
5343
5405
|
projectRoot: config.projectRoot,
|
|
5344
|
-
outDir: (0,
|
|
5406
|
+
outDir: (0, import_node_path17.join)(deployDirectory, "dist"),
|
|
5345
5407
|
mode: "production"
|
|
5346
5408
|
});
|
|
5347
5409
|
return {
|
|
5348
5410
|
clientDirectory: client?.outDir ?? null,
|
|
5349
|
-
serverBundlePath: (0,
|
|
5411
|
+
serverBundlePath: (0, import_node_path17.join)(deployDirectory, "server-bundled.js"),
|
|
5350
5412
|
deployDirectory
|
|
5351
5413
|
};
|
|
5352
5414
|
}
|
|
@@ -5568,11 +5630,11 @@ ${stdout}`.toLowerCase();
|
|
|
5568
5630
|
|
|
5569
5631
|
// src/commands/deploy/adapters/railway-adapter.ts
|
|
5570
5632
|
var import_node_child_process9 = require("child_process");
|
|
5571
|
-
var
|
|
5633
|
+
var import_node_path19 = require("path");
|
|
5572
5634
|
|
|
5573
5635
|
// src/commands/deploy/artifacts/railway-json-generator.ts
|
|
5574
|
-
var
|
|
5575
|
-
var
|
|
5636
|
+
var import_promises13 = require("fs/promises");
|
|
5637
|
+
var import_node_path18 = require("path");
|
|
5576
5638
|
var GENERATED_HEADER2 = "Generated by kora deploy - do not edit manually.";
|
|
5577
5639
|
function generateRailwayJson(options) {
|
|
5578
5640
|
const file = {
|
|
@@ -5597,9 +5659,9 @@ function generateRailwayJson(options) {
|
|
|
5597
5659
|
`;
|
|
5598
5660
|
}
|
|
5599
5661
|
async function writeRailwayJsonArtifact(deployDirectory, options) {
|
|
5600
|
-
await (0,
|
|
5601
|
-
const filePath = (0,
|
|
5602
|
-
await (0,
|
|
5662
|
+
await (0, import_promises13.mkdir)(deployDirectory, { recursive: true });
|
|
5663
|
+
const filePath = (0, import_node_path18.join)(deployDirectory, "railway.json");
|
|
5664
|
+
await (0, import_promises13.writeFile)(filePath, generateRailwayJson(options), "utf-8");
|
|
5603
5665
|
return filePath;
|
|
5604
5666
|
}
|
|
5605
5667
|
|
|
@@ -5688,7 +5750,7 @@ var RailwayAdapter = class {
|
|
|
5688
5750
|
appName: config.appName,
|
|
5689
5751
|
region: config.region
|
|
5690
5752
|
};
|
|
5691
|
-
const deployDirectory = (0,
|
|
5753
|
+
const deployDirectory = (0, import_node_path19.join)(config.projectRoot, ".kora", "deploy");
|
|
5692
5754
|
await writeRailwayJsonArtifact(deployDirectory, {
|
|
5693
5755
|
appName: config.appName,
|
|
5694
5756
|
environment: config.environment
|
|
@@ -5699,12 +5761,12 @@ var RailwayAdapter = class {
|
|
|
5699
5761
|
});
|
|
5700
5762
|
const client = config.deployTarget === "sync-server" ? null : await buildClient({
|
|
5701
5763
|
projectRoot: config.projectRoot,
|
|
5702
|
-
outDir: (0,
|
|
5764
|
+
outDir: (0, import_node_path19.join)(deployDirectory, "dist"),
|
|
5703
5765
|
mode: "production"
|
|
5704
5766
|
});
|
|
5705
5767
|
return {
|
|
5706
5768
|
clientDirectory: client?.outDir ?? null,
|
|
5707
|
-
serverBundlePath: (0,
|
|
5769
|
+
serverBundlePath: (0, import_node_path19.join)(deployDirectory, "server-bundled.js"),
|
|
5708
5770
|
deployDirectory
|
|
5709
5771
|
};
|
|
5710
5772
|
}
|
|
@@ -5989,8 +6051,8 @@ function createDeployAdapter(platform) {
|
|
|
5989
6051
|
}
|
|
5990
6052
|
|
|
5991
6053
|
// src/commands/deploy/artifacts/dockerfile-generator.ts
|
|
5992
|
-
var
|
|
5993
|
-
var
|
|
6054
|
+
var import_promises14 = require("fs/promises");
|
|
6055
|
+
var import_node_path20 = require("path");
|
|
5994
6056
|
var GENERATED_HEADER3 = "# Generated by kora deploy \u2014 do not edit manually";
|
|
5995
6057
|
function generateDockerfile(options = {}) {
|
|
5996
6058
|
const nodeVersion = options.nodeVersion ?? "20-alpine";
|
|
@@ -6056,37 +6118,37 @@ function generateDockerIgnore() {
|
|
|
6056
6118
|
].join("\n");
|
|
6057
6119
|
}
|
|
6058
6120
|
async function writeDockerfileArtifact(deployDirectory, options = {}) {
|
|
6059
|
-
await (0,
|
|
6060
|
-
const dockerfilePath = (0,
|
|
6061
|
-
await (0,
|
|
6121
|
+
await (0, import_promises14.mkdir)(deployDirectory, { recursive: true });
|
|
6122
|
+
const dockerfilePath = (0, import_node_path20.join)(deployDirectory, "Dockerfile");
|
|
6123
|
+
await (0, import_promises14.writeFile)(dockerfilePath, generateDockerfile(options), "utf-8");
|
|
6062
6124
|
if (options.nativeDependencies && Object.keys(options.nativeDependencies).length > 0) {
|
|
6063
|
-
const pkgJsonPath = (0,
|
|
6064
|
-
await (0,
|
|
6125
|
+
const pkgJsonPath = (0, import_node_path20.join)(deployDirectory, "package.json");
|
|
6126
|
+
await (0, import_promises14.writeFile)(pkgJsonPath, generateDeployPackageJson(options.nativeDependencies), "utf-8");
|
|
6065
6127
|
}
|
|
6066
6128
|
return dockerfilePath;
|
|
6067
6129
|
}
|
|
6068
6130
|
async function writeDockerIgnoreArtifact(deployDirectory) {
|
|
6069
|
-
await (0,
|
|
6070
|
-
const dockerIgnorePath = (0,
|
|
6071
|
-
await (0,
|
|
6131
|
+
await (0, import_promises14.mkdir)(deployDirectory, { recursive: true });
|
|
6132
|
+
const dockerIgnorePath = (0, import_node_path20.join)(deployDirectory, ".dockerignore");
|
|
6133
|
+
await (0, import_promises14.writeFile)(dockerIgnorePath, generateDockerIgnore(), "utf-8");
|
|
6072
6134
|
return dockerIgnorePath;
|
|
6073
6135
|
}
|
|
6074
6136
|
|
|
6075
6137
|
// src/commands/deploy/state/deploy-state.ts
|
|
6076
|
-
var
|
|
6077
|
-
var
|
|
6078
|
-
var KORA_DEPLOY_DIRECTORY = (0,
|
|
6138
|
+
var import_promises15 = require("fs/promises");
|
|
6139
|
+
var import_node_path21 = require("path");
|
|
6140
|
+
var KORA_DEPLOY_DIRECTORY = (0, import_node_path21.join)(".kora", "deploy");
|
|
6079
6141
|
var DEPLOY_STATE_FILENAME = "deploy.json";
|
|
6080
6142
|
function resolveDeployDirectory(projectRoot) {
|
|
6081
|
-
return (0,
|
|
6143
|
+
return (0, import_node_path21.join)(projectRoot, KORA_DEPLOY_DIRECTORY);
|
|
6082
6144
|
}
|
|
6083
6145
|
function resolveDeployStatePath(projectRoot) {
|
|
6084
|
-
return (0,
|
|
6146
|
+
return (0, import_node_path21.join)(resolveDeployDirectory(projectRoot), DEPLOY_STATE_FILENAME);
|
|
6085
6147
|
}
|
|
6086
6148
|
async function readDeployState(projectRoot) {
|
|
6087
6149
|
const statePath = resolveDeployStatePath(projectRoot);
|
|
6088
6150
|
try {
|
|
6089
|
-
const source = await (0,
|
|
6151
|
+
const source = await (0, import_promises15.readFile)(statePath, "utf-8");
|
|
6090
6152
|
const parsed = JSON.parse(source);
|
|
6091
6153
|
return parseDeployState(parsed);
|
|
6092
6154
|
} catch (error) {
|
|
@@ -6133,13 +6195,13 @@ async function updateDeployState(projectRoot, patch, now = /* @__PURE__ */ new D
|
|
|
6133
6195
|
return nextState;
|
|
6134
6196
|
}
|
|
6135
6197
|
async function resetDeployState(projectRoot) {
|
|
6136
|
-
await (0,
|
|
6198
|
+
await (0, import_promises15.rm)(resolveDeployDirectory(projectRoot), { recursive: true, force: true });
|
|
6137
6199
|
}
|
|
6138
6200
|
async function persistDeployState(projectRoot, state) {
|
|
6139
6201
|
const deployDir = resolveDeployDirectory(projectRoot);
|
|
6140
6202
|
const statePath = resolveDeployStatePath(projectRoot);
|
|
6141
|
-
await (0,
|
|
6142
|
-
await (0,
|
|
6203
|
+
await (0, import_promises15.mkdir)(deployDir, { recursive: true });
|
|
6204
|
+
await (0, import_promises15.writeFile)(statePath, `${JSON.stringify(state, null, 2)}
|
|
6143
6205
|
`, "utf-8");
|
|
6144
6206
|
}
|
|
6145
6207
|
function parseDeployState(value) {
|
|
@@ -6338,6 +6400,9 @@ var deployCommand = (0, import_citty5.defineCommand)({
|
|
|
6338
6400
|
storedPlatform: existingState?.platform,
|
|
6339
6401
|
confirm: confirmMode
|
|
6340
6402
|
});
|
|
6403
|
+
if (!isImplementedDeployPlatform(platform)) {
|
|
6404
|
+
throw new DeployPlatformUnavailableError(platform, IMPLEMENTED_DEPLOY_PLATFORMS);
|
|
6405
|
+
}
|
|
6341
6406
|
const appName = resolveAppName(args.app, existingState?.appName, projectRoot, confirmMode);
|
|
6342
6407
|
const region = resolveRegion(args.region, existingState?.region, confirmMode);
|
|
6343
6408
|
const deployDirectory = resolveDeployDirectory(projectRoot);
|
|
@@ -6448,11 +6513,11 @@ async function resolvePlatform(options) {
|
|
|
6448
6513
|
value: "aws-lightsail"
|
|
6449
6514
|
},
|
|
6450
6515
|
{
|
|
6451
|
-
label: "Render",
|
|
6516
|
+
label: "Render (coming soon)",
|
|
6452
6517
|
value: "render"
|
|
6453
6518
|
},
|
|
6454
6519
|
{
|
|
6455
|
-
label: "Docker (self-hosted)",
|
|
6520
|
+
label: "Docker (self-hosted, coming soon)",
|
|
6456
6521
|
value: "docker"
|
|
6457
6522
|
},
|
|
6458
6523
|
{
|
|
@@ -6473,7 +6538,7 @@ function resolveAppName(argValue, storedValue, projectRoot, confirm) {
|
|
|
6473
6538
|
"Missing app name in --confirm mode. Provide --app or run an interactive deploy first."
|
|
6474
6539
|
);
|
|
6475
6540
|
}
|
|
6476
|
-
return sanitizeAppName((0,
|
|
6541
|
+
return sanitizeAppName((0, import_node_path22.basename)(projectRoot));
|
|
6477
6542
|
}
|
|
6478
6543
|
function resolveRegion(argValue, storedValue, confirm) {
|
|
6479
6544
|
if (typeof argValue === "string" && argValue.length > 0) return argValue;
|
|
@@ -6508,7 +6573,7 @@ function configureAdapterContext(adapter, context) {
|
|
|
6508
6573
|
}
|
|
6509
6574
|
}
|
|
6510
6575
|
function detectDeployTarget(projectRoot) {
|
|
6511
|
-
return (0, import_node_fs4.existsSync)((0,
|
|
6576
|
+
return (0, import_node_fs4.existsSync)((0, import_node_path22.join)(projectRoot, "src-tauri")) ? "sync-server" : "full-stack";
|
|
6512
6577
|
}
|
|
6513
6578
|
function hasContextSetter(adapter) {
|
|
6514
6579
|
return typeof adapter.setContext === "function";
|
|
@@ -6516,9 +6581,9 @@ function hasContextSetter(adapter) {
|
|
|
6516
6581
|
|
|
6517
6582
|
// src/commands/dev/dev-command.ts
|
|
6518
6583
|
var import_node_fs6 = require("fs");
|
|
6519
|
-
var
|
|
6520
|
-
var import_node_path23 = require("path");
|
|
6584
|
+
var import_promises16 = require("fs/promises");
|
|
6521
6585
|
var import_node_path24 = require("path");
|
|
6586
|
+
var import_node_path25 = require("path");
|
|
6522
6587
|
var import_citty6 = require("citty");
|
|
6523
6588
|
init_fs_helpers();
|
|
6524
6589
|
|
|
@@ -6627,7 +6692,7 @@ function delay(ms) {
|
|
|
6627
6692
|
// src/commands/dev/schema-watcher.ts
|
|
6628
6693
|
var import_node_child_process11 = require("child_process");
|
|
6629
6694
|
var import_node_fs5 = require("fs");
|
|
6630
|
-
var
|
|
6695
|
+
var import_node_path23 = require("path");
|
|
6631
6696
|
init_fs_helpers();
|
|
6632
6697
|
init_schema_loader();
|
|
6633
6698
|
var SchemaWatcher = class {
|
|
@@ -6661,7 +6726,7 @@ var SchemaWatcher = class {
|
|
|
6661
6726
|
this.watcher = null;
|
|
6662
6727
|
}
|
|
6663
6728
|
async regenerate() {
|
|
6664
|
-
const koraBinJs = (0,
|
|
6729
|
+
const koraBinJs = (0, import_node_path23.join)(
|
|
6665
6730
|
this.config.projectRoot,
|
|
6666
6731
|
"node_modules",
|
|
6667
6732
|
"@korajs",
|
|
@@ -6777,7 +6842,7 @@ var devCommand = (0, import_citty6.defineCommand)({
|
|
|
6777
6842
|
const watchDebounceMs = typeof config?.dev?.watch === "object" && typeof config.dev.watch.debounceMs === "number" ? config.dev.watch.debounceMs : 300;
|
|
6778
6843
|
const viteEntryPoint = await resolveProjectBinaryEntryPoint(projectRoot, "vite", "vite");
|
|
6779
6844
|
if (!viteEntryPoint) {
|
|
6780
|
-
throw new DevServerError("vite", (0,
|
|
6845
|
+
throw new DevServerError("vite", (0, import_node_path24.join)(projectRoot, "node_modules", ".bin", "vite"));
|
|
6781
6846
|
}
|
|
6782
6847
|
const syncServerFile = await findSyncServerFile(projectRoot);
|
|
6783
6848
|
let managedSyncStore = normalizeManagedSyncStore(config, projectRoot);
|
|
@@ -6793,7 +6858,7 @@ var devCommand = (0, import_citty6.defineCommand)({
|
|
|
6793
6858
|
}
|
|
6794
6859
|
if (shouldStartSync && syncServerFile === null && managedSyncStore) {
|
|
6795
6860
|
const hasServerPackage = await fileExists3(
|
|
6796
|
-
(0,
|
|
6861
|
+
(0, import_node_path24.join)(projectRoot, "node_modules", "@korajs", "server", "package.json")
|
|
6797
6862
|
);
|
|
6798
6863
|
if (!hasServerPackage) {
|
|
6799
6864
|
logger.warn(
|
|
@@ -6810,7 +6875,7 @@ var devCommand = (0, import_citty6.defineCommand)({
|
|
|
6810
6875
|
}
|
|
6811
6876
|
let configuredSchemaPath = null;
|
|
6812
6877
|
if (typeof config?.schema === "string") {
|
|
6813
|
-
const candidate = (0,
|
|
6878
|
+
const candidate = (0, import_node_path25.resolve)(projectRoot, config.schema);
|
|
6814
6879
|
if (await fileExists3(candidate)) {
|
|
6815
6880
|
configuredSchemaPath = candidate;
|
|
6816
6881
|
} else {
|
|
@@ -6938,10 +7003,10 @@ var devCommand = (0, import_citty6.defineCommand)({
|
|
|
6938
7003
|
}
|
|
6939
7004
|
});
|
|
6940
7005
|
async function findSyncServerFile(projectRoot) {
|
|
6941
|
-
const candidates = [(0,
|
|
7006
|
+
const candidates = [(0, import_node_path24.join)(projectRoot, "server.ts"), (0, import_node_path24.join)(projectRoot, "server.js")];
|
|
6942
7007
|
for (const candidate of candidates) {
|
|
6943
7008
|
try {
|
|
6944
|
-
await (0,
|
|
7009
|
+
await (0, import_promises16.access)(candidate);
|
|
6945
7010
|
return candidate;
|
|
6946
7011
|
} catch {
|
|
6947
7012
|
}
|
|
@@ -6950,7 +7015,7 @@ async function findSyncServerFile(projectRoot) {
|
|
|
6950
7015
|
}
|
|
6951
7016
|
async function fileExists3(path) {
|
|
6952
7017
|
try {
|
|
6953
|
-
await (0,
|
|
7018
|
+
await (0, import_promises16.access)(path);
|
|
6954
7019
|
return true;
|
|
6955
7020
|
} catch {
|
|
6956
7021
|
return false;
|
|
@@ -6960,7 +7025,7 @@ function loadProjectEnv(projectRoot, mode = "development") {
|
|
|
6960
7025
|
const files = [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`];
|
|
6961
7026
|
const existingKeys = new Set(Object.keys(process.env));
|
|
6962
7027
|
for (const file of files) {
|
|
6963
|
-
const path = (0,
|
|
7028
|
+
const path = (0, import_node_path24.join)(projectRoot, file);
|
|
6964
7029
|
if (!(0, import_node_fs6.existsSync)(path)) continue;
|
|
6965
7030
|
const parsed = parseEnvFile((0, import_node_fs6.readFileSync)(path, "utf8"));
|
|
6966
7031
|
for (const [key, value] of Object.entries(parsed)) {
|
|
@@ -6999,7 +7064,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
|
|
|
6999
7064
|
const store = sync.store;
|
|
7000
7065
|
if (store === void 0) return { type: "memory" };
|
|
7001
7066
|
if (store === "memory") return { type: "memory" };
|
|
7002
|
-
if (store === "sqlite") return { type: "sqlite", filename: (0,
|
|
7067
|
+
if (store === "sqlite") return { type: "sqlite", filename: (0, import_node_path24.join)(projectRoot, "kora-sync.db") };
|
|
7003
7068
|
if (store === "postgres") {
|
|
7004
7069
|
const connectionString = process.env.DATABASE_URL;
|
|
7005
7070
|
if (!connectionString) return null;
|
|
@@ -7008,7 +7073,7 @@ function normalizeManagedSyncStore(config, projectRoot) {
|
|
|
7008
7073
|
if (typeof store === "object" && store !== null) {
|
|
7009
7074
|
if (store.type === "memory") return { type: "memory" };
|
|
7010
7075
|
if (store.type === "sqlite") {
|
|
7011
|
-
const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0,
|
|
7076
|
+
const filename = typeof store.filename === "string" && store.filename.length > 0 ? (0, import_node_path25.resolve)(projectRoot, store.filename) : (0, import_node_path24.join)(projectRoot, "kora-sync.db");
|
|
7012
7077
|
return { type: "sqlite", filename };
|
|
7013
7078
|
}
|
|
7014
7079
|
if (store.type === "postgres" && typeof store.connectionString === "string") {
|
|
@@ -7076,8 +7141,8 @@ var import_citty7 = require("citty");
|
|
|
7076
7141
|
init_fs_helpers();
|
|
7077
7142
|
|
|
7078
7143
|
// src/commands/doctor/doctor-checks.ts
|
|
7079
|
-
var
|
|
7080
|
-
var
|
|
7144
|
+
var import_promises17 = require("fs/promises");
|
|
7145
|
+
var import_node_path26 = require("path");
|
|
7081
7146
|
init_fs_helpers();
|
|
7082
7147
|
init_schema_loader();
|
|
7083
7148
|
var STATUS_ENDPOINT = "/__kora/status";
|
|
@@ -7157,9 +7222,9 @@ async function runDoctorChecks(options) {
|
|
|
7157
7222
|
}
|
|
7158
7223
|
async function resolveSchemaPath(projectRoot, configSchema) {
|
|
7159
7224
|
if (typeof configSchema === "string") {
|
|
7160
|
-
const resolved = (0,
|
|
7225
|
+
const resolved = (0, import_node_path26.resolve)(projectRoot, configSchema);
|
|
7161
7226
|
try {
|
|
7162
|
-
await (0,
|
|
7227
|
+
await (0, import_promises17.access)(resolved);
|
|
7163
7228
|
return resolved;
|
|
7164
7229
|
} catch {
|
|
7165
7230
|
}
|
|
@@ -7168,12 +7233,12 @@ async function resolveSchemaPath(projectRoot, configSchema) {
|
|
|
7168
7233
|
}
|
|
7169
7234
|
async function checkWorkerFile(projectRoot) {
|
|
7170
7235
|
const candidates = [
|
|
7171
|
-
(0,
|
|
7172
|
-
(0,
|
|
7236
|
+
(0, import_node_path26.join)(projectRoot, "src", "kora-worker.ts"),
|
|
7237
|
+
(0, import_node_path26.join)(projectRoot, "src", "kora-worker.js")
|
|
7173
7238
|
];
|
|
7174
7239
|
for (const path of candidates) {
|
|
7175
7240
|
try {
|
|
7176
|
-
await (0,
|
|
7241
|
+
await (0, import_promises17.access)(path);
|
|
7177
7242
|
return {
|
|
7178
7243
|
id: "worker",
|
|
7179
7244
|
title: "SQLite WASM worker",
|
|
@@ -7193,7 +7258,7 @@ async function checkWorkerFile(projectRoot) {
|
|
|
7193
7258
|
}
|
|
7194
7259
|
async function checkDependencies(projectRoot) {
|
|
7195
7260
|
try {
|
|
7196
|
-
const raw = await (0,
|
|
7261
|
+
const raw = await (0, import_promises17.readFile)((0, import_node_path26.join)(projectRoot, "package.json"), "utf-8");
|
|
7197
7262
|
const pkg = JSON.parse(raw);
|
|
7198
7263
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
7199
7264
|
const hasKora = Boolean(deps.korajs || deps.kora);
|
|
@@ -7242,9 +7307,9 @@ async function resolveSyncHttpUrl(projectRoot, config) {
|
|
|
7242
7307
|
}
|
|
7243
7308
|
async function readEnvSyncUrl(projectRoot) {
|
|
7244
7309
|
for (const file of [".env", ".env.local"]) {
|
|
7245
|
-
const path = (0,
|
|
7310
|
+
const path = (0, import_node_path26.join)(projectRoot, file);
|
|
7246
7311
|
try {
|
|
7247
|
-
const content = await (0,
|
|
7312
|
+
const content = await (0, import_promises17.readFile)(path, "utf-8");
|
|
7248
7313
|
for (const line of content.split("\n")) {
|
|
7249
7314
|
const trimmed = line.trim();
|
|
7250
7315
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -7390,8 +7455,8 @@ var doctorCommand = (0, import_citty7.defineCommand)({
|
|
|
7390
7455
|
});
|
|
7391
7456
|
|
|
7392
7457
|
// src/commands/generate/generate-command.ts
|
|
7393
|
-
var
|
|
7394
|
-
var
|
|
7458
|
+
var import_promises18 = require("fs/promises");
|
|
7459
|
+
var import_node_path27 = require("path");
|
|
7395
7460
|
var import_citty8 = require("citty");
|
|
7396
7461
|
init_fs_helpers();
|
|
7397
7462
|
init_schema_loader();
|
|
@@ -7577,7 +7642,7 @@ var generateCommand = (0, import_citty8.defineCommand)({
|
|
|
7577
7642
|
}
|
|
7578
7643
|
let schemaPath;
|
|
7579
7644
|
if (args.schema && typeof args.schema === "string") {
|
|
7580
|
-
schemaPath = (0,
|
|
7645
|
+
schemaPath = (0, import_node_path27.resolve)(args.schema);
|
|
7581
7646
|
} else {
|
|
7582
7647
|
const found = await findSchemaFile(projectRoot);
|
|
7583
7648
|
if (!found) {
|
|
@@ -7600,9 +7665,9 @@ var generateCommand = (0, import_citty8.defineCommand)({
|
|
|
7600
7665
|
}
|
|
7601
7666
|
const output = generateTypes(schema);
|
|
7602
7667
|
const outputFile = typeof args.output === "string" ? args.output : "kora/generated/types.ts";
|
|
7603
|
-
const outputPath = (0,
|
|
7604
|
-
await (0,
|
|
7605
|
-
await (0,
|
|
7668
|
+
const outputPath = (0, import_node_path27.resolve)(projectRoot, outputFile);
|
|
7669
|
+
await (0, import_promises18.mkdir)((0, import_node_path27.dirname)(outputPath), { recursive: true });
|
|
7670
|
+
await (0, import_promises18.writeFile)(outputPath, output, "utf-8");
|
|
7606
7671
|
logger.success(`Generated types at ${outputPath}`);
|
|
7607
7672
|
}
|
|
7608
7673
|
}),
|
|
@@ -7635,7 +7700,7 @@ var generateCommand = (0, import_citty8.defineCommand)({
|
|
|
7635
7700
|
}
|
|
7636
7701
|
let schemaPath;
|
|
7637
7702
|
if (args.schema && typeof args.schema === "string") {
|
|
7638
|
-
schemaPath = (0,
|
|
7703
|
+
schemaPath = (0, import_node_path27.resolve)(projectRoot, args.schema);
|
|
7639
7704
|
} else {
|
|
7640
7705
|
const found = await findSchemaFile(projectRoot);
|
|
7641
7706
|
if (!found) {
|
|
@@ -7649,13 +7714,13 @@ var generateCommand = (0, import_citty8.defineCommand)({
|
|
|
7649
7714
|
schemaPath = found;
|
|
7650
7715
|
}
|
|
7651
7716
|
const schema = await loadSchemaDefinition(schemaPath, projectRoot);
|
|
7652
|
-
const outputDir = typeof args.output === "string" ? (0,
|
|
7717
|
+
const outputDir = typeof args.output === "string" ? (0, import_node_path27.resolve)(projectRoot, args.output) : (0, import_node_path27.resolve)(projectRoot, "kora/generated/hooks");
|
|
7653
7718
|
const files = generateCollectionHooks(schema);
|
|
7654
7719
|
for (const [name, content] of files) {
|
|
7655
7720
|
const adjusted = typeof args.types === "string" && args.types !== "../types" ? content.replace("from '../types'", `from '${args.types}'`) : content;
|
|
7656
|
-
const filePath = (0,
|
|
7657
|
-
await (0,
|
|
7658
|
-
await (0,
|
|
7721
|
+
const filePath = (0, import_node_path27.resolve)(outputDir, name);
|
|
7722
|
+
await (0, import_promises18.mkdir)((0, import_node_path27.dirname)(filePath), { recursive: true });
|
|
7723
|
+
await (0, import_promises18.writeFile)(filePath, adjusted, "utf-8");
|
|
7659
7724
|
}
|
|
7660
7725
|
logger.success(`Generated ${String(files.size)} hook file(s) at ${outputDir}`);
|
|
7661
7726
|
logger.step("Run `kora generate types` first if ../types is missing.");
|
|
@@ -7784,8 +7849,8 @@ var logsCommand = (0, import_citty9.defineCommand)({
|
|
|
7784
7849
|
});
|
|
7785
7850
|
|
|
7786
7851
|
// src/commands/migrate/migrate-command.ts
|
|
7787
|
-
var
|
|
7788
|
-
var
|
|
7852
|
+
var import_promises19 = require("fs/promises");
|
|
7853
|
+
var import_node_path28 = require("path");
|
|
7789
7854
|
var import_citty10 = require("citty");
|
|
7790
7855
|
init_fs_helpers();
|
|
7791
7856
|
|
|
@@ -7879,8 +7944,8 @@ function isBreakingChange(change) {
|
|
|
7879
7944
|
if (change.type === "field-changed") {
|
|
7880
7945
|
if (change.before.kind !== change.after.kind) return true;
|
|
7881
7946
|
if (change.before.itemKind !== change.after.itemKind) return true;
|
|
7882
|
-
|
|
7883
|
-
|
|
7947
|
+
const kept = new Set(change.after.enumValues ?? []);
|
|
7948
|
+
if ((change.before.enumValues ?? []).some((value) => !kept.has(value))) return true;
|
|
7884
7949
|
if (change.before.required !== change.after.required && change.after.required) return true;
|
|
7885
7950
|
return false;
|
|
7886
7951
|
}
|
|
@@ -7909,22 +7974,473 @@ function compareChanges(left, right) {
|
|
|
7909
7974
|
return 0;
|
|
7910
7975
|
}
|
|
7911
7976
|
|
|
7977
|
+
// src/commands/migrate/table-evolution-directive.ts
|
|
7978
|
+
var import_internal = require("@korajs/core/internal");
|
|
7979
|
+
var EVOLVE_TABLE_DIRECTIVE = "--kora:evolve-table";
|
|
7980
|
+
function evolveFieldSpec(descriptor) {
|
|
7981
|
+
return {
|
|
7982
|
+
kind: descriptor.kind,
|
|
7983
|
+
...descriptor.itemKind ? { itemKind: descriptor.itemKind } : {},
|
|
7984
|
+
...descriptor.enumValues ? { enumValues: [...descriptor.enumValues] } : {},
|
|
7985
|
+
...descriptor.defaultValue !== void 0 ? { default: descriptor.defaultValue } : {},
|
|
7986
|
+
...descriptor.auto ? { auto: true } : {}
|
|
7987
|
+
};
|
|
7988
|
+
}
|
|
7989
|
+
function formatEvolveTableDirective(target) {
|
|
7990
|
+
return `${EVOLVE_TABLE_DIRECTIVE} ${JSON.stringify(target)}`;
|
|
7991
|
+
}
|
|
7992
|
+
function parseEvolveTableDirective(statement) {
|
|
7993
|
+
const trimmed = statement.trimStart();
|
|
7994
|
+
if (!trimmed.startsWith(EVOLVE_TABLE_DIRECTIVE)) return null;
|
|
7995
|
+
const payload = trimmed.slice(EVOLVE_TABLE_DIRECTIVE.length).trim();
|
|
7996
|
+
let parsed;
|
|
7997
|
+
try {
|
|
7998
|
+
parsed = JSON.parse(payload);
|
|
7999
|
+
} catch {
|
|
8000
|
+
parsed = null;
|
|
8001
|
+
}
|
|
8002
|
+
const target = parsed;
|
|
8003
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8004
|
+
const isStrings = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
8005
|
+
const isSpec = (value) => isRecord(value) && typeof value.kind === "string";
|
|
8006
|
+
if (!isRecord(target) || typeof target.table !== "string" || !isRecord(target.add) || !Object.values(target.add).every(isSpec) || !isStrings(target.drop) || !isRecord(target.change) || !Object.values(target.change).every(
|
|
8007
|
+
(change) => isRecord(change) && isSpec(change.from) && isSpec(change.to)
|
|
8008
|
+
) || !isStrings(target.addIndexes) || !isStrings(target.removeIndexes)) {
|
|
8009
|
+
throw new Error(
|
|
8010
|
+
`Malformed migration directive "${statement}": expected ${EVOLVE_TABLE_DIRECTIVE} {"table", "add", "drop", "change", "addIndexes", "removeIndexes"}. Regenerate the migration with \`kora migrate\`.`
|
|
8011
|
+
);
|
|
8012
|
+
}
|
|
8013
|
+
return {
|
|
8014
|
+
table: target.table,
|
|
8015
|
+
add: target.add,
|
|
8016
|
+
drop: [...target.drop],
|
|
8017
|
+
change: target.change,
|
|
8018
|
+
addIndexes: [...target.addIndexes],
|
|
8019
|
+
removeIndexes: [...target.removeIndexes]
|
|
8020
|
+
};
|
|
8021
|
+
}
|
|
8022
|
+
async function expandEvolveTableForSqlite(target, query, now) {
|
|
8023
|
+
const catalog = await (0, import_internal.readSqliteTableCatalog)(query, target.table);
|
|
8024
|
+
if (!catalog) return [];
|
|
8025
|
+
const table = quoteIdentifier(target.table);
|
|
8026
|
+
const existing = new Map(
|
|
8027
|
+
catalog.columns.map((column) => [column.name, column.type.toUpperCase()])
|
|
8028
|
+
);
|
|
8029
|
+
const plan = planColumns(target, existing, sqliteType);
|
|
8030
|
+
const statements = [];
|
|
8031
|
+
const presentIndexes = await indexColumnsByName(query, target.table);
|
|
8032
|
+
const removedIndexNames = new Set(
|
|
8033
|
+
target.removeIndexes.flatMap((field) => koraIndexNames(target.table, field)).filter((name) => presentIndexes.has(name))
|
|
8034
|
+
);
|
|
8035
|
+
for (const name of removedIndexNames) {
|
|
8036
|
+
statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(name)}`);
|
|
8037
|
+
}
|
|
8038
|
+
if (plan.drop.length > 0 || plan.change.length > 0) {
|
|
8039
|
+
statements.push(
|
|
8040
|
+
...await sqliteRebuildStatements(catalog, plan, query, removedIndexNames, now)
|
|
8041
|
+
);
|
|
8042
|
+
} else {
|
|
8043
|
+
for (const [field, spec] of plan.add) {
|
|
8044
|
+
statements.push(
|
|
8045
|
+
`ALTER TABLE ${table} ADD COLUMN ${quoteIdentifier(field)} ${sqliteType(spec)}${defaultClause(spec)}`
|
|
8046
|
+
);
|
|
8047
|
+
if (spec.auto && spec.kind === "timestamp" && spec.default === void 0) {
|
|
8048
|
+
statements.push(`UPDATE ${table} SET ${quoteIdentifier(field)} = ${now}`);
|
|
8049
|
+
}
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
const indexed = await singleColumnIndexes(query, target.table, removedIndexNames);
|
|
8053
|
+
for (const field of target.addIndexes) {
|
|
8054
|
+
if (indexed.has(field)) continue;
|
|
8055
|
+
statements.push(
|
|
8056
|
+
`CREATE INDEX IF NOT EXISTS ${koraIndexNames(target.table, field)[0]} ON ${table} (${quoteIdentifier(field)})`
|
|
8057
|
+
);
|
|
8058
|
+
}
|
|
8059
|
+
return statements;
|
|
8060
|
+
}
|
|
8061
|
+
async function expandEvolveTableForPostgres(target, query, now) {
|
|
8062
|
+
const columns = await query(
|
|
8063
|
+
`SELECT column_name::text AS name, upper(data_type::text) AS type FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ${sqlText(target.table)}`
|
|
8064
|
+
);
|
|
8065
|
+
if (columns.length === 0) return [];
|
|
8066
|
+
const table = quoteIdentifier(target.table);
|
|
8067
|
+
const existing = new Map(columns.map((row) => [String(row.name), String(row.type)]));
|
|
8068
|
+
const plan = planColumns(target, existing, postgresType);
|
|
8069
|
+
const statements = [];
|
|
8070
|
+
const indexNames = new Set(
|
|
8071
|
+
(await query(
|
|
8072
|
+
`SELECT indexname::text AS name FROM pg_indexes WHERE schemaname = current_schema() AND tablename = ${sqlText(target.table)}`
|
|
8073
|
+
)).map((row) => String(row.name))
|
|
8074
|
+
);
|
|
8075
|
+
for (const field of target.removeIndexes) {
|
|
8076
|
+
const [serverName, clientName] = koraIndexNames(target.table, field);
|
|
8077
|
+
if (indexNames.has(serverName.toLowerCase())) {
|
|
8078
|
+
statements.push(`DROP INDEX IF EXISTS ${serverName}`);
|
|
8079
|
+
}
|
|
8080
|
+
if (indexNames.has(clientName)) {
|
|
8081
|
+
statements.push(`DROP INDEX IF EXISTS ${quoteIdentifier(clientName)}`);
|
|
8082
|
+
}
|
|
8083
|
+
}
|
|
8084
|
+
for (const field of plan.drop) {
|
|
8085
|
+
statements.push(`ALTER TABLE ${table} DROP COLUMN IF EXISTS ${quoteIdentifier(field)}`);
|
|
8086
|
+
}
|
|
8087
|
+
for (const [field, change] of plan.change) {
|
|
8088
|
+
const column = quoteIdentifier(field);
|
|
8089
|
+
const type = postgresType(change.to);
|
|
8090
|
+
statements.push(`ALTER TABLE ${table} ALTER COLUMN ${column} DROP DEFAULT`);
|
|
8091
|
+
statements.push(
|
|
8092
|
+
`ALTER TABLE ${table} ALTER COLUMN ${column} TYPE ${type} USING CAST((${postgresProjection(column, change.from, change.to, now)}) AS ${type})`
|
|
8093
|
+
);
|
|
8094
|
+
if (change.to.default !== void 0) {
|
|
8095
|
+
statements.push(
|
|
8096
|
+
`ALTER TABLE ${table} ALTER COLUMN ${column} SET DEFAULT ${(0, import_internal.sqlDefaultLiteral)(change.to.default)}`
|
|
8097
|
+
);
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
for (const [field, spec] of plan.add) {
|
|
8101
|
+
statements.push(
|
|
8102
|
+
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${quoteIdentifier(field)} ${postgresType(spec)}${defaultClause(spec)}`
|
|
8103
|
+
);
|
|
8104
|
+
if (spec.auto && spec.kind === "timestamp" && spec.default === void 0) {
|
|
8105
|
+
statements.push(`UPDATE ${table} SET ${quoteIdentifier(field)} = ${now}`);
|
|
8106
|
+
}
|
|
8107
|
+
}
|
|
8108
|
+
for (const field of target.addIndexes) {
|
|
8109
|
+
const name = koraIndexNames(target.table, field)[0];
|
|
8110
|
+
if (indexNames.has(name.toLowerCase())) continue;
|
|
8111
|
+
statements.push(`CREATE INDEX IF NOT EXISTS ${name} ON ${table} (${quoteIdentifier(field)})`);
|
|
8112
|
+
}
|
|
8113
|
+
return statements;
|
|
8114
|
+
}
|
|
8115
|
+
function planColumns(target, existing, typeOf) {
|
|
8116
|
+
const plan = { add: [], drop: [], change: [] };
|
|
8117
|
+
for (const [field, spec] of Object.entries(target.add)) {
|
|
8118
|
+
if (!existing.has(field)) plan.add.push([field, spec]);
|
|
8119
|
+
}
|
|
8120
|
+
for (const [field, change] of Object.entries(target.change)) {
|
|
8121
|
+
const current = existing.get(field);
|
|
8122
|
+
if (current === void 0) {
|
|
8123
|
+
plan.add.push([field, change.to]);
|
|
8124
|
+
continue;
|
|
8125
|
+
}
|
|
8126
|
+
const from = typeOf(change.from);
|
|
8127
|
+
const to = typeOf(change.to);
|
|
8128
|
+
if (from !== to && current === to) continue;
|
|
8129
|
+
plan.change.push([field, change]);
|
|
8130
|
+
}
|
|
8131
|
+
for (const field of target.drop) {
|
|
8132
|
+
if (existing.has(field) && !(field in target.add) && !(field in target.change)) {
|
|
8133
|
+
if ((0, import_internal.isKoraInternalColumn)(field)) {
|
|
8134
|
+
throw new Error(
|
|
8135
|
+
`Migration directive refuses to drop Kora's own column "${field}" of "${target.table}".`
|
|
8136
|
+
);
|
|
8137
|
+
}
|
|
8138
|
+
plan.drop.push(field);
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
return plan;
|
|
8142
|
+
}
|
|
8143
|
+
async function sqliteRebuildStatements(catalog, plan, query, removedIndexNames, now) {
|
|
8144
|
+
const table = catalog.table;
|
|
8145
|
+
const temp = `_kora_evolve_${table}`;
|
|
8146
|
+
const dropped = new Set(plan.drop);
|
|
8147
|
+
const changed = new Map(plan.change);
|
|
8148
|
+
const ordered = [...catalog.columns].sort((a, b) => a.cid - b.cid).filter((column) => !dropped.has(column.name));
|
|
8149
|
+
const pkColumns = ordered.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk);
|
|
8150
|
+
const fkGroups = /* @__PURE__ */ new Map();
|
|
8151
|
+
for (const fk of catalog.foreignKeys) {
|
|
8152
|
+
const group = fkGroups.get(fk.id) ?? [];
|
|
8153
|
+
group.push(fk);
|
|
8154
|
+
fkGroups.set(fk.id, group);
|
|
8155
|
+
}
|
|
8156
|
+
const columnReferences = /* @__PURE__ */ new Map();
|
|
8157
|
+
const tableConstraints = [];
|
|
8158
|
+
for (const group of fkGroups.values()) {
|
|
8159
|
+
group.sort((a, b) => a.seq - b.seq);
|
|
8160
|
+
const first = group[0];
|
|
8161
|
+
if (!first || group.some((fk) => dropped.has(fk.from))) continue;
|
|
8162
|
+
const actions = foreignKeyActions(first);
|
|
8163
|
+
const targetRef = group.every((fk) => fk.to !== null && fk.to !== "") ? `${quoteIdentifier(first.table)}(${group.map((fk) => quoteIdentifier(fk.to ?? "")).join(", ")})` : quoteIdentifier(first.table);
|
|
8164
|
+
if (group.length === 1) {
|
|
8165
|
+
columnReferences.set(first.from, `REFERENCES ${targetRef}${actions}`);
|
|
8166
|
+
} else {
|
|
8167
|
+
tableConstraints.push(
|
|
8168
|
+
`FOREIGN KEY (${group.map((fk) => quoteIdentifier(fk.from)).join(", ")}) REFERENCES ${targetRef}${actions}`
|
|
8169
|
+
);
|
|
8170
|
+
}
|
|
8171
|
+
}
|
|
8172
|
+
if (pkColumns.length > 1) {
|
|
8173
|
+
tableConstraints.unshift(
|
|
8174
|
+
`PRIMARY KEY (${pkColumns.map((c) => quoteIdentifier(c.name)).join(", ")})`
|
|
8175
|
+
);
|
|
8176
|
+
}
|
|
8177
|
+
for (const unique of catalog.uniqueConstraints) {
|
|
8178
|
+
if (unique.some((column) => dropped.has(column))) continue;
|
|
8179
|
+
tableConstraints.push(`UNIQUE (${unique.map(quoteIdentifier).join(", ")})`);
|
|
8180
|
+
}
|
|
8181
|
+
for (const check of (0, import_internal.parseSqliteCheckConstraints)(catalog.sql)) {
|
|
8182
|
+
if ((0, import_internal.sqliteCheckReferencesColumn)(check, dropped)) continue;
|
|
8183
|
+
tableConstraints.push((0, import_internal.sqliteCheckConstraintDefinition)(check));
|
|
8184
|
+
}
|
|
8185
|
+
const definitions = [];
|
|
8186
|
+
const targetColumns = [];
|
|
8187
|
+
const selections = [];
|
|
8188
|
+
for (const column of ordered) {
|
|
8189
|
+
const change = changed.get(column.name);
|
|
8190
|
+
const parts = [quoteIdentifier(column.name)];
|
|
8191
|
+
if (change) {
|
|
8192
|
+
parts.push(sqliteType(change.to));
|
|
8193
|
+
} else if (column.type) {
|
|
8194
|
+
parts.push(column.type);
|
|
8195
|
+
}
|
|
8196
|
+
if (pkColumns.length === 1 && column.pk === 1) parts.push("PRIMARY KEY");
|
|
8197
|
+
if (column.notnull === 1) parts.push("NOT NULL");
|
|
8198
|
+
if (change) {
|
|
8199
|
+
if (change.to.default !== void 0) {
|
|
8200
|
+
parts.push(`DEFAULT ${(0, import_internal.sqlDefaultLiteral)(change.to.default)}`);
|
|
8201
|
+
}
|
|
8202
|
+
} else if (column.dflt_value !== null) {
|
|
8203
|
+
parts.push(`DEFAULT (${column.dflt_value})`);
|
|
8204
|
+
}
|
|
8205
|
+
const reference = columnReferences.get(column.name);
|
|
8206
|
+
if (reference) parts.push(reference);
|
|
8207
|
+
definitions.push(parts.join(" "));
|
|
8208
|
+
targetColumns.push(quoteIdentifier(column.name));
|
|
8209
|
+
selections.push(
|
|
8210
|
+
change ? sqliteProjection(quoteIdentifier(column.name), change.from, change.to, now) : quoteIdentifier(column.name)
|
|
8211
|
+
);
|
|
8212
|
+
}
|
|
8213
|
+
for (const [field, spec] of plan.add) {
|
|
8214
|
+
definitions.push(`${quoteIdentifier(field)} ${sqliteType(spec)}${defaultClause(spec)}`);
|
|
8215
|
+
targetColumns.push(quoteIdentifier(field));
|
|
8216
|
+
selections.push(fallbackLiteral(spec, now));
|
|
8217
|
+
}
|
|
8218
|
+
const indexColumns = await indexColumnsByName(query, table);
|
|
8219
|
+
const dependents = [];
|
|
8220
|
+
for (const row of await query(
|
|
8221
|
+
`SELECT type, name, sql FROM sqlite_master WHERE type IN ('index', 'trigger') AND tbl_name = ${sqlText(table)} AND sql IS NOT NULL ORDER BY type, name`
|
|
8222
|
+
)) {
|
|
8223
|
+
const name = String(row.name);
|
|
8224
|
+
if (row.type === "index") {
|
|
8225
|
+
if (removedIndexNames.has(name)) continue;
|
|
8226
|
+
if ((indexColumns.get(name) ?? []).some((column) => dropped.has(column))) continue;
|
|
8227
|
+
}
|
|
8228
|
+
dependents.push(String(row.sql));
|
|
8229
|
+
}
|
|
8230
|
+
return [
|
|
8231
|
+
`DROP TABLE IF EXISTS ${quoteIdentifier(temp)}`,
|
|
8232
|
+
`CREATE TABLE ${quoteIdentifier(temp)} (
|
|
8233
|
+
${[...definitions, ...tableConstraints].join(",\n ")}
|
|
8234
|
+
)`,
|
|
8235
|
+
`INSERT INTO ${quoteIdentifier(temp)} (${targetColumns.join(", ")}) SELECT ${selections.join(", ")} FROM ${quoteIdentifier(table)}`,
|
|
8236
|
+
`DROP TABLE ${quoteIdentifier(table)}`,
|
|
8237
|
+
`ALTER TABLE ${quoteIdentifier(temp)} RENAME TO ${quoteIdentifier(table)}`,
|
|
8238
|
+
...dependents
|
|
8239
|
+
];
|
|
8240
|
+
}
|
|
8241
|
+
async function indexColumnsByName(query, table) {
|
|
8242
|
+
const out = /* @__PURE__ */ new Map();
|
|
8243
|
+
for (const index of await query(`SELECT name FROM pragma_index_list(${sqlText(table)})`)) {
|
|
8244
|
+
const name = String(index.name);
|
|
8245
|
+
const info = await query(`SELECT name FROM pragma_index_info(${sqlText(name)}) ORDER BY seqno`);
|
|
8246
|
+
out.set(
|
|
8247
|
+
name,
|
|
8248
|
+
info.filter((row) => typeof row.name === "string").map((row) => String(row.name))
|
|
8249
|
+
);
|
|
8250
|
+
}
|
|
8251
|
+
return out;
|
|
8252
|
+
}
|
|
8253
|
+
async function singleColumnIndexes(query, table, ignore) {
|
|
8254
|
+
const out = /* @__PURE__ */ new Set();
|
|
8255
|
+
for (const [name, columns] of await indexColumnsByName(query, table)) {
|
|
8256
|
+
if (ignore.has(name)) continue;
|
|
8257
|
+
const only = columns[0];
|
|
8258
|
+
if (columns.length === 1 && only !== void 0) out.add(only);
|
|
8259
|
+
}
|
|
8260
|
+
return out;
|
|
8261
|
+
}
|
|
8262
|
+
function koraIndexNames(table, field) {
|
|
8263
|
+
quoteIdentifier(table);
|
|
8264
|
+
quoteIdentifier(field);
|
|
8265
|
+
return [`idx_${table}_${field}`, (0, import_internal.collectionIndexName)(table, field)];
|
|
8266
|
+
}
|
|
8267
|
+
function defaultClause(spec) {
|
|
8268
|
+
return spec.default !== void 0 ? ` DEFAULT ${(0, import_internal.sqlDefaultLiteral)(spec.default)}` : "";
|
|
8269
|
+
}
|
|
8270
|
+
function sqliteType(spec) {
|
|
8271
|
+
switch (spec.kind) {
|
|
8272
|
+
case "number":
|
|
8273
|
+
return "REAL";
|
|
8274
|
+
case "boolean":
|
|
8275
|
+
case "timestamp":
|
|
8276
|
+
return "INTEGER";
|
|
8277
|
+
case "richtext":
|
|
8278
|
+
return "BLOB";
|
|
8279
|
+
default:
|
|
8280
|
+
return "TEXT";
|
|
8281
|
+
}
|
|
8282
|
+
}
|
|
8283
|
+
function postgresType(spec) {
|
|
8284
|
+
switch (spec.kind) {
|
|
8285
|
+
case "number":
|
|
8286
|
+
return "DOUBLE PRECISION";
|
|
8287
|
+
case "boolean":
|
|
8288
|
+
return "INTEGER";
|
|
8289
|
+
case "timestamp":
|
|
8290
|
+
return "BIGINT";
|
|
8291
|
+
case "array":
|
|
8292
|
+
case "object":
|
|
8293
|
+
case "json":
|
|
8294
|
+
return "JSONB";
|
|
8295
|
+
case "richtext":
|
|
8296
|
+
return "BYTEA";
|
|
8297
|
+
default:
|
|
8298
|
+
return "TEXT";
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
function fallbackLiteral(target, now) {
|
|
8302
|
+
if (target.auto && target.kind === "timestamp") return String(now);
|
|
8303
|
+
if (target.default !== void 0) return (0, import_internal.sqlDefaultLiteral)(target.default);
|
|
8304
|
+
return "NULL";
|
|
8305
|
+
}
|
|
8306
|
+
var NUMERIC_SOURCES = /* @__PURE__ */ new Set(["number", "timestamp", "boolean"]);
|
|
8307
|
+
var TEXT_SOURCES = /* @__PURE__ */ new Set(["string", "enum"]);
|
|
8308
|
+
var TRUE_WORDS = "('1','true','t','yes','y','on')";
|
|
8309
|
+
var FALSE_WORDS = "('0','false','f','no','n','off')";
|
|
8310
|
+
function sqliteProjection(column, source, target, now) {
|
|
8311
|
+
if (source.kind === target.kind && source.itemKind === target.itemKind) return column;
|
|
8312
|
+
const fallback = fallbackLiteral(target, now);
|
|
8313
|
+
if (target.kind === "string") return `CAST(${column} AS TEXT)`;
|
|
8314
|
+
if (target.kind === "number" || target.kind === "timestamp") {
|
|
8315
|
+
if (NUMERIC_SOURCES.has(source.kind) || TEXT_SOURCES.has(source.kind)) {
|
|
8316
|
+
const castType = target.kind === "number" ? "REAL" : "INTEGER";
|
|
8317
|
+
return `CASE WHEN ${column} IS NULL THEN NULL ELSE CAST(${column} AS ${castType}) END`;
|
|
8318
|
+
}
|
|
8319
|
+
}
|
|
8320
|
+
if (target.kind === "boolean") {
|
|
8321
|
+
if (NUMERIC_SOURCES.has(source.kind)) {
|
|
8322
|
+
return `CASE WHEN ${column} IS NULL THEN NULL WHEN CAST(${column} AS REAL) = 0 THEN 0 ELSE 1 END`;
|
|
8323
|
+
}
|
|
8324
|
+
if (TEXT_SOURCES.has(source.kind)) {
|
|
8325
|
+
const word = `LOWER(TRIM(CAST(${column} AS TEXT)))`;
|
|
8326
|
+
return `CASE WHEN ${column} IS NULL THEN NULL WHEN ${word} IN ${TRUE_WORDS} THEN 1 WHEN ${word} IN ${FALSE_WORDS} THEN 0 ELSE ${fallback} END`;
|
|
8327
|
+
}
|
|
8328
|
+
}
|
|
8329
|
+
if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
|
|
8330
|
+
if (TEXT_SOURCES.has(source.kind)) {
|
|
8331
|
+
const allowed = target.enumValues.map((value) => (0, import_internal.sqlDefaultLiteral)(value)).join(", ");
|
|
8332
|
+
return `CASE WHEN ${column} IN (${allowed}) THEN ${column} ELSE ${fallback} END`;
|
|
8333
|
+
}
|
|
8334
|
+
}
|
|
8335
|
+
return fallback;
|
|
8336
|
+
}
|
|
8337
|
+
function postgresProjection(column, source, target, now) {
|
|
8338
|
+
if (source.kind === target.kind && source.itemKind === target.itemKind) return column;
|
|
8339
|
+
const fallback = fallbackLiteral(target, now);
|
|
8340
|
+
const text = `(${column})::text`;
|
|
8341
|
+
if (target.kind === "string") return text;
|
|
8342
|
+
if (target.kind === "number" || target.kind === "timestamp") {
|
|
8343
|
+
const toTarget = (value) => target.kind === "number" ? value : `round(${value})::bigint`;
|
|
8344
|
+
if (NUMERIC_SOURCES.has(source.kind)) {
|
|
8345
|
+
return `CASE WHEN ${column} IS NULL THEN NULL ELSE ${toTarget(`(${column})::double precision`)} END`;
|
|
8346
|
+
}
|
|
8347
|
+
if (TEXT_SOURCES.has(source.kind)) {
|
|
8348
|
+
const trimmed = `btrim(${text})`;
|
|
8349
|
+
return `CASE WHEN ${column} IS NULL THEN NULL WHEN ${trimmed} ~ '^[-+]?([0-9]+([.][0-9]*)?|[.][0-9]+)([eE][-+]?[0-9]+)?$' THEN ${toTarget(`(${trimmed})::double precision`)} ELSE ${fallback} END`;
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
if (target.kind === "boolean") {
|
|
8353
|
+
if (NUMERIC_SOURCES.has(source.kind)) {
|
|
8354
|
+
return `CASE WHEN ${column} IS NULL THEN NULL WHEN (${column})::double precision = 0 THEN 0 ELSE 1 END`;
|
|
8355
|
+
}
|
|
8356
|
+
if (TEXT_SOURCES.has(source.kind)) {
|
|
8357
|
+
const word = `lower(btrim(${text}))`;
|
|
8358
|
+
return `CASE WHEN ${column} IS NULL THEN NULL WHEN ${word} IN ${TRUE_WORDS} THEN 1 WHEN ${word} IN ${FALSE_WORDS} THEN 0 ELSE ${fallback} END`;
|
|
8359
|
+
}
|
|
8360
|
+
}
|
|
8361
|
+
if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
|
|
8362
|
+
if (TEXT_SOURCES.has(source.kind)) {
|
|
8363
|
+
const allowed = target.enumValues.map((value) => (0, import_internal.sqlDefaultLiteral)(value)).join(", ");
|
|
8364
|
+
return `CASE WHEN ${text} IN (${allowed}) THEN ${text} ELSE ${fallback} END`;
|
|
8365
|
+
}
|
|
8366
|
+
}
|
|
8367
|
+
return fallback;
|
|
8368
|
+
}
|
|
8369
|
+
function foreignKeyActions(fk) {
|
|
8370
|
+
let out = "";
|
|
8371
|
+
const onDelete = fk.on_delete?.toUpperCase();
|
|
8372
|
+
const onUpdate = fk.on_update?.toUpperCase();
|
|
8373
|
+
if (onDelete && onDelete !== "NO ACTION") out += ` ON DELETE ${onDelete}`;
|
|
8374
|
+
if (onUpdate && onUpdate !== "NO ACTION") out += ` ON UPDATE ${onUpdate}`;
|
|
8375
|
+
return out;
|
|
8376
|
+
}
|
|
8377
|
+
function sqlText(value) {
|
|
8378
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
8379
|
+
}
|
|
8380
|
+
function quoteIdentifier(identifier) {
|
|
8381
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
|
|
8382
|
+
throw new Error(`Invalid SQL identifier: ${identifier}`);
|
|
8383
|
+
}
|
|
8384
|
+
return `"${identifier}"`;
|
|
8385
|
+
}
|
|
8386
|
+
|
|
8387
|
+
// src/commands/migrate/value-domain-directive.ts
|
|
8388
|
+
var import_internal2 = require("@korajs/core/internal");
|
|
8389
|
+
var RELAX_VALUE_DOMAIN_DIRECTIVE = "--kora:relax-value-domain";
|
|
8390
|
+
function formatRelaxValueDomainDirective(target) {
|
|
8391
|
+
return `${RELAX_VALUE_DOMAIN_DIRECTIVE} ${JSON.stringify(target)}`;
|
|
8392
|
+
}
|
|
8393
|
+
function parseRelaxValueDomainDirective(statement) {
|
|
8394
|
+
const trimmed = statement.trimStart();
|
|
8395
|
+
if (!trimmed.startsWith(RELAX_VALUE_DOMAIN_DIRECTIVE)) return null;
|
|
8396
|
+
const payload = trimmed.slice(RELAX_VALUE_DOMAIN_DIRECTIVE.length).trim();
|
|
8397
|
+
let parsed;
|
|
8398
|
+
try {
|
|
8399
|
+
parsed = JSON.parse(payload);
|
|
8400
|
+
} catch {
|
|
8401
|
+
parsed = null;
|
|
8402
|
+
}
|
|
8403
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.table !== "string" || !Array.isArray(parsed.fields) || !parsed.fields.every((field) => typeof field === "string") || parsed.enums !== void 0 && (!Array.isArray(parsed.enums) || !parsed.enums.every((field) => typeof field === "string"))) {
|
|
8404
|
+
throw new Error(
|
|
8405
|
+
`Malformed migration directive "${statement}": expected ${RELAX_VALUE_DOMAIN_DIRECTIVE} {"table": "...", "fields": ["..."]}. Regenerate the migration with \`kora migrate\`.`
|
|
8406
|
+
);
|
|
8407
|
+
}
|
|
8408
|
+
const target = parsed;
|
|
8409
|
+
return {
|
|
8410
|
+
table: target.table,
|
|
8411
|
+
fields: [...target.fields],
|
|
8412
|
+
...target.enums !== void 0 ? { enums: [...target.enums] } : {}
|
|
8413
|
+
};
|
|
8414
|
+
}
|
|
8415
|
+
async function expandRelaxValueDomainForSqlite(target, query) {
|
|
8416
|
+
return (0, import_internal2.planSqliteConstraintRelaxation)(query, [target.table], {
|
|
8417
|
+
[target.table]: target.enums ?? target.fields
|
|
8418
|
+
});
|
|
8419
|
+
}
|
|
8420
|
+
async function expandRelaxValueDomainForPostgres(target, query) {
|
|
8421
|
+
return (0, import_internal2.planPostgresConstraintRelaxation)(
|
|
8422
|
+
query,
|
|
8423
|
+
{ [target.table]: target.fields },
|
|
8424
|
+
target.enums !== void 0 ? { [target.table]: target.enums } : {}
|
|
8425
|
+
);
|
|
8426
|
+
}
|
|
8427
|
+
|
|
7912
8428
|
// src/commands/migrate/migration-generator.ts
|
|
7913
8429
|
function generateMigration(previous, current, diff) {
|
|
7914
8430
|
const up = [];
|
|
7915
|
-
const
|
|
8431
|
+
const downGroups = [];
|
|
7916
8432
|
for (const change of diff.changes) {
|
|
7917
8433
|
if (change.type === "collection-added") {
|
|
7918
8434
|
const collectionDef = current.collections[change.collection];
|
|
7919
8435
|
if (!collectionDef) continue;
|
|
7920
8436
|
up.push(...(0, import_core2.generateSQL)(change.collection, collectionDef));
|
|
7921
|
-
|
|
8437
|
+
downGroups.push(dropCollectionStatements(change.collection));
|
|
7922
8438
|
}
|
|
7923
8439
|
if (change.type === "collection-removed") {
|
|
7924
8440
|
const collectionDef = previous.collections[change.collection];
|
|
7925
8441
|
up.push(...dropCollectionStatements(change.collection));
|
|
7926
8442
|
if (collectionDef) {
|
|
7927
|
-
|
|
8443
|
+
downGroups.push((0, import_core2.generateSQL)(change.collection, collectionDef));
|
|
7928
8444
|
}
|
|
7929
8445
|
}
|
|
7930
8446
|
}
|
|
@@ -7937,48 +8453,59 @@ function generateMigration(previous, current, diff) {
|
|
|
7937
8453
|
const previousDef = previous.collections[collection];
|
|
7938
8454
|
const currentDef = current.collections[collection];
|
|
7939
8455
|
if (!previousDef || !currentDef) continue;
|
|
8456
|
+
const valueDomainOnly = diff.changes.every(
|
|
8457
|
+
(change) => change.collection !== collection || change.type === "field-changed" && isValueDomainChange(change.before, change.after)
|
|
8458
|
+
);
|
|
8459
|
+
if (valueDomainOnly) {
|
|
8460
|
+
const directive = relaxValueDomainDirective(collection, previousDef, currentDef);
|
|
8461
|
+
up.push(directive);
|
|
8462
|
+
downGroups.push([directive]);
|
|
8463
|
+
continue;
|
|
8464
|
+
}
|
|
7940
8465
|
validateRebuildSafety(collection, previousDef, currentDef);
|
|
7941
|
-
|
|
7942
|
-
|
|
8466
|
+
const relax = relaxValueDomainDirective(collection, previousDef, currentDef);
|
|
8467
|
+
up.push(relax, formatEvolveTableDirective(evolveTarget(collection, previousDef, currentDef)));
|
|
8468
|
+
downGroups.push([
|
|
8469
|
+
relax,
|
|
8470
|
+
formatEvolveTableDirective(evolveTarget(collection, currentDef, previousDef))
|
|
8471
|
+
]);
|
|
7943
8472
|
}
|
|
7944
|
-
down.reverse();
|
|
7945
8473
|
return {
|
|
7946
8474
|
up,
|
|
7947
|
-
down,
|
|
8475
|
+
down: downGroups.reverse().flat(),
|
|
7948
8476
|
summary: diff.changes.map(formatChange),
|
|
7949
8477
|
containsBreakingChanges: diff.hasBreakingChanges
|
|
7950
8478
|
};
|
|
7951
8479
|
}
|
|
7952
|
-
function
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
statements.push(
|
|
7972
|
-
`INSERT INTO ${tempTable} (${columns.map(quoteIdentifier).join(", ")}) SELECT ${selectExpressions.join(", ")} FROM ${table}`
|
|
7973
|
-
);
|
|
7974
|
-
statements.push(`DROP TABLE ${table}`);
|
|
7975
|
-
statements.push(`ALTER TABLE ${tempTable} RENAME TO ${table}`);
|
|
7976
|
-
for (const indexField of to.indexes) {
|
|
7977
|
-
statements.push(
|
|
7978
|
-
`CREATE INDEX IF NOT EXISTS idx_${collection}_${indexField} ON ${table} (${quoteIdentifier(indexField)})`
|
|
7979
|
-
);
|
|
8480
|
+
function isValueDomainChange(before, after) {
|
|
8481
|
+
return before.kind === after.kind && before.itemKind === after.itemKind && before.auto === after.auto;
|
|
8482
|
+
}
|
|
8483
|
+
function evolveTarget(collection, from, to) {
|
|
8484
|
+
const target = {
|
|
8485
|
+
table: collection,
|
|
8486
|
+
add: {},
|
|
8487
|
+
drop: [],
|
|
8488
|
+
change: {},
|
|
8489
|
+
addIndexes: to.indexes.filter((field) => !from.indexes.includes(field)),
|
|
8490
|
+
removeIndexes: from.indexes.filter((field) => !to.indexes.includes(field))
|
|
8491
|
+
};
|
|
8492
|
+
for (const [field, descriptor] of Object.entries(to.fields)) {
|
|
8493
|
+
const before = from.fields[field];
|
|
8494
|
+
if (!before) {
|
|
8495
|
+
target.add[field] = evolveFieldSpec(descriptor);
|
|
8496
|
+
} else if (before.kind !== descriptor.kind || before.itemKind !== descriptor.itemKind) {
|
|
8497
|
+
target.change[field] = { from: evolveFieldSpec(before), to: evolveFieldSpec(descriptor) };
|
|
8498
|
+
}
|
|
7980
8499
|
}
|
|
7981
|
-
|
|
8500
|
+
target.drop = Object.keys(from.fields).filter((field) => !(field in to.fields));
|
|
8501
|
+
return target;
|
|
8502
|
+
}
|
|
8503
|
+
function relaxValueDomainDirective(collection, from, to) {
|
|
8504
|
+
const fields = [.../* @__PURE__ */ new Set([...Object.keys(from.fields), ...Object.keys(to.fields)])].sort();
|
|
8505
|
+
const enums = fields.filter(
|
|
8506
|
+
(field) => from.fields[field]?.kind === "enum" || to.fields[field]?.kind === "enum"
|
|
8507
|
+
);
|
|
8508
|
+
return formatRelaxValueDomainDirective({ table: collection, fields, enums });
|
|
7982
8509
|
}
|
|
7983
8510
|
function validateRebuildSafety(collection, from, to) {
|
|
7984
8511
|
for (const [fieldName, descriptor] of Object.entries(to.fields)) {
|
|
@@ -8000,69 +8527,6 @@ function validateRebuildSafety(collection, from, to) {
|
|
|
8000
8527
|
}
|
|
8001
8528
|
}
|
|
8002
8529
|
}
|
|
8003
|
-
function projectionForColumn(column, fromFields, targetDescriptor) {
|
|
8004
|
-
if (column === "id" || column === "_created_at" || column === "_updated_at" || column === "_deleted") {
|
|
8005
|
-
return quoteIdentifier(column);
|
|
8006
|
-
}
|
|
8007
|
-
const sourceDescriptor = fromFields[column];
|
|
8008
|
-
if (sourceDescriptor && targetDescriptor) {
|
|
8009
|
-
return projectionForFieldTransform(column, sourceDescriptor, targetDescriptor);
|
|
8010
|
-
}
|
|
8011
|
-
if (sourceDescriptor) {
|
|
8012
|
-
return quoteIdentifier(column);
|
|
8013
|
-
}
|
|
8014
|
-
if (!targetDescriptor) {
|
|
8015
|
-
return "NULL";
|
|
8016
|
-
}
|
|
8017
|
-
if (targetDescriptor.auto && targetDescriptor.kind === "timestamp") {
|
|
8018
|
-
return "CAST(strftime('%s','now') AS INTEGER) * 1000";
|
|
8019
|
-
}
|
|
8020
|
-
if (targetDescriptor.defaultValue !== void 0) {
|
|
8021
|
-
return sqlLiteral(targetDescriptor.defaultValue);
|
|
8022
|
-
}
|
|
8023
|
-
return "NULL";
|
|
8024
|
-
}
|
|
8025
|
-
function projectionForFieldTransform(column, source, target) {
|
|
8026
|
-
const sourceColumn = quoteIdentifier(column);
|
|
8027
|
-
if (source.kind === target.kind && source.itemKind === target.itemKind) {
|
|
8028
|
-
if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
|
|
8029
|
-
const allowed = target.enumValues.map((value) => sqlLiteral(value)).join(", ");
|
|
8030
|
-
const fallback = target.defaultValue !== void 0 ? sqlLiteral(target.defaultValue) : sourceColumn;
|
|
8031
|
-
return `CASE WHEN ${sourceColumn} IN (${allowed}) THEN ${sourceColumn} ELSE ${fallback} END`;
|
|
8032
|
-
}
|
|
8033
|
-
return sourceColumn;
|
|
8034
|
-
}
|
|
8035
|
-
if (target.kind === "string") {
|
|
8036
|
-
return `CAST(${sourceColumn} AS TEXT)`;
|
|
8037
|
-
}
|
|
8038
|
-
if (target.kind === "number" || target.kind === "timestamp") {
|
|
8039
|
-
if (source.kind === "string" || source.kind === "enum" || source.kind === "number" || source.kind === "timestamp" || source.kind === "boolean") {
|
|
8040
|
-
const castType = target.kind === "number" ? "REAL" : "INTEGER";
|
|
8041
|
-
return `CASE WHEN ${sourceColumn} IS NULL THEN NULL ELSE CAST(${sourceColumn} AS ${castType}) END`;
|
|
8042
|
-
}
|
|
8043
|
-
}
|
|
8044
|
-
if (target.kind === "boolean") {
|
|
8045
|
-
if (source.kind === "number" || source.kind === "timestamp" || source.kind === "boolean") {
|
|
8046
|
-
return `CASE WHEN ${sourceColumn} IS NULL THEN NULL WHEN CAST(${sourceColumn} AS REAL) = 0 THEN 0 ELSE 1 END`;
|
|
8047
|
-
}
|
|
8048
|
-
if (source.kind === "string" || source.kind === "enum") {
|
|
8049
|
-
return `CASE WHEN ${sourceColumn} IS NULL THEN NULL WHEN LOWER(TRIM(CAST(${sourceColumn} AS TEXT))) IN ('1','true','t','yes','y','on') THEN 1 WHEN LOWER(TRIM(CAST(${sourceColumn} AS TEXT))) IN ('0','false','f','no','n','off') THEN 0 ELSE ${projectionFallback(target)} END`;
|
|
8050
|
-
}
|
|
8051
|
-
}
|
|
8052
|
-
if (target.kind === "enum" && target.enumValues && target.enumValues.length > 0) {
|
|
8053
|
-
if (source.kind === "string" || source.kind === "enum") {
|
|
8054
|
-
const allowed = target.enumValues.map((value) => sqlLiteral(value)).join(", ");
|
|
8055
|
-
return `CASE WHEN ${sourceColumn} IN (${allowed}) THEN ${sourceColumn} ELSE ${projectionFallback(target)} END`;
|
|
8056
|
-
}
|
|
8057
|
-
}
|
|
8058
|
-
if (target.kind === "array" && source.kind === "array" && source.itemKind === target.itemKind) {
|
|
8059
|
-
return sourceColumn;
|
|
8060
|
-
}
|
|
8061
|
-
if (target.auto && target.kind === "timestamp") {
|
|
8062
|
-
return "CAST(strftime('%s','now') AS INTEGER) * 1000";
|
|
8063
|
-
}
|
|
8064
|
-
return projectionFallback(target);
|
|
8065
|
-
}
|
|
8066
8530
|
function canTransformField(source, target) {
|
|
8067
8531
|
if (source.kind === target.kind && source.itemKind === target.itemKind) {
|
|
8068
8532
|
return true;
|
|
@@ -8087,69 +8551,12 @@ function canTransformField(source, target) {
|
|
|
8087
8551
|
}
|
|
8088
8552
|
return false;
|
|
8089
8553
|
}
|
|
8090
|
-
function projectionFallback(target) {
|
|
8091
|
-
if (target.auto && target.kind === "timestamp") {
|
|
8092
|
-
return "CAST(strftime('%s','now') AS INTEGER) * 1000";
|
|
8093
|
-
}
|
|
8094
|
-
if (target.defaultValue !== void 0) {
|
|
8095
|
-
return sqlLiteral(target.defaultValue);
|
|
8096
|
-
}
|
|
8097
|
-
return "NULL";
|
|
8098
|
-
}
|
|
8099
8554
|
function dropCollectionStatements(collection) {
|
|
8100
|
-
const table =
|
|
8101
|
-
const opsTable =
|
|
8555
|
+
const table = quoteIdentifier2(collection);
|
|
8556
|
+
const opsTable = quoteIdentifier2(`_kora_ops_${collection}`);
|
|
8102
8557
|
return [`DROP TABLE IF EXISTS ${table}`, `DROP TABLE IF EXISTS ${opsTable}`];
|
|
8103
8558
|
}
|
|
8104
|
-
function
|
|
8105
|
-
const sqlType = mapFieldType(descriptor);
|
|
8106
|
-
const parts = [quoteIdentifier(fieldName), sqlType];
|
|
8107
|
-
if (descriptor.required && descriptor.defaultValue === void 0 && !descriptor.auto) {
|
|
8108
|
-
parts.push("NOT NULL");
|
|
8109
|
-
}
|
|
8110
|
-
if (descriptor.defaultValue !== void 0) {
|
|
8111
|
-
parts.push(`DEFAULT ${sqlLiteral(descriptor.defaultValue)}`);
|
|
8112
|
-
}
|
|
8113
|
-
if (descriptor.kind === "enum" && descriptor.enumValues) {
|
|
8114
|
-
const values = descriptor.enumValues.map((value) => sqlLiteral(value)).join(", ");
|
|
8115
|
-
parts.push(`CHECK (${quoteIdentifier(fieldName)} IN (${values}))`);
|
|
8116
|
-
}
|
|
8117
|
-
return parts.join(" ");
|
|
8118
|
-
}
|
|
8119
|
-
function mapFieldType(descriptor) {
|
|
8120
|
-
switch (descriptor.kind) {
|
|
8121
|
-
case "string":
|
|
8122
|
-
return "TEXT";
|
|
8123
|
-
case "number":
|
|
8124
|
-
return "REAL";
|
|
8125
|
-
case "boolean":
|
|
8126
|
-
return "INTEGER";
|
|
8127
|
-
case "enum":
|
|
8128
|
-
return "TEXT";
|
|
8129
|
-
case "timestamp":
|
|
8130
|
-
return "INTEGER";
|
|
8131
|
-
case "array":
|
|
8132
|
-
return "TEXT";
|
|
8133
|
-
case "object":
|
|
8134
|
-
return "TEXT";
|
|
8135
|
-
case "json":
|
|
8136
|
-
return "TEXT";
|
|
8137
|
-
case "blob":
|
|
8138
|
-
return "TEXT";
|
|
8139
|
-
case "secret":
|
|
8140
|
-
return "TEXT";
|
|
8141
|
-
case "richtext":
|
|
8142
|
-
return "BLOB";
|
|
8143
|
-
}
|
|
8144
|
-
}
|
|
8145
|
-
function sqlLiteral(value) {
|
|
8146
|
-
if (value === null) return "NULL";
|
|
8147
|
-
if (typeof value === "number") return String(value);
|
|
8148
|
-
if (typeof value === "boolean") return value ? "1" : "0";
|
|
8149
|
-
if (typeof value === "string") return `'${value.replaceAll("'", "''")}'`;
|
|
8150
|
-
return `'${JSON.stringify(value).replaceAll("'", "''")}'`;
|
|
8151
|
-
}
|
|
8152
|
-
function quoteIdentifier(identifier) {
|
|
8559
|
+
function quoteIdentifier2(identifier) {
|
|
8153
8560
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier)) {
|
|
8154
8561
|
throw new Error(`Invalid SQL identifier: ${identifier}`);
|
|
8155
8562
|
}
|
|
@@ -8228,6 +8635,8 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
|
|
|
8228
8635
|
const driver = driverOverride ?? await loadSqliteDriver(projectRoot);
|
|
8229
8636
|
const db = driver.open(path);
|
|
8230
8637
|
let statementsApplied = 0;
|
|
8638
|
+
const foreignKeysOn = Number(db.query?.("PRAGMA foreign_keys")[0]?.foreign_keys ?? 0) === 1;
|
|
8639
|
+
if (foreignKeysOn) db.exec("PRAGMA foreign_keys = OFF");
|
|
8231
8640
|
try {
|
|
8232
8641
|
db.exec("BEGIN");
|
|
8233
8642
|
db.exec(
|
|
@@ -8243,12 +8652,38 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
|
|
|
8243
8652
|
skipped: true
|
|
8244
8653
|
};
|
|
8245
8654
|
}
|
|
8655
|
+
const now = Date.now();
|
|
8656
|
+
const catalogQuery = (table) => {
|
|
8657
|
+
const query = db.query?.bind(db);
|
|
8658
|
+
if (!query) {
|
|
8659
|
+
throw new Error(
|
|
8660
|
+
`Migration ${migrationId}: the SQLite driver cannot read the catalog needed to migrate "${table}".`
|
|
8661
|
+
);
|
|
8662
|
+
}
|
|
8663
|
+
return async (text) => query(text);
|
|
8664
|
+
};
|
|
8246
8665
|
for (const statement of statements) {
|
|
8247
|
-
|
|
8666
|
+
const relax = parseRelaxValueDomainDirective(statement);
|
|
8667
|
+
const evolve = relax ? null : parseEvolveTableDirective(statement);
|
|
8668
|
+
if (relax) {
|
|
8669
|
+
const expanded = await expandRelaxValueDomainForSqlite(relax, catalogQuery(relax.table));
|
|
8670
|
+
for (const rebuild of expanded) db.exec(rebuild);
|
|
8671
|
+
} else if (evolve) {
|
|
8672
|
+
const expanded = await expandEvolveTableForSqlite(evolve, catalogQuery(evolve.table), now);
|
|
8673
|
+
for (const change of expanded) db.exec(change);
|
|
8674
|
+
} else {
|
|
8675
|
+
db.exec(statement);
|
|
8676
|
+
}
|
|
8248
8677
|
statementsApplied++;
|
|
8249
8678
|
}
|
|
8679
|
+
const violations = db.query?.("PRAGMA foreign_key_check") ?? [];
|
|
8680
|
+
if (violations.length > 0) {
|
|
8681
|
+
throw new Error(
|
|
8682
|
+
`Migration ${migrationId} left ${violations.length} foreign key violation(s) (first: ${JSON.stringify(violations[0])}); nothing was applied.`
|
|
8683
|
+
);
|
|
8684
|
+
}
|
|
8250
8685
|
db.exec(
|
|
8251
|
-
`INSERT OR REPLACE INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${
|
|
8686
|
+
`INSERT OR REPLACE INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${sqlLiteral(migrationId)}, ${fromVersion}, ${toVersion}, ${Date.now()})`
|
|
8252
8687
|
);
|
|
8253
8688
|
db.exec("COMMIT");
|
|
8254
8689
|
return {
|
|
@@ -8264,6 +8699,12 @@ async function runSqliteMigration(path, statements, migrationId, fromVersion, to
|
|
|
8264
8699
|
}
|
|
8265
8700
|
throw error;
|
|
8266
8701
|
} finally {
|
|
8702
|
+
if (foreignKeysOn) {
|
|
8703
|
+
try {
|
|
8704
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
8705
|
+
} catch {
|
|
8706
|
+
}
|
|
8707
|
+
}
|
|
8267
8708
|
if (typeof db.close === "function") {
|
|
8268
8709
|
db.close();
|
|
8269
8710
|
}
|
|
@@ -8285,6 +8726,9 @@ async function loadSqliteDriver(projectRoot) {
|
|
|
8285
8726
|
const row = db.prepare("SELECT COUNT(*) AS count FROM _kora_migrations WHERE id = ?").get(id);
|
|
8286
8727
|
return (row?.count ?? 0) > 0;
|
|
8287
8728
|
},
|
|
8729
|
+
query(sql) {
|
|
8730
|
+
return db.prepare(sql).all();
|
|
8731
|
+
},
|
|
8288
8732
|
close() {
|
|
8289
8733
|
db.close();
|
|
8290
8734
|
}
|
|
@@ -8298,7 +8742,11 @@ async function loadSqliteDriver(projectRoot) {
|
|
|
8298
8742
|
}
|
|
8299
8743
|
}
|
|
8300
8744
|
async function runPostgresMigration(connectionString, statements, migrationId, fromVersion, toVersion, clientFactoryOverride) {
|
|
8301
|
-
const sql = typeof clientFactoryOverride === "function" ? clientFactoryOverride(connectionString) : (
|
|
8745
|
+
const sql = typeof clientFactoryOverride === "function" ? clientFactoryOverride(connectionString) : (
|
|
8746
|
+
// One connection: BEGIN, the statements and COMMIT must share it (postgres.js
|
|
8747
|
+
// refuses a raw BEGIN on a pool, UNSAFE_TRANSACTION).
|
|
8748
|
+
(await loadPostgresModule()).default(connectionString, { max: 1 })
|
|
8749
|
+
);
|
|
8302
8750
|
let statementsApplied = 0;
|
|
8303
8751
|
try {
|
|
8304
8752
|
await sql.unsafe("BEGIN");
|
|
@@ -8306,7 +8754,7 @@ async function runPostgresMigration(connectionString, statements, migrationId, f
|
|
|
8306
8754
|
"CREATE TABLE IF NOT EXISTS _kora_migrations (id TEXT PRIMARY KEY, from_version INTEGER NOT NULL, to_version INTEGER NOT NULL, applied_at BIGINT NOT NULL)"
|
|
8307
8755
|
);
|
|
8308
8756
|
const existing = await sql.unsafe(
|
|
8309
|
-
`SELECT COUNT(*)::int AS count FROM _kora_migrations WHERE id = ${
|
|
8757
|
+
`SELECT COUNT(*)::int AS count FROM _kora_migrations WHERE id = ${sqlLiteral(migrationId)}`
|
|
8310
8758
|
);
|
|
8311
8759
|
if ((existing[0]?.count ?? 0) > 0) {
|
|
8312
8760
|
await sql.unsafe("COMMIT");
|
|
@@ -8317,12 +8765,25 @@ async function runPostgresMigration(connectionString, statements, migrationId, f
|
|
|
8317
8765
|
skipped: true
|
|
8318
8766
|
};
|
|
8319
8767
|
}
|
|
8768
|
+
const now = Date.now();
|
|
8769
|
+
const query = async (text) => await sql.unsafe(text);
|
|
8320
8770
|
for (const statement of statements) {
|
|
8321
|
-
|
|
8771
|
+
const relax = parseRelaxValueDomainDirective(statement);
|
|
8772
|
+
const evolve = relax ? null : parseEvolveTableDirective(statement);
|
|
8773
|
+
if (relax) {
|
|
8774
|
+
const expanded = await expandRelaxValueDomainForPostgres(relax, query);
|
|
8775
|
+
for (const alter of expanded) await sql.unsafe(alter);
|
|
8776
|
+
} else if (evolve) {
|
|
8777
|
+
const expanded = await expandEvolveTableForPostgres(evolve, query, now);
|
|
8778
|
+
for (const alter of expanded) await sql.unsafe(alter);
|
|
8779
|
+
} else {
|
|
8780
|
+
assertNotLegacyRebuild(statement, migrationId);
|
|
8781
|
+
await sql.unsafe(statement);
|
|
8782
|
+
}
|
|
8322
8783
|
statementsApplied++;
|
|
8323
8784
|
}
|
|
8324
8785
|
await sql.unsafe(
|
|
8325
|
-
`INSERT INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${
|
|
8786
|
+
`INSERT INTO _kora_migrations (id, from_version, to_version, applied_at) VALUES (${sqlLiteral(migrationId)}, ${fromVersion}, ${toVersion}, ${Date.now()}) ON CONFLICT (id) DO UPDATE SET from_version = EXCLUDED.from_version, to_version = EXCLUDED.to_version, applied_at = EXCLUDED.applied_at`
|
|
8326
8787
|
);
|
|
8327
8788
|
await sql.unsafe("COMMIT");
|
|
8328
8789
|
return {
|
|
@@ -8343,7 +8804,14 @@ async function runPostgresMigration(connectionString, statements, migrationId, f
|
|
|
8343
8804
|
}
|
|
8344
8805
|
}
|
|
8345
8806
|
}
|
|
8346
|
-
function
|
|
8807
|
+
function assertNotLegacyRebuild(statement, migrationId) {
|
|
8808
|
+
if (/^\s*CREATE\s+TABLE\s+"?_kora_mig_[A-Za-z0-9_]+_new"?/i.test(statement)) {
|
|
8809
|
+
throw new Error(
|
|
8810
|
+
`Migration ${migrationId} was generated by an earlier \`kora migrate\` as a SQLite-only table rebuild, which cannot run on Postgres. Regenerate it: restore kora/schema.snapshot.json to the schema version it migrates from, delete the migration's files, and run \`kora migrate\` again.`
|
|
8811
|
+
);
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
function sqlLiteral(value) {
|
|
8347
8815
|
return `'${value.replaceAll("'", "''")}'`;
|
|
8348
8816
|
}
|
|
8349
8817
|
async function loadPostgresModule() {
|
|
@@ -8431,12 +8899,12 @@ var migrateCommand = (0, import_citty10.defineCommand)({
|
|
|
8431
8899
|
throw new InvalidProjectError(process.cwd());
|
|
8432
8900
|
}
|
|
8433
8901
|
const config = await loadKoraConfig(projectRoot);
|
|
8434
|
-
const resolvedSchemaPath = typeof args.schema === "string" ? (0,
|
|
8902
|
+
const resolvedSchemaPath = typeof args.schema === "string" ? (0, import_node_path28.resolve)(projectRoot, args.schema) : typeof config?.schema === "string" ? (0, import_node_path28.resolve)(projectRoot, config.schema) : await findSchemaFile(projectRoot);
|
|
8435
8903
|
if (!resolvedSchemaPath) {
|
|
8436
8904
|
throw new SchemaNotFoundError(["src/schema.ts", "schema.ts", "src/schema.js", "schema.js"]);
|
|
8437
8905
|
}
|
|
8438
8906
|
const currentSchema = await loadSchemaDefinition(resolvedSchemaPath, projectRoot);
|
|
8439
|
-
const snapshotFile = (0,
|
|
8907
|
+
const snapshotFile = (0, import_node_path28.join)(projectRoot, SNAPSHOT_PATH);
|
|
8440
8908
|
const previousSchema = await readSchemaSnapshot(snapshotFile);
|
|
8441
8909
|
if (!previousSchema) {
|
|
8442
8910
|
if (args["dry-run"] === true) {
|
|
@@ -8449,7 +8917,7 @@ var migrateCommand = (0, import_citty10.defineCommand)({
|
|
|
8449
8917
|
return;
|
|
8450
8918
|
}
|
|
8451
8919
|
const diff = diffSchemas(previousSchema, currentSchema);
|
|
8452
|
-
const outputDir = typeof args["output-dir"] === "string" ? (0,
|
|
8920
|
+
const outputDir = typeof args["output-dir"] === "string" ? (0, import_node_path28.resolve)(projectRoot, args["output-dir"]) : (0, import_node_path28.resolve)(projectRoot, MIGRATIONS_DIR);
|
|
8453
8921
|
if (!diff.hasChanges) {
|
|
8454
8922
|
logger.success("No schema changes detected.");
|
|
8455
8923
|
if (args.apply === true) {
|
|
@@ -8501,7 +8969,7 @@ var migrateCommand = (0, import_citty10.defineCommand)({
|
|
|
8501
8969
|
logger.warn("Dry run enabled: no files written, no migrations applied.");
|
|
8502
8970
|
return;
|
|
8503
8971
|
}
|
|
8504
|
-
await (0,
|
|
8972
|
+
await (0, import_promises19.mkdir)(outputDir, { recursive: true });
|
|
8505
8973
|
const migrationPath = await writeMigrationFile(
|
|
8506
8974
|
outputDir,
|
|
8507
8975
|
diff.fromVersion,
|
|
@@ -8537,7 +9005,7 @@ var migrateCommand = (0, import_citty10.defineCommand)({
|
|
|
8537
9005
|
});
|
|
8538
9006
|
async function readSchemaSnapshot(path) {
|
|
8539
9007
|
try {
|
|
8540
|
-
const content = await (0,
|
|
9008
|
+
const content = await (0, import_promises19.readFile)(path, "utf-8");
|
|
8541
9009
|
return JSON.parse(content);
|
|
8542
9010
|
} catch {
|
|
8543
9011
|
return null;
|
|
@@ -8558,15 +9026,15 @@ function isInteractiveTerminal2() {
|
|
|
8558
9026
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
8559
9027
|
}
|
|
8560
9028
|
async function writeSchemaSnapshot(path, schema) {
|
|
8561
|
-
await (0,
|
|
8562
|
-
await (0,
|
|
9029
|
+
await (0, import_promises19.mkdir)((0, import_node_path28.dirname)(path), { recursive: true });
|
|
9030
|
+
await (0, import_promises19.writeFile)(path, `${JSON.stringify(schema, null, 2)}
|
|
8563
9031
|
`, "utf-8");
|
|
8564
9032
|
}
|
|
8565
9033
|
async function writeMigrationFile(outputDir, fromVersion, toVersion, generated) {
|
|
8566
|
-
const existing = await (0,
|
|
9034
|
+
const existing = await (0, import_promises19.readdir)(outputDir).catch(() => []);
|
|
8567
9035
|
const sequence = existing.filter((file) => /^\d{3}-/.test(file)).length + 1;
|
|
8568
9036
|
const filename = `${String(sequence).padStart(3, "0")}-v${fromVersion}-to-v${toVersion}.ts`;
|
|
8569
|
-
const path = (0,
|
|
9037
|
+
const path = (0, import_node_path28.join)(outputDir, filename);
|
|
8570
9038
|
const migrationId = filename.replace(/\.ts$/, "");
|
|
8571
9039
|
const fileContent = [
|
|
8572
9040
|
`export const up = ${JSON.stringify(generated.up, null, 2)} as const`,
|
|
@@ -8578,10 +9046,10 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
|
|
|
8578
9046
|
`export const containsBreakingChanges = ${generated.containsBreakingChanges}`,
|
|
8579
9047
|
""
|
|
8580
9048
|
].join("\n");
|
|
8581
|
-
await (0,
|
|
8582
|
-
const transformsPath = (0,
|
|
8583
|
-
await (0,
|
|
8584
|
-
await writeMigrationManifest((0,
|
|
9049
|
+
await (0, import_promises19.writeFile)(path, fileContent, "utf-8");
|
|
9050
|
+
const transformsPath = (0, import_node_path28.join)(outputDir, `${migrationId}.transforms.ts`);
|
|
9051
|
+
await (0, import_promises19.writeFile)(transformsPath, generateOperationTransformModule(fromVersion, toVersion), "utf-8");
|
|
9052
|
+
await writeMigrationManifest((0, import_node_path28.join)(outputDir, `${migrationId}.json`), {
|
|
8585
9053
|
id: migrationId,
|
|
8586
9054
|
fromVersion,
|
|
8587
9055
|
toVersion,
|
|
@@ -8593,22 +9061,22 @@ async function writeMigrationFile(outputDir, fromVersion, toVersion, generated)
|
|
|
8593
9061
|
return path;
|
|
8594
9062
|
}
|
|
8595
9063
|
async function writeMigrationManifest(path, manifest) {
|
|
8596
|
-
await (0,
|
|
9064
|
+
await (0, import_promises19.writeFile)(path, `${JSON.stringify(manifest, null, 2)}
|
|
8597
9065
|
`, "utf-8");
|
|
8598
9066
|
}
|
|
8599
9067
|
async function listMigrationManifests(outputDir) {
|
|
8600
|
-
const files = await (0,
|
|
9068
|
+
const files = await (0, import_promises19.readdir)(outputDir).catch(() => []);
|
|
8601
9069
|
const migrationFiles = files.filter((file) => /^\d{3}-.*\.ts$/.test(file)).sort((left, right) => left.localeCompare(right));
|
|
8602
9070
|
const manifests = [];
|
|
8603
9071
|
for (const file of migrationFiles) {
|
|
8604
9072
|
const id = file.replace(/\.ts$/, "");
|
|
8605
|
-
const manifestPath = (0,
|
|
9073
|
+
const manifestPath = (0, import_node_path28.join)(outputDir, `${id}.json`);
|
|
8606
9074
|
const jsonManifest = await readMigrationManifest(manifestPath);
|
|
8607
9075
|
if (jsonManifest) {
|
|
8608
9076
|
manifests.push({ ...jsonManifest, id });
|
|
8609
9077
|
continue;
|
|
8610
9078
|
}
|
|
8611
|
-
const migrationPath = (0,
|
|
9079
|
+
const migrationPath = (0, import_node_path28.join)(outputDir, file);
|
|
8612
9080
|
const sourceManifest = await readMigrationManifestFromSource(migrationPath, id);
|
|
8613
9081
|
manifests.push(sourceManifest);
|
|
8614
9082
|
}
|
|
@@ -8616,7 +9084,7 @@ async function listMigrationManifests(outputDir) {
|
|
|
8616
9084
|
}
|
|
8617
9085
|
async function readMigrationManifest(path) {
|
|
8618
9086
|
try {
|
|
8619
|
-
const content = await (0,
|
|
9087
|
+
const content = await (0, import_promises19.readFile)(path, "utf-8");
|
|
8620
9088
|
return JSON.parse(content);
|
|
8621
9089
|
} catch (error) {
|
|
8622
9090
|
const code = error.code;
|
|
@@ -8627,7 +9095,7 @@ async function readMigrationManifest(path) {
|
|
|
8627
9095
|
}
|
|
8628
9096
|
}
|
|
8629
9097
|
async function readMigrationManifestFromSource(path, id) {
|
|
8630
|
-
const content = await (0,
|
|
9098
|
+
const content = await (0, import_promises19.readFile)(path, "utf-8");
|
|
8631
9099
|
const versions = parseVersionsFromMigrationId(id);
|
|
8632
9100
|
return {
|
|
8633
9101
|
id,
|
|
@@ -8689,18 +9157,18 @@ function parseLiteralExport(source, exportName) {
|
|
|
8689
9157
|
}
|
|
8690
9158
|
function resolveSqliteApplyPath(dbArg, projectRoot, config) {
|
|
8691
9159
|
if (typeof dbArg === "string") {
|
|
8692
|
-
return (0,
|
|
9160
|
+
return (0, import_node_path28.resolve)(projectRoot, dbArg);
|
|
8693
9161
|
}
|
|
8694
9162
|
const sync = config?.dev?.sync;
|
|
8695
9163
|
if (typeof sync === "object" && sync !== null) {
|
|
8696
9164
|
if (sync.store === "sqlite") {
|
|
8697
|
-
return (0,
|
|
9165
|
+
return (0, import_node_path28.join)(projectRoot, "kora-sync.db");
|
|
8698
9166
|
}
|
|
8699
9167
|
if (typeof sync.store === "object" && sync.store !== null && sync.store.type === "sqlite") {
|
|
8700
9168
|
if (typeof sync.store.filename === "string" && sync.store.filename.length > 0) {
|
|
8701
|
-
return (0,
|
|
9169
|
+
return (0, import_node_path28.resolve)(projectRoot, sync.store.filename);
|
|
8702
9170
|
}
|
|
8703
|
-
return (0,
|
|
9171
|
+
return (0, import_node_path28.join)(projectRoot, "kora-sync.db");
|
|
8704
9172
|
}
|
|
8705
9173
|
}
|
|
8706
9174
|
return void 0;
|
|
@@ -8851,7 +9319,7 @@ function printStatus(status, url, logger) {
|
|
|
8851
9319
|
|
|
8852
9320
|
// src/commands/studio/studio-command.ts
|
|
8853
9321
|
var import_node_fs9 = require("fs");
|
|
8854
|
-
var
|
|
9322
|
+
var import_node_path31 = require("path");
|
|
8855
9323
|
var import_citty12 = require("citty");
|
|
8856
9324
|
var studioCommand = (0, import_citty12.defineCommand)({
|
|
8857
9325
|
meta: {
|
|
@@ -8902,7 +9370,7 @@ var studioCommand = (0, import_citty12.defineCommand)({
|
|
|
8902
9370
|
);
|
|
8903
9371
|
}
|
|
8904
9372
|
const { loadSchemaDefinition: loadSchemaDefinition2 } = await Promise.resolve().then(() => (init_schema_loader(), schema_loader_exports));
|
|
8905
|
-
const schema = await loadSchemaDefinition2((0,
|
|
9373
|
+
const schema = await loadSchemaDefinition2((0, import_node_path31.resolve)(process.cwd(), args.schema), process.cwd());
|
|
8906
9374
|
const { SpectatorManager: SpectatorManager2 } = await Promise.resolve().then(() => (init_spectator_manager(), spectator_manager_exports));
|
|
8907
9375
|
const spectator = new SpectatorManager2({
|
|
8908
9376
|
url: args.connect,
|
|
@@ -8929,7 +9397,7 @@ var studioCommand = (0, import_citty12.defineCommand)({
|
|
|
8929
9397
|
let schema = defaultLabSchema2();
|
|
8930
9398
|
if (typeof args.schema === "string") {
|
|
8931
9399
|
const { loadSchemaDefinition: loadSchemaDefinition2 } = await Promise.resolve().then(() => (init_schema_loader(), schema_loader_exports));
|
|
8932
|
-
schema = await loadSchemaDefinition2((0,
|
|
9400
|
+
schema = await loadSchemaDefinition2((0, import_node_path31.resolve)(process.cwd(), args.schema), process.cwd());
|
|
8933
9401
|
}
|
|
8934
9402
|
const initialDevices = args.devices !== void 0 ? Math.max(1, Number.parseInt(args.devices, 10) || 2) : 2;
|
|
8935
9403
|
const lab = new LabManager2(schema);
|
|
@@ -8954,7 +9422,7 @@ var studioCommand = (0, import_citty12.defineCommand)({
|
|
|
8954
9422
|
"Pass --db <path> to inspect a database, --lab for the sync laboratory, or --connect <wss url> for live spectator mode."
|
|
8955
9423
|
);
|
|
8956
9424
|
}
|
|
8957
|
-
const dbPath = (0,
|
|
9425
|
+
const dbPath = (0, import_node_path31.resolve)(process.cwd(), args.db);
|
|
8958
9426
|
if (!(0, import_node_fs9.existsSync)(dbPath)) {
|
|
8959
9427
|
throw new Error(`Database file not found: ${dbPath}`);
|
|
8960
9428
|
}
|