@coreframe/scripts 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/bin/coreframe.js +35 -0
  2. package/dist/config.d.ts +9 -0
  3. package/dist/config.js +1 -0
  4. package/dist/coreframe.d.ts +282 -0
  5. package/dist/coreframe.js +2 -0
  6. package/dist/db/client.d.ts +30 -0
  7. package/dist/db/client.js +1 -0
  8. package/dist/db/config.d.ts +24 -0
  9. package/dist/db/config.js +1 -0
  10. package/dist/db/drizzle-kit.d.ts +9 -0
  11. package/dist/db/drizzle-kit.js +2 -0
  12. package/dist/db/ensure.d.ts +20 -0
  13. package/dist/db/ensure.js +2 -0
  14. package/dist/db/migrate-held.d.ts +18 -0
  15. package/dist/db/migrate-held.js +10 -0
  16. package/dist/db/migrate.d.ts +4 -0
  17. package/dist/db/migrate.js +2 -0
  18. package/dist/db/project.d.ts +12 -0
  19. package/dist/db/project.js +1 -0
  20. package/dist/db/reset.d.ts +4 -0
  21. package/dist/db/reset.js +2 -0
  22. package/dist/db/schema-push.d.ts +22 -0
  23. package/dist/db/schema-push.js +1 -0
  24. package/dist/db/seed.d.ts +6 -0
  25. package/dist/db/seed.js +2 -0
  26. package/dist/db/setup.d.ts +6 -0
  27. package/dist/db/setup.js +2 -0
  28. package/dist/deploy/check-migrations.d.ts +4 -0
  29. package/dist/deploy/check-migrations.js +2 -0
  30. package/dist/deploy/checks.d.ts +6 -0
  31. package/dist/deploy/checks.js +1 -0
  32. package/dist/deploy/config.d.ts +53 -0
  33. package/dist/deploy/config.js +1 -0
  34. package/dist/deploy/migrations.d.ts +24 -0
  35. package/dist/deploy/migrations.js +4 -0
  36. package/dist/deploy/pending-migrations.d.ts +9 -0
  37. package/dist/deploy/pending-migrations.js +1 -0
  38. package/dist/deploy/tauri-release.d.ts +105 -0
  39. package/dist/deploy/tauri-release.js +1 -0
  40. package/dist/deploy/version.d.ts +20 -0
  41. package/dist/deploy/version.js +1 -0
  42. package/dist/deploy.d.ts +19 -0
  43. package/dist/deploy.js +2 -0
  44. package/dist/dev.d.ts +21 -0
  45. package/dist/dev.js +2 -0
  46. package/dist/image-generator.d.ts +6 -0
  47. package/dist/image-generator.js +1 -0
  48. package/dist/project.d.ts +4 -0
  49. package/dist/project.js +1 -0
  50. package/dist/typecheck.d.ts +4 -0
  51. package/dist/typecheck.js +2 -0
  52. package/dist/upgrade/check-skill-current.d.ts +11 -0
  53. package/dist/upgrade/check-skill-current.js +2 -0
  54. package/dist/upgrade/ensure-template-remote.d.ts +11 -0
  55. package/dist/upgrade/ensure-template-remote.js +2 -0
  56. package/dist/upgrade/fast-forward-template-files.d.ts +9 -0
  57. package/dist/upgrade/fast-forward-template-files.js +2 -0
  58. package/package.json +53 -8
  59. package/readme.md +0 -1
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process"
3
+ import { existsSync } from "node:fs"
4
+ import { dirname, resolve } from "node:path"
5
+ import { fileURLToPath } from "node:url"
6
+
7
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..")
8
+ const distBin = resolve(packageRoot, "dist/coreframe.js")
9
+
10
+ if (!existsSync(distBin)) {
11
+ const build = spawnSync("pnpm", ["--dir", packageRoot, "build"], {
12
+ shell: process.platform === "win32",
13
+ stdio: "inherit",
14
+ })
15
+
16
+ if (build.error) {
17
+ console.error(build.error.message)
18
+ process.exit(1)
19
+ }
20
+
21
+ if (build.status !== 0) {
22
+ process.exit(build.status ?? 1)
23
+ }
24
+ }
25
+
26
+ const cli = spawnSync(process.execPath, [distBin, ...process.argv.slice(2)], {
27
+ stdio: "inherit",
28
+ })
29
+
30
+ if (cli.error) {
31
+ console.error(cli.error.message)
32
+ process.exit(1)
33
+ }
34
+
35
+ process.exit(cli.status ?? 1)
@@ -0,0 +1,9 @@
1
+ import { DeployConfig } from "./deploy/config.js";
2
+
3
+ //#region config.d.ts
4
+ type CoreframeConfig = {
5
+ deploy?: DeployConfig;
6
+ };
7
+ declare function defineConfig(config: CoreframeConfig): CoreframeConfig;
8
+ //#endregion
9
+ export { CoreframeConfig, defineConfig };
package/dist/config.js ADDED
@@ -0,0 +1 @@
1
+ function e(e){return e}export{e as defineConfig};
@@ -0,0 +1,282 @@
1
+ //#region coreframe.d.ts
2
+ declare const coreframeCli: Partial<import("cmd-ts/dist/cjs/argparser").Register> & {
3
+ parse(context: import("cmd-ts/dist/cjs/argparser").ParseContext): Promise<import("cmd-ts/dist/cjs/argparser").ParsingResult<{
4
+ command: "deploy";
5
+ args: {
6
+ runMigrations: boolean;
7
+ skipMigrations: boolean;
8
+ skipPostDeployChecks: boolean;
9
+ tauriLocalPlatforms: ("macos" | "windows" | "linux" | "ios" | "android")[];
10
+ tauriWorkflowName: string | undefined;
11
+ tauriWorkflowPlatforms: ("macos" | "windows" | "linux" | "ios" | "android")[];
12
+ tauriWorkflowRef: string | undefined;
13
+ yes: boolean;
14
+ };
15
+ } | {
16
+ command: "typecheck";
17
+ args: {};
18
+ } | {
19
+ command: "dev";
20
+ args: {
21
+ platform: "ios" | "android" | "web" | "desktop" | undefined;
22
+ };
23
+ } | {
24
+ command: "upgrade";
25
+ args: {
26
+ command: "ensure-template-remote";
27
+ args: {
28
+ remote: string;
29
+ noFetch: boolean;
30
+ };
31
+ } | {
32
+ command: "check-skill-current";
33
+ args: {
34
+ target: string;
35
+ apply: boolean;
36
+ };
37
+ } | {
38
+ command: "fast-forward-template-files";
39
+ args: {
40
+ target: string;
41
+ base: string | undefined;
42
+ apply: boolean;
43
+ };
44
+ };
45
+ } | {
46
+ command: "db";
47
+ args: {
48
+ command: "ensure";
49
+ args: {};
50
+ } | {
51
+ command: "generate";
52
+ args: {
53
+ args: string[];
54
+ };
55
+ } | {
56
+ command: "migrate";
57
+ args: {
58
+ args: string[];
59
+ };
60
+ } | {
61
+ command: "studio";
62
+ args: {
63
+ args: string[];
64
+ };
65
+ } | {
66
+ command: "migrate-held";
67
+ args: {
68
+ confirm: "run-held-migrations" | undefined;
69
+ };
70
+ } | {
71
+ command: "migrate-local";
72
+ args: {};
73
+ } | {
74
+ command: "migrations-check";
75
+ args: {};
76
+ } | {
77
+ command: "reset";
78
+ args: {};
79
+ } | {
80
+ command: "seed";
81
+ args: {};
82
+ } | {
83
+ command: "setup";
84
+ args: {};
85
+ };
86
+ }>>;
87
+ } & import("cmd-ts/dist/cjs/helpdoc").Named & Partial<import("cmd-ts/dist/cjs/helpdoc").Descriptive & import("cmd-ts/dist/cjs/helpdoc").Versioned> & import("cmd-ts/dist/cjs/helpdoc").PrintHelp & Partial<import("cmd-ts/dist/cjs/helpdoc").Versioned> & import("cmd-ts/dist/cjs/argparser").Register & import("cmd-ts/dist/cjs/runner").Handling<{
88
+ command: "deploy";
89
+ args: {
90
+ runMigrations: boolean;
91
+ skipMigrations: boolean;
92
+ skipPostDeployChecks: boolean;
93
+ tauriLocalPlatforms: ("macos" | "windows" | "linux" | "ios" | "android")[];
94
+ tauriWorkflowName: string | undefined;
95
+ tauriWorkflowPlatforms: ("macos" | "windows" | "linux" | "ios" | "android")[];
96
+ tauriWorkflowRef: string | undefined;
97
+ yes: boolean;
98
+ };
99
+ } | {
100
+ command: "typecheck";
101
+ args: {};
102
+ } | {
103
+ command: "dev";
104
+ args: {
105
+ platform: "ios" | "android" | "web" | "desktop" | undefined;
106
+ };
107
+ } | {
108
+ command: "upgrade";
109
+ args: {
110
+ command: "ensure-template-remote";
111
+ args: {
112
+ remote: string;
113
+ noFetch: boolean;
114
+ };
115
+ } | {
116
+ command: "check-skill-current";
117
+ args: {
118
+ target: string;
119
+ apply: boolean;
120
+ };
121
+ } | {
122
+ command: "fast-forward-template-files";
123
+ args: {
124
+ target: string;
125
+ base: string | undefined;
126
+ apply: boolean;
127
+ };
128
+ };
129
+ } | {
130
+ command: "db";
131
+ args: {
132
+ command: "ensure";
133
+ args: {};
134
+ } | {
135
+ command: "generate";
136
+ args: {
137
+ args: string[];
138
+ };
139
+ } | {
140
+ command: "migrate";
141
+ args: {
142
+ args: string[];
143
+ };
144
+ } | {
145
+ command: "studio";
146
+ args: {
147
+ args: string[];
148
+ };
149
+ } | {
150
+ command: "migrate-held";
151
+ args: {
152
+ confirm: "run-held-migrations" | undefined;
153
+ };
154
+ } | {
155
+ command: "migrate-local";
156
+ args: {};
157
+ } | {
158
+ command: "migrations-check";
159
+ args: {};
160
+ } | {
161
+ command: "reset";
162
+ args: {};
163
+ } | {
164
+ command: "seed";
165
+ args: {};
166
+ } | {
167
+ command: "setup";
168
+ args: {};
169
+ };
170
+ }, {
171
+ command: "deploy";
172
+ value: Promise<void>;
173
+ } | {
174
+ command: "typecheck";
175
+ value: Promise<void>;
176
+ } | {
177
+ command: "dev";
178
+ value: Promise<void>;
179
+ } | {
180
+ command: "upgrade";
181
+ value: {
182
+ command: "ensure-template-remote";
183
+ value: void;
184
+ } | {
185
+ command: "check-skill-current";
186
+ value: void;
187
+ } | {
188
+ command: "fast-forward-template-files";
189
+ value: void;
190
+ };
191
+ } | {
192
+ command: "db";
193
+ value: {
194
+ command: "ensure";
195
+ value: Promise<void>;
196
+ } | {
197
+ command: "generate";
198
+ value: Promise<void>;
199
+ } | {
200
+ command: "migrate";
201
+ value: Promise<void>;
202
+ } | {
203
+ command: "studio";
204
+ value: Promise<void>;
205
+ } | {
206
+ command: "migrate-held";
207
+ value: Promise<void>;
208
+ } | {
209
+ command: "migrate-local";
210
+ value: Promise<void>;
211
+ } | {
212
+ command: "migrations-check";
213
+ value: Promise<void>;
214
+ } | {
215
+ command: "reset";
216
+ value: Promise<void>;
217
+ } | {
218
+ command: "seed";
219
+ value: Promise<void>;
220
+ } | {
221
+ command: "setup";
222
+ value: Promise<void>;
223
+ };
224
+ }> & {
225
+ run(context: import("cmd-ts/dist/cjs/argparser").ParseContext): Promise<import("cmd-ts/dist/cjs/argparser").ParsingResult<{
226
+ command: "deploy";
227
+ value: Promise<void>;
228
+ } | {
229
+ command: "typecheck";
230
+ value: Promise<void>;
231
+ } | {
232
+ command: "dev";
233
+ value: Promise<void>;
234
+ } | {
235
+ command: "upgrade";
236
+ value: {
237
+ command: "ensure-template-remote";
238
+ value: void;
239
+ } | {
240
+ command: "check-skill-current";
241
+ value: void;
242
+ } | {
243
+ command: "fast-forward-template-files";
244
+ value: void;
245
+ };
246
+ } | {
247
+ command: "db";
248
+ value: {
249
+ command: "ensure";
250
+ value: Promise<void>;
251
+ } | {
252
+ command: "generate";
253
+ value: Promise<void>;
254
+ } | {
255
+ command: "migrate";
256
+ value: Promise<void>;
257
+ } | {
258
+ command: "studio";
259
+ value: Promise<void>;
260
+ } | {
261
+ command: "migrate-held";
262
+ value: Promise<void>;
263
+ } | {
264
+ command: "migrate-local";
265
+ value: Promise<void>;
266
+ } | {
267
+ command: "migrations-check";
268
+ value: Promise<void>;
269
+ } | {
270
+ command: "reset";
271
+ value: Promise<void>;
272
+ } | {
273
+ command: "seed";
274
+ value: Promise<void>;
275
+ } | {
276
+ command: "setup";
277
+ value: Promise<void>;
278
+ };
279
+ }>>;
280
+ };
281
+ //#endregion
282
+ export { coreframeCli };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{runDrizzleKitCommand as e}from"./db/drizzle-kit.js";import{seedLocalDb as t}from"./db/seed.js";import{migrateLocalDb as n}from"./db/migrate.js";import{resetLocalDb as r}from"./db/reset.js";import{setupLocalDb as i}from"./db/setup.js";import{ensureLocalDb as a}from"./db/ensure.js";import{migrateHeldMigrations as o}from"./db/migrate-held.js";import{tauriReleasePlatforms as s}from"./deploy/config.js";import{deployProject as c}from"./deploy.js";import{checkDeployMigrations as l}from"./deploy/check-migrations.js";import{runDevServer as u}from"./dev.js";import{typecheckProject as d}from"./typecheck.js";import{checkUpgradeSkillCurrent as f}from"./upgrade/check-skill-current.js";import{ensureTemplateRemote as p}from"./upgrade/ensure-template-remote.js";import{fastForwardTemplateFiles as m}from"./upgrade/fast-forward-template-files.js";import{array as h,binary as g,command as _,flag as v,multioption as y,oneOf as b,option as x,optional as S,positional as C,rest as w,run as T,string as E,subcommands as D}from"cmd-ts";const O=D({name:`coreframe`,cmds:{deploy:_({name:`deploy`,args:{runMigrations:v({long:`run-migrations`,description:`Run pending deploy-safe database migrations before deploying.`,defaultValue:()=>!1}),skipMigrations:v({long:`skip-migrations`,description:`Deploy without running pending deploy-safe database migrations.`,defaultValue:()=>!1}),skipPostDeployChecks:v({long:`skip-post-deploy-checks`,description:`Skip post-deploy checks after Cloudflare deployment.`,defaultValue:()=>!1}),tauriLocalPlatforms:y({long:`tauri-local-platform`,type:h(b(s)),description:`Build one Tauri release platform locally. Repeat for multiple platforms.`,defaultValue:()=>[]}),tauriWorkflowName:x({long:`tauri-workflow-name`,type:S(E),description:`GitHub Actions workflow file or name for remote Tauri release builds.`}),tauriWorkflowPlatforms:y({long:`tauri-workflow-platform`,type:h(b(s)),description:`Build one Tauri release platform with GitHub Actions. Repeat for multiple platforms.`,defaultValue:()=>[]}),tauriWorkflowRef:x({long:`tauri-workflow-ref`,type:S(E),description:`Git ref used when dispatching remote Tauri release builds.`}),yes:v({long:`yes`,short:`y`,description:`Approve noninteractive deploy prompts.`,defaultValue:()=>!1})},handler:({runMigrations:e,skipMigrations:t,skipPostDeployChecks:n,tauriLocalPlatforms:r,tauriWorkflowName:i,tauriWorkflowPlatforms:a,tauriWorkflowRef:o,yes:s})=>c([...e?[`--run-migrations`]:[],...t?[`--skip-migrations`]:[],...n?[`--skip-post-deploy-checks`]:[],...r.flatMap(e=>[`--tauri-local-platform`,e]),...i?[`--tauri-workflow-name`,i]:[],...a.flatMap(e=>[`--tauri-workflow-platform`,e]),...o?[`--tauri-workflow-ref`,o]:[],...s?[`--yes`]:[]])}),typecheck:_({name:`typecheck`,args:{},handler:d}),dev:_({name:`dev`,args:{platform:C({type:S(b([`web`,`desktop`,`ios`,`android`])),displayName:`platform`,description:`Development target platform. Defaults to web.`})},handler:({platform:e})=>u({platform:e??`web`})}),upgrade:D({name:`upgrade`,cmds:{"ensure-template-remote":_({name:`ensure-template-remote`,args:{remote:x({long:`remote`,type:E,defaultValue:()=>`template`,description:`Remote name to ensure.`}),noFetch:v({long:`no-fetch`,description:`Do not fetch the remote after ensuring it exists.`,defaultValue:()=>!1})},handler:({noFetch:e,remote:t})=>p({fetch:!e,remote:t})}),"check-skill-current":_({name:`check-skill-current`,args:{target:x({long:`target`,type:E,description:`Target Coreframe template git ref.`}),apply:v({long:`apply`,description:`Replace the local upgrade skill with the target version when it differs.`,defaultValue:()=>!1})},handler:({apply:e,target:t})=>{process.exitCode=f({apply:e,target:t})}}),"fast-forward-template-files":_({name:`fast-forward-template-files`,args:{target:x({long:`target`,type:E,description:`Target Coreframe template git ref.`}),base:x({long:`base`,type:S(E),description:`Previous Coreframe template git ref. Defaults to package.json engines.coreframe.`}),apply:v({long:`apply`,description:`Write clean fast-forwards to the worktree.`,defaultValue:()=>!1})},handler:({apply:e,base:t,target:n})=>m({apply:e,base:t,target:n})})}}),db:D({name:`db`,cmds:{ensure:_({name:`ensure`,args:{},handler:a}),generate:_({name:`generate`,args:{args:w({displayName:`drizzle-kit args`,description:`Additional arguments passed to drizzle-kit generate.`})},handler:({args:t})=>e(`generate`,t)}),migrate:_({name:`migrate`,args:{args:w({displayName:`drizzle-kit args`,description:`Additional arguments passed to drizzle-kit migrate.`})},handler:({args:t})=>e(`migrate`,t)}),"migrate-held":_({name:`migrate-held`,args:{confirm:x({long:`confirm`,type:S(b([`run-held-migrations`])),description:`Required confirmation value for held migrations.`})},handler:({confirm:e})=>o({argv:e?[`--confirm=${e}`]:[]})}),"migrate-local":_({name:`migrate-local`,args:{},handler:n}),"migrations-check":_({name:`migrations-check`,args:{},handler:l}),reset:_({name:`reset`,args:{},handler:r}),seed:_({name:`seed`,args:{},handler:()=>t()}),setup:_({name:`setup`,args:{},handler:()=>i()}),studio:_({name:`studio`,args:{args:w({displayName:`drizzle-kit args`,description:`Additional arguments passed to drizzle-kit studio.`})},handler:({args:t})=>e(`studio`,t)})}})}});import.meta.main&&T(g(O),process.argv);export{O as coreframeCli};
@@ -0,0 +1,30 @@
1
+ import { SQL } from "drizzle-orm";
2
+
3
+ //#region db/client.d.ts
4
+ type CoreframeDatabaseDialect = "postgres" | "sqlite";
5
+ type CoreframeQueryResult<Row extends Record<string, unknown> = Record<string, unknown>> = {
6
+ rowCount?: number | null;
7
+ rows: Row[];
8
+ };
9
+ type CoreframeDatabaseClient = {
10
+ dialect: CoreframeDatabaseDialect;
11
+ execute<Row extends Record<string, unknown> = Record<string, unknown>>(statement: string): Promise<CoreframeQueryResult<Row>>;
12
+ transaction?<Result>(run: (database: CoreframeDatabaseClient) => Promise<Result>): Promise<Result>;
13
+ close?(): Promise<void>;
14
+ };
15
+ type CoreframeDrizzleExecutor = {
16
+ execute<Result = unknown>(query: SQL): Result | Promise<Result>;
17
+ };
18
+ type CreateDrizzleDatabaseClientOptions = {
19
+ close?(): Promise<void>;
20
+ database: CoreframeDrizzleExecutor;
21
+ dialect: CoreframeDatabaseDialect;
22
+ };
23
+ declare function createDrizzleDatabaseClient({
24
+ close,
25
+ database,
26
+ dialect
27
+ }: CreateDrizzleDatabaseClientOptions): CoreframeDatabaseClient;
28
+ declare function normalizeQueryResult<Row extends Record<string, unknown> = Record<string, unknown>>(result: unknown): CoreframeQueryResult<Row>;
29
+ //#endregion
30
+ export { CoreframeDatabaseClient, CoreframeDatabaseDialect, CoreframeDrizzleExecutor, CoreframeQueryResult, CreateDrizzleDatabaseClientOptions, createDrizzleDatabaseClient, normalizeQueryResult };
@@ -0,0 +1 @@
1
+ import{sql as e}from"drizzle-orm";function t({close:t,database:r,dialect:i}){return{dialect:i,async execute(t){return n(await r.execute(e.raw(t)))},close:t}}function n(e){return Array.isArray(e)?{rows:e}:r(e)?{rowCount:typeof e.rowCount==`number`?e.rowCount:null,rows:e.rows}:{rows:[]}}function r(e){return typeof e==`object`&&!!e&&`rows`in e&&Array.isArray(e.rows)}export{t as createDrizzleDatabaseClient,n as normalizeQueryResult};
@@ -0,0 +1,24 @@
1
+ //#region db/config.d.ts
2
+ declare const LOCAL_DB_DIR: string;
3
+ declare const MIGRATIONS_FOLDER = "drizzle";
4
+ declare const HELD_MIGRATIONS_FOLDER = "drizzle-hold";
5
+ declare const NOW: Date;
6
+ declare const SEED_IDS: {
7
+ readonly adminUser: "00000000-0000-4000-8000-000000000001";
8
+ readonly viewerUser: "00000000-0000-4000-8000-000000000002";
9
+ readonly inactiveUser: "00000000-0000-4000-8000-000000000003";
10
+ readonly unicodeUser: "00000000-0000-4000-8000-000000000004";
11
+ readonly longNameUser: "00000000-0000-4000-8000-000000000005";
12
+ };
13
+ declare const SEED_EMAILS: {
14
+ readonly adminUser: "admin@example.com";
15
+ readonly viewerUser: "viewer@example.com";
16
+ readonly inactiveUser: "inactive@example.com";
17
+ readonly unicodeUser: "unicode@example.com";
18
+ readonly longNameUser: "long-name@example.com";
19
+ };
20
+ type SeedProfile = "small" | "large" | "demo" | "test";
21
+ declare function getSeedProfile(argv?: string[]): SeedProfile;
22
+ declare function generatedUserId(index: number): string;
23
+ //#endregion
24
+ export { HELD_MIGRATIONS_FOLDER, LOCAL_DB_DIR, MIGRATIONS_FOLDER, NOW, SEED_EMAILS, SEED_IDS, SeedProfile, generatedUserId, getSeedProfile };
@@ -0,0 +1 @@
1
+ const e=process.env.PGLITE_DATA_DIR??`.pglite`,t=`drizzle`,n=`drizzle-hold`,r=new Date(`2026-01-15T12:00:00.000Z`),i={adminUser:`00000000-0000-4000-8000-000000000001`,viewerUser:`00000000-0000-4000-8000-000000000002`,inactiveUser:`00000000-0000-4000-8000-000000000003`,unicodeUser:`00000000-0000-4000-8000-000000000004`,longNameUser:`00000000-0000-4000-8000-000000000005`},a={adminUser:`admin@example.com`,viewerUser:`viewer@example.com`,inactiveUser:`inactive@example.com`,unicodeUser:`unicode@example.com`,longNameUser:`long-name@example.com`};function o(e=process.argv.slice(2)){if(e.includes(`--large`))return`large`;let t=e.find(e=>e.startsWith(`--profile=`))?.slice(10)??`small`;if(t===`small`||t===`large`||t===`demo`||t===`test`)return t;throw Error(`Unknown seed profile "${t}". Expected small, large, demo, or test.`)}function s(e){return`00000000-0000-4000-8000-${String(1e3+e).padStart(12,`0`)}`}export{n as HELD_MIGRATIONS_FOLDER,e as LOCAL_DB_DIR,t as MIGRATIONS_FOLDER,r as NOW,a as SEED_EMAILS,i as SEED_IDS,s as generatedUserId,o as getSeedProfile};
@@ -0,0 +1,9 @@
1
+ //#region db/drizzle-kit.d.ts
2
+ type Spawn = (file: string, args: readonly string[], options: {
3
+ env: NodeJS.ProcessEnv;
4
+ stdio: "inherit";
5
+ }) => PromiseLike<unknown>;
6
+ type DrizzleKitCommand = "generate" | "migrate" | "studio";
7
+ declare function runDrizzleKitCommand(command: DrizzleKitCommand, args?: string[], spawn?: Spawn): Promise<void>;
8
+ //#endregion
9
+ export { runDrizzleKitCommand };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import e from"node:path";import t from"node:process";import n from"nano-spawn";const r=e.resolve(`node_modules`,`.bin`),i=t.platform===`win32`?`drizzle-kit.cmd`:`drizzle-kit`,a=t.env.PATH??t.env.Path??``;async function o(o,s=[],c=n){await c(i,[o,...s],{env:{...t.env,PATH:r+e.delimiter+a},stdio:`inherit`})}import.meta.main&&o(t.argv[2],t.argv.slice(3)).catch(e=>{console.error(e),t.exit(1)});export{o as runDrizzleKitCommand};
@@ -0,0 +1,20 @@
1
+ import { importProjectModule } from "../project.js";
2
+ import { planLocalSchemaPush } from "./schema-push.js";
3
+ import { seedLocalDb } from "./seed.js";
4
+ import { setupLocalDb } from "./setup.js";
5
+
6
+ //#region db/ensure.d.ts
7
+ type EnsureLocalDbOptions = {
8
+ importProjectModule?: typeof importProjectModule;
9
+ planLocalSchemaPush?: typeof planLocalSchemaPush;
10
+ seedLocalDb?: typeof seedLocalDb;
11
+ setupLocalDb?: typeof setupLocalDb;
12
+ };
13
+ declare function ensureLocalDb({
14
+ importProjectModule: importModule,
15
+ planLocalSchemaPush: planSchemaPush,
16
+ seedLocalDb: seed,
17
+ setupLocalDb: setup
18
+ }?: EnsureLocalDbOptions): Promise<void>;
19
+ //#endregion
20
+ export { ensureLocalDb };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{importProjectModule as e}from"../project.js";import{LOCAL_DB_DIR as t,SEED_EMAILS as n,SEED_IDS as r}from"./config.js";import{inferSchemaDialect as i,planLocalSchemaPush as a}from"./schema-push.js";import{seedLocalDb as o}from"./seed.js";import{setupLocalDb as s}from"./setup.js";import{eq as c}from"drizzle-orm";async function l(t=e){let[{createLocalDb:n},r]=await Promise.all([t(`src/db/local.ts`),t(`src/db/schema.ts`)]);return{createLocalDb:n,schema:r}}async function u({createLocalDb:e,schema:{users:i}}){let a=e(t);try{let[e]=await a.select({id:i.id}).from(i).where(c(i.email,n.adminUser)).limit(1);return e?.id===r.adminUser}catch{return!1}finally{await a.$client.close()}}async function d({createLocalDb:e,schema:n},r=a){let o=i(n),s=e(t);try{let e=await r(o,n,s);return e.sqlStatements.length===0?(console.log(`Local database schema already in sync`),{appliedStatements:0,plan:e}):(console.log(`Local database schema drift detected; applying ${e.sqlStatements.length} statement(s)`),await e.apply(),console.log(`Local database schema synced`),{appliedStatements:e.sqlStatements.length,plan:e})}finally{await s.$client.close()}}async function f({importProjectModule:t=e,planLocalSchemaPush:n=a,seedLocalDb:r=o,setupLocalDb:i=s}={}){let c=await l(t);await u(c)?console.log(`Local database already set up`):(console.log(`Local database is missing or unseeded; running setup`),await i(),c=await l(t)),(await d(c,n)).appliedStatements>0&&!await u(c)&&(console.log(`Local database seed data missing after schema sync; running seed`),await r())}import.meta.main&&f().catch(e=>{console.error(e),process.exit(1)});export{f as ensureLocalDb};
@@ -0,0 +1,18 @@
1
+ import { CoreframeDatabaseClient, CoreframeDatabaseDialect } from "./client.js";
2
+
3
+ //#region db/migrate-held.d.ts
4
+ type MigrateHeldOptions = {
5
+ argv?: string[];
6
+ database?: CoreframeDatabaseClient;
7
+ env?: NodeJS.ProcessEnv;
8
+ };
9
+ declare function migrateHeldMigrations({
10
+ argv,
11
+ database,
12
+ env
13
+ }?: MigrateHeldOptions): Promise<void>;
14
+ declare function validateHeldMigrationHeader(sql: string, relativePath: string): void;
15
+ declare function heldMigrationSelectQuery(dialect: CoreframeDatabaseDialect, id: string): string;
16
+ declare function heldMigrationInsertQuery(dialect: CoreframeDatabaseDialect, id: string, sha256: string): string;
17
+ //#endregion
18
+ export { heldMigrationInsertQuery, heldMigrationSelectQuery, migrateHeldMigrations, validateHeldMigrationHeader };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import{collectHeldMigrationFiles as e,formatMigrationList as t}from"../deploy/migrations.js";import{createProjectDeployDatabase as n}from"./project.js";import{readFile as r}from"node:fs/promises";import{createHash as i}from"node:crypto";const a=`--confirm=run-held-migrations`,o=[`-- Held migration:`,`-- Safe after:`,`-- Verification:`];async function s({argv:i=process.argv.slice(2),database:o,env:s=process.env}={}){let d=await e();if(console.log(`Held migration files:`),console.log(t(d)),!i.includes(a))throw Error(`Held migrations require explicit confirmation. Re-run with ${a}.`);if(d.length===0){console.log(`No held migrations to run.`);return}let m=o??await n({env:s});try{await u(m);for(let e of d){let t=await r(e.path,`utf8`);c(t,e.relativePath);let n=l(t),i=await m.execute(f(m.dialect,e.id));if(i.rows.length===1){if(i.rows[0]?.sha256!==n)throw Error(`Held migration ${e.relativePath} changed after it was applied.`);console.log(`Skipping already-applied held migration ${e.relativePath}`);continue}console.log(`Applying held migration ${e.relativePath}`),await h(m,async r=>{await r.execute(t),await r.execute(p(m.dialect,e.id,n))})}}finally{o||await m.close?.()}}function c(e,t){let n=o.filter(t=>!e.includes(t));if(n.length>0)throw Error(`${t} is missing held migration header fields: ${n.join(`, `)}`)}function l(e){return i(`sha256`).update(e).digest(`hex`)}async function u(e){for(let t of d(e.dialect))await e.execute(t)}function d(e){switch(e){case`postgres`:return[`create schema if not exists drizzle`,`create table if not exists drizzle.__held_migrations (
3
+ id text primary key,
4
+ sha256 text not null,
5
+ applied_at timestamptz not null default now()
6
+ )`];case`sqlite`:return[`create table if not exists __held_migrations (
7
+ id text primary key,
8
+ sha256 text not null,
9
+ applied_at text not null default current_timestamp
10
+ )`]}}function f(e,t){return`select sha256 from ${m(e)} where id = ${g(t)}`}function p(e,t,n){return`insert into ${m(e)} (id, sha256) values (${g(t)}, ${g(n)})`}function m(e){switch(e){case`postgres`:return`drizzle.__held_migrations`;case`sqlite`:return`__held_migrations`}}async function h(e,t){if(e.transaction){await e.transaction(t);return}await e.execute(`begin`);try{await t(e),await e.execute(`commit`)}catch(t){throw await e.execute(`rollback`),t}}function g(e){return`'${e.replaceAll(`'`,`''`)}'`}import.meta.main&&s().catch(e=>{console.error(e),process.exit(1)});export{p as heldMigrationInsertQuery,f as heldMigrationSelectQuery,s as migrateHeldMigrations,c as validateHeldMigrationHeader};
@@ -0,0 +1,4 @@
1
+ //#region db/migrate.d.ts
2
+ declare function migrateLocalDb(): Promise<void>;
3
+ //#endregion
4
+ export { migrateLocalDb };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{importProjectModule as e}from"../project.js";import{LOCAL_DB_DIR as t,MIGRATIONS_FOLDER as n}from"./config.js";import{resolve as r}from"node:path";import{access as i}from"node:fs/promises";import{migrate as a}from"drizzle-orm/pglite/migrator";const o=r(n);async function s(){await i(o);let{createLocalDb:n}=await e(`src/db/local.ts`),r=n(t);try{await a(r,{migrationsFolder:o}),console.log(`Applied local migrations from ${o}`)}finally{await r.$client.close()}}import.meta.main&&s().catch(e=>{console.error(e),process.exit(1)});export{s as migrateLocalDb};
@@ -0,0 +1,12 @@
1
+ import { CoreframeDatabaseClient } from "./client.js";
2
+
3
+ //#region db/project.d.ts
4
+ type CreateProjectDatabaseOptions = {
5
+ env?: NodeJS.ProcessEnv;
6
+ };
7
+ type ProjectDatabaseModule = {
8
+ createDeployDatabase?(options?: CreateProjectDatabaseOptions): CoreframeDatabaseClient | Promise<CoreframeDatabaseClient>;
9
+ };
10
+ declare function createProjectDeployDatabase(options?: CreateProjectDatabaseOptions): Promise<CoreframeDatabaseClient>;
11
+ //#endregion
12
+ export { CreateProjectDatabaseOptions, ProjectDatabaseModule, createProjectDeployDatabase };
@@ -0,0 +1 @@
1
+ import{importProjectModule as e}from"../project.js";const t=`scripts/coreframe.db.ts`;async function n(n={}){let r=await e(t);if(!r.createDeployDatabase)throw Error(`${t} must export createDeployDatabase() for Coreframe database workflows.`);return r.createDeployDatabase(n)}export{n as createProjectDeployDatabase};
@@ -0,0 +1,4 @@
1
+ //#region db/reset.d.ts
2
+ declare function resetLocalDb(): Promise<void>;
3
+ //#endregion
4
+ export { resetLocalDb };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{LOCAL_DB_DIR as e}from"./config.js";import{relative as t,resolve as n}from"node:path";import{rm as r}from"node:fs/promises";async function i(){let i=n(e),a=t(process.cwd(),i);if(a===``||a.startsWith(`..`))throw Error(`Refusing to reset local database outside the project: ${i}`);await r(i,{force:!0,recursive:!0}),console.log(`Removed local PGlite data at ${i}`)}import.meta.main&&i().catch(e=>{console.error(e),process.exit(1)});export{i as resetLocalDb};
@@ -0,0 +1,22 @@
1
+ import { CoreframeDatabaseDialect } from "./client.js";
2
+
3
+ //#region db/schema-push.d.ts
4
+ type SchemaPushPlan = {
5
+ sqlStatements: string[];
6
+ hints: Array<{
7
+ hint: string;
8
+ statement?: string;
9
+ }>;
10
+ apply(): Promise<void>;
11
+ };
12
+ type SchemaPushModule = {
13
+ pushSchema(schema: Record<string, unknown>, db: never, migrationsConfig?: {
14
+ table?: string;
15
+ schema?: string;
16
+ }): Promise<SchemaPushPlan>;
17
+ };
18
+ type ImportSchemaPushModule = (dialect: CoreframeDatabaseDialect) => Promise<SchemaPushModule>;
19
+ declare function planLocalSchemaPush(dialect: CoreframeDatabaseDialect, schema: Record<string, unknown>, db: unknown, importModule?: ImportSchemaPushModule): Promise<SchemaPushPlan>;
20
+ declare function inferSchemaDialect(schema: Record<string, unknown>): CoreframeDatabaseDialect;
21
+ //#endregion
22
+ export { SchemaPushModule, SchemaPushPlan, inferSchemaDialect, planLocalSchemaPush };
@@ -0,0 +1 @@
1
+ import{createRequire as e}from"node:module";import{resolve as t}from"node:path";import{is as n}from"drizzle-orm";import{pathToFileURL as r}from"node:url";import{PgMaterializedView as i,PgTable as a,PgView as o}from"drizzle-orm/pg-core";import{SQLiteTable as s,SQLiteView as c}from"drizzle-orm/sqlite-core";const l={postgres:`drizzle-kit/payload/postgres`,sqlite:`drizzle-kit/payload/sqlite`};async function u(e,t,n,r=f){let{pushSchema:i}=await r(e);return i(t,n)}function d(e){let t=!1,n=!1;for(let r of Object.values(e))t||=p(r),n||=m(r);if(t&&n)throw Error(`Local database schema mixes PostgreSQL and SQLite entities.`);if(t)return`postgres`;if(n)return`sqlite`;throw Error(`Could not infer local database dialect from src/db/schema.ts.`)}async function f(n){return await import(r(e(t(`package.json`)).resolve(l[n])).href)}function p(e){return n(e,a)||n(e,o)||n(e,i)}function m(e){return n(e,s)||n(e,c)}export{d as inferSchemaDialect,u as planLocalSchemaPush};
@@ -0,0 +1,6 @@
1
+ import { SeedProfile } from "./config.js";
2
+
3
+ //#region db/seed.d.ts
4
+ declare function seedLocalDb(profile?: SeedProfile): Promise<void>;
5
+ //#endregion
6
+ export { seedLocalDb };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{importProjectModule as e}from"../project.js";import{LOCAL_DB_DIR as t,getSeedProfile as n}from"./config.js";async function r(r=n()){let[{createLocalDb:i},{seedUsers:a}]=await Promise.all([e(`src/db/local.ts`),e(`scripts/db/seeds/users.ts`)]),o=i(t);try{await a(o,r),console.log(`Seeded local database with "${r}" profile`)}finally{await o.$client.close()}}import.meta.main&&r().catch(e=>{console.error(e),process.exit(1)});export{r as seedLocalDb};
@@ -0,0 +1,6 @@
1
+ import { SeedProfile } from "./config.js";
2
+
3
+ //#region db/setup.d.ts
4
+ declare function setupLocalDb(profile?: SeedProfile): Promise<void>;
5
+ //#endregion
6
+ export { setupLocalDb };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{seedLocalDb as e}from"./seed.js";import{migrateLocalDb as t}from"./migrate.js";import{resetLocalDb as n}from"./reset.js";async function r(r=`small`){await n(),await t(),await e(r)}import.meta.main&&r().catch(e=>{console.error(e),process.exit(1)});export{r as setupLocalDb};
@@ -0,0 +1,4 @@
1
+ //#region deploy/check-migrations.d.ts
2
+ declare function checkDeployMigrations(): Promise<void>;
3
+ //#endregion
4
+ export { checkDeployMigrations };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{collectDeployMigrationFiles as e,formatMigrationList as t,formatMigrationScanFailures as n,scanDeployMigrationFiles as r}from"./migrations.js";async function i(){let i=await e();console.log(`Deploy migration files:`),console.log(t(i));let a=await r(i);if(a.length>0)throw Error(`Deploy migrations contain unsafe SQL. Move unsafe statements to held migrations.\n${n(a)}`);console.log(`Deploy migrations passed safety checks.`)}import.meta.main&&i().catch(e=>{console.error(e),process.exit(1)});export{i as checkDeployMigrations};
@@ -0,0 +1,6 @@
1
+ import { DeployContext, ResolvedDeployConfig } from "./config.js";
2
+
3
+ //#region deploy/checks.d.ts
4
+ declare function runPostDeployChecks(config: ResolvedDeployConfig, context: DeployContext): Promise<void>;
5
+ //#endregion
6
+ export { runPostDeployChecks };
@@ -0,0 +1 @@
1
+ async function e(e,n){let r=n.productionUrl??e.productionUrl;e.smokeChecks.length>0&&!r&&console.log(`Skipping HTTP smoke checks. Set DEPLOY_URL or productionUrl in deploy config.`);for(let n of e.smokeChecks){if(!r&&!n.url)continue;let e=n.url??new URL(n.path??`/`,r).toString(),i=await fetch(e);if(!t(i.status,n.expectedStatus??`2xx`))throw Error(`${n.name} smoke check failed: ${e} returned ${i.status}.`);console.log(`${n.name} smoke check passed: ${i.status} ${e}`)}await e.afterDeploy?.({...n,productionUrl:r})}function t(e,t){return t===`2xx`?e>=200&&e<300:Array.isArray(t)?t.includes(e):e===t}export{e as runPostDeployChecks};
@@ -0,0 +1,53 @@
1
+ //#region deploy/config.d.ts
2
+ type DeployContext = {
3
+ environment: string;
4
+ productionUrl?: string;
5
+ releaseName: string;
6
+ version: string;
7
+ };
8
+ declare const tauriReleasePlatforms: readonly ["macos", "windows", "linux", "ios", "android"];
9
+ type TauriReleasePlatform = (typeof tauriReleasePlatforms)[number];
10
+ type TauriReleasePlatformConfig = {
11
+ artifactGlobs?: string[];
12
+ buildArgs?: string[];
13
+ targetTriple?: string;
14
+ updatePlatform?: string;
15
+ };
16
+ type TauriReleaseConfig = {
17
+ artifactDownloadDir?: string;
18
+ b2Bucket?: string;
19
+ latestManifestPath?: string;
20
+ localPlatforms?: TauriReleasePlatform[];
21
+ platform?: Partial<Record<TauriReleasePlatform, TauriReleasePlatformConfig>>;
22
+ publicBaseUrl?: string;
23
+ publicPathPrefix?: string;
24
+ releaseNotesPath?: string;
25
+ releasePath?: string;
26
+ releaseUploadDir?: string;
27
+ workflowPlatforms?: TauriReleasePlatform[];
28
+ workflowName?: string;
29
+ workflowPollIntervalMs?: number;
30
+ workflowRef?: string;
31
+ workflowTimeoutMs?: number;
32
+ };
33
+ type DeploySmokeCheck = {
34
+ name: string;
35
+ path?: string;
36
+ url?: string;
37
+ expectedStatus?: "2xx" | number | number[];
38
+ };
39
+ type DeployConfig = {
40
+ afterDeploy?: (context: DeployContext) => Promise<void> | void;
41
+ productionUrl?: string;
42
+ smokeChecks?: DeploySmokeCheck[];
43
+ tauriRelease?: TauriReleaseConfig;
44
+ };
45
+ type ResolvedDeployConfig = {
46
+ afterDeploy?: (context: DeployContext) => Promise<void> | void;
47
+ productionUrl?: string;
48
+ smokeChecks: DeploySmokeCheck[];
49
+ tauriRelease: TauriReleaseConfig;
50
+ };
51
+ declare function resolveDeployConfig(config: DeployConfig, env?: NodeJS.ProcessEnv): ResolvedDeployConfig;
52
+ //#endregion
53
+ export { DeployConfig, DeployContext, DeploySmokeCheck, ResolvedDeployConfig, TauriReleaseConfig, TauriReleasePlatform, TauriReleasePlatformConfig, resolveDeployConfig, tauriReleasePlatforms };
@@ -0,0 +1 @@
1
+ const e=[`macos`,`windows`,`linux`,`ios`,`android`];function t(e,t=process.env){return{afterDeploy:e.afterDeploy,productionUrl:e.productionUrl??t.DEPLOY_URL??t.PRODUCTION_URL,smokeChecks:e.smokeChecks??[{name:`home`,path:`/`}],tauriRelease:e.tauriRelease??{}}}export{t as resolveDeployConfig,e as tauriReleasePlatforms};
@@ -0,0 +1,24 @@
1
+ //#region deploy/migrations.d.ts
2
+ type MigrationFile = {
3
+ id: string;
4
+ path: string;
5
+ relativePath: string;
6
+ };
7
+ type MigrationRisk = {
8
+ code: string;
9
+ line: number;
10
+ match: string;
11
+ reason: string;
12
+ };
13
+ type MigrationScanResult = {
14
+ migration: MigrationFile;
15
+ risks: MigrationRisk[];
16
+ };
17
+ declare function collectDeployMigrationFiles(cwd?: string): Promise<MigrationFile[]>;
18
+ declare function collectHeldMigrationFiles(cwd?: string): Promise<MigrationFile[]>;
19
+ declare function scanDeployMigrationFiles(migrations: MigrationFile[]): Promise<MigrationScanResult[]>;
20
+ declare function scanMigrationSql(sql: string): MigrationRisk[];
21
+ declare function formatMigrationScanFailures(results: MigrationScanResult[]): string;
22
+ declare function formatMigrationList(migrations: MigrationFile[]): string;
23
+ //#endregion
24
+ export { MigrationFile, MigrationRisk, MigrationScanResult, collectDeployMigrationFiles, collectHeldMigrationFiles, formatMigrationList, formatMigrationScanFailures, scanDeployMigrationFiles, scanMigrationSql };
@@ -0,0 +1,4 @@
1
+ import{HELD_MIGRATIONS_FOLDER as e,MIGRATIONS_FOLDER as t}from"../db/config.js";import{basename as n,join as r,relative as i,resolve as a}from"node:path";import{access as o,readFile as s,readdir as c}from"node:fs/promises";const l=[{code:`drop-schema-object`,pattern:/\bdrop\s+(?:table|view|materialized\s+view|index|sequence|type|schema|function|trigger|policy)\b/giu,reason:`drops schema that the currently deployed app may still depend on`},{code:`drop-column`,pattern:/\balter\s+table\b[^;]*?\bdrop\s+column\b/giu,reason:`removes a column that the currently deployed app may still read or write`},{code:`drop-constraint`,pattern:/\balter\s+table\b[^;]*?\bdrop\s+constraint\b/giu,reason:`removes a constraint outside the staged contract phase`},{code:`truncate`,pattern:/\btruncate\b/giu,reason:`removes data outside the deploy-safe migration path`},{code:`rename-table-or-column`,pattern:/\balter\s+table\b[^;]*?\brename(?:\s+column)?\b/giu,reason:`renames schema in a way old and new app versions cannot both address`},{code:`rewrite-column-type`,pattern:/\balter\s+table\b[^;]*?\balter\s+column\b[^;]*?\btype\b/giu,reason:`rewrites a column type outside a staged expand/contract rollout`},{code:`set-not-null`,pattern:/\balter\s+table\b[^;]*?\balter\s+column\b[^;]*?\bset\s+not\s+null\b/giu,reason:`enforces NOT NULL before a separate backfill and verification step`},{code:`set-default`,pattern:/\balter\s+table\b[^;]*?\balter\s+column\b[^;]*?\bset\s+default\b/giu,reason:`changes default write behavior while old app code may still be running`},{code:`broad-update`,pattern:/\bupdate\b/giu,reason:`changes data outside a separately reviewed backfill or held migration`},{code:`broad-delete`,pattern:/\bdelete\s+from\b/giu,reason:`removes data outside a held migration`}];async function u(e=process.cwd()){return _(a(e,t),e)}async function d(t=process.cwd()){return v(a(t,e),t)}async function f(e){return(await Promise.all(e.map(async e=>({migration:e,risks:p(await s(e.path,`utf8`))})))).filter(e=>e.risks.length>0)}function p(e){let t=S(e),n=g(t),r=[];for(let n of l)for(let i of t.matchAll(n.pattern))r.push(T(e,i,n.code,n.reason));for(let i of t.matchAll(/\balter\s+table\s+(?:if\s+exists\s+|only\s+)?(?<table>(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))?)\s+add\s+column\b[^;]*?\bnot\s+null\b/giu))n.has(w(i.groups?.table))||r.push(T(e,i,`add-not-null-column`,`adds a required column to an existing table before a separate compatibility rollout`));for(let i of t.matchAll(/\balter\s+table\s+(?:if\s+exists\s+|only\s+)?(?<table>(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))?)\s+add\s+constraint\b[^;]*?\b(?:foreign\s+key|unique|primary\s+key|check)\b/giu))n.has(w(i.groups?.table))||r.push(T(e,i,`add-constraint`,`enforces a constraint on an existing table before a separate validation step`));for(let i of t.matchAll(/\bcreate\s+unique\s+index(?:\s+concurrently)?(?:\s+if\s+not\s+exists)?\s+(?:"[^"]+"|\w+)\s+on\s+(?<table>(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))?)/giu))n.has(w(i.groups?.table))||r.push(T(e,i,`create-unique-index`,`adds uniqueness to existing data before a separate dedupe and validation step`));return r.sort((e,t)=>e.line-t.line||e.code.localeCompare(t.code))}function m(e){return e.flatMap(e=>e.risks.map(t=>`${e.migration.relativePath}:${t.line} ${t.code}: ${t.reason} (${t.match.trim()})`)).join(`
2
+ `)}function h(e){return e.length===0?`No migration files found.`:e.map(e=>`- ${e.relativePath}`).join(`
3
+ `)}function g(e){let t=new Set;for(let n of e.matchAll(/\bcreate\s+(?:temporary\s+|temp\s+)?table\s+(?:if\s+not\s+exists\s+)?(?<table>(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))?)/giu))t.add(w(n.groups?.table));return t}async function _(e,t){let n=await y(e),i=[];for(let a of n){let n=r(e,a.name);if(a.isDirectory()){let e=r(n,`migration.sql`);await b(e)&&i.push(x(e,t,a.name));continue}a.isFile()&&a.name.endsWith(`.sql`)&&i.push(x(n,t,a.name.replace(/\.sql$/u,``)))}return i.sort((e,t)=>e.id.localeCompare(t.id))}async function v(e,t){return(await y(e)).filter(e=>e.isFile()&&e.name.endsWith(`.sql`)).map(n=>x(r(e,n.name),t,n.name.replace(/\.sql$/u,``))).sort((e,t)=>e.id.localeCompare(t.id))}async function y(e){try{return await c(e,{withFileTypes:!0})}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return[];throw e}}async function b(e){try{return await o(e),!0}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return!1;throw e}}function x(e,t,r=n(e,`.sql`)){return{id:r,path:e,relativePath:i(t,e)}}function S(e){return e.replace(/--[^\n\r]*/gu,C).replace(/\/\*[\s\S]*?\*\//gu,C).replace(/\$([A-Za-z_][A-Za-z0-9_]*)?\$[\s\S]*?\$\1\$/gu,C).replace(/'(?:''|[^'])*'/gu,C)}function C(e){return e.replace(/[^\n\r]/gu,` `)}function w(e){return(e??``).split(`.`).map(e=>e.trim().replace(/^"|"$/gu,``).toLowerCase()).at(-1)??``}function T(e,t,n,r){return{code:n,line:E(e,t.index??0),match:e.slice(t.index??0,(t.index??0)+t[0].length),reason:r}}function E(e,t){let n=1;for(let r=0;r<t;r+=1)e[r]===`
4
+ `&&(n+=1);return n}export{u as collectDeployMigrationFiles,d as collectHeldMigrationFiles,h as formatMigrationList,m as formatMigrationScanFailures,f as scanDeployMigrationFiles,p as scanMigrationSql};
@@ -0,0 +1,9 @@
1
+ import { CoreframeDatabaseClient, CoreframeDatabaseDialect } from "../db/client.js";
2
+ import { MigrationFile } from "./migrations.js";
3
+
4
+ //#region deploy/pending-migrations.d.ts
5
+ declare function filterPendingMigrations(migrations: MigrationFile[], appliedNames: Iterable<string>): MigrationFile[];
6
+ declare function readAppliedMigrationNames(database: CoreframeDatabaseClient): Promise<Set<string>>;
7
+ declare function appliedMigrationsQuery(dialect: CoreframeDatabaseDialect): "select name from drizzle.__drizzle_migrations where name is not null" | "select name from __drizzle_migrations where name is not null";
8
+ //#endregion
9
+ export { appliedMigrationsQuery, filterPendingMigrations, readAppliedMigrationNames };
@@ -0,0 +1 @@
1
+ function e(e,t){let n=new Set(t);return e.filter(e=>!n.has(e.id))}async function t(e){try{let t=await e.execute(n(e.dialect));return new Set(t.rows.flatMap(e=>e.name?[e.name]:[]))}catch(e){if(r(e))return new Set;throw e}}function n(e){switch(e){case`postgres`:return`select name from drizzle.__drizzle_migrations where name is not null`;case`sqlite`:return`select name from __drizzle_migrations where name is not null`}}function r(e){return e instanceof Error&&(`code`in e&&(e.code===`42P01`||e.code===`3F000`)||e.message.includes(`no such table`)||e.message.includes(`no such schema`))}export{n as appliedMigrationsQuery,e as filterPendingMigrations,t as readAppliedMigrationNames};
@@ -0,0 +1,105 @@
1
+ import { TauriReleaseConfig, TauriReleasePlatform } from "./config.js";
2
+
3
+ //#region deploy/tauri-release.d.ts
4
+ type TauriReleaseArgs = {
5
+ tauriLocalPlatforms: TauriReleasePlatform[];
6
+ tauriWorkflowPlatforms: TauriReleasePlatform[];
7
+ tauriWorkflowName?: string;
8
+ tauriWorkflowRef?: string;
9
+ };
10
+ type TauriReleasePlan = {
11
+ artifactDownloadDir: string;
12
+ b2ApplicationKey?: string;
13
+ b2ApplicationKeyId?: string;
14
+ b2Bucket?: string;
15
+ enabled: boolean;
16
+ latestManifestPath: string;
17
+ localPlatforms: TauriReleasePlatform[];
18
+ platform: Record<TauriReleasePlatform, ResolvedTauriReleasePlatformConfig>;
19
+ publicBaseUrl?: string;
20
+ publicPathPrefix: string;
21
+ releaseNotesPath?: string;
22
+ releasePath: string;
23
+ releaseUploadDir: string;
24
+ signingPrivateKey?: string;
25
+ workflowName: string;
26
+ workflowPlatforms: TauriReleasePlatform[];
27
+ workflowPollIntervalMs: number;
28
+ workflowRef?: string;
29
+ workflowTimeoutMs: number;
30
+ };
31
+ type TauriReleaseMetadataSnapshot = {
32
+ files: {
33
+ path: string;
34
+ source: string;
35
+ }[];
36
+ };
37
+ type PreparedTauriRelease = {
38
+ manifestPath: string;
39
+ uploadDir: string;
40
+ };
41
+ type ResolvedTauriReleasePlatformConfig = {
42
+ artifactGlobs: string[];
43
+ buildArgs: string[];
44
+ targetTriple: string;
45
+ updatePlatform: string;
46
+ };
47
+ type TauriUpdateManifest = {
48
+ notes?: string;
49
+ platforms: Record<string, {
50
+ signature: string;
51
+ url: string;
52
+ }>;
53
+ pub_date: string;
54
+ version: string;
55
+ };
56
+ declare function parseTauriReleasePlatformList(value: string | undefined): ("macos" | "windows" | "linux" | "ios" | "android")[];
57
+ declare function parseTauriReleasePlatform(value: string): TauriReleasePlatform;
58
+ declare function resolveTauriReleasePlan({
59
+ args,
60
+ config,
61
+ env
62
+ }: {
63
+ args: TauriReleaseArgs;
64
+ config?: TauriReleaseConfig;
65
+ env: NodeJS.ProcessEnv;
66
+ }): TauriReleasePlan;
67
+ declare function validateTauriReleasePlan(plan: TauriReleasePlan, cwd?: string): Promise<void>;
68
+ declare function writeTauriReleaseMetadata(plan: TauriReleasePlan, version: string, cwd?: string): Promise<TauriReleaseMetadataSnapshot>;
69
+ declare function restoreTauriReleaseMetadata(snapshot: TauriReleaseMetadataSnapshot): Promise<void>;
70
+ declare function prepareTauriRelease({
71
+ env,
72
+ plan,
73
+ releaseName,
74
+ version
75
+ }: {
76
+ env: NodeJS.ProcessEnv;
77
+ plan: TauriReleasePlan;
78
+ releaseName: string;
79
+ version: string;
80
+ }): Promise<PreparedTauriRelease | undefined>;
81
+ declare function uploadTauriReleaseToB2(plan: TauriReleasePlan, release: PreparedTauriRelease): Promise<void>;
82
+ declare function createTauriUpdateManifest({
83
+ artifacts,
84
+ notes,
85
+ pubDate,
86
+ version
87
+ }: {
88
+ artifacts: {
89
+ signature: string;
90
+ updatePlatform: string;
91
+ url: string;
92
+ }[];
93
+ notes?: string;
94
+ pubDate?: Date;
95
+ version: string;
96
+ }): TauriUpdateManifest;
97
+ declare function createTauriReleaseUpload({
98
+ plan,
99
+ version
100
+ }: {
101
+ plan: TauriReleasePlan;
102
+ version: string;
103
+ }): Promise<PreparedTauriRelease>;
104
+ //#endregion
105
+ export { PreparedTauriRelease, TauriReleaseArgs, TauriReleaseMetadataSnapshot, TauriReleasePlan, createTauriReleaseUpload, createTauriUpdateManifest, parseTauriReleasePlatform, parseTauriReleasePlatformList, prepareTauriRelease, resolveTauriReleasePlan, restoreTauriReleaseMetadata, uploadTauriReleaseToB2, validateTauriReleasePlan, writeTauriReleaseMetadata };
@@ -0,0 +1 @@
1
+ import{tauriReleasePlatforms as e}from"./config.js";import{basename as t,dirname as n,join as r,relative as i,resolve as a,sep as o}from"node:path";import s from"nano-spawn";import{access as c,cp as l,mkdir as u,readFile as d,rm as f,stat as p,writeFile as m}from"node:fs/promises";import{randomUUID as h}from"node:crypto";import{constants as g}from"node:fs";import{setTimeout as _}from"node:timers/promises";import{glob as v}from"tinyglobby";const y={android:{artifactGlobs:[`tauri/src-tauri/target/**/*.sig`,`tauri/gen/android/**/*.sig`],buildArgs:[`tauri`,`android`,`build`,`--target`,`aarch64-linux-android`],targetTriple:`aarch64-linux-android`,updatePlatform:`android-aarch64`},ios:{artifactGlobs:[`tauri/src-tauri/target/**/*.sig`,`tauri/gen/apple/**/*.sig`],buildArgs:[`tauri`,`ios`,`build`,`--target`,`aarch64-apple-ios`],targetTriple:`aarch64-apple-ios`,updatePlatform:`ios-aarch64`},linux:{artifactGlobs:[`tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/**/*.sig`,`tauri/src-tauri/target/release/bundle/**/*.sig`,`tauri/src-tauri/target/**/release/bundle/**/*.sig`],buildArgs:[`tauri`,`build`,`--target`,`x86_64-unknown-linux-gnu`,`--`,`--locked`],targetTriple:`x86_64-unknown-linux-gnu`,updatePlatform:`linux-x86_64`},macos:{artifactGlobs:[`tauri/src-tauri/target/aarch64-apple-darwin/release/bundle/**/*.sig`,`tauri/src-tauri/target/release/bundle/**/*.sig`,`tauri/src-tauri/target/**/release/bundle/**/*.sig`],buildArgs:[`tauri`,`build`,`--target`,`aarch64-apple-darwin`,`--`,`--locked`],targetTriple:`aarch64-apple-darwin`,updatePlatform:`darwin-aarch64`},windows:{artifactGlobs:[`tauri/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/**/*.sig`,`tauri/src-tauri/target/release/bundle/**/*.sig`,`tauri/src-tauri/target/**/release/bundle/**/*.sig`],buildArgs:[`tauri`,`build`,`--target`,`x86_64-pc-windows-msvc`,`--`,`--locked`],targetTriple:`x86_64-pc-windows-msvc`,updatePlatform:`windows-x86_64`}};function b(e){return e?e.split(/[,\s]+/u).filter(Boolean).map(e=>x(e)):[]}function x(t){if(Y(t))return t;throw Error(`Invalid Tauri release platform "${t}". Expected one of: ${e.join(`, `)}.`)}function S({args:e,config:t={},env:n}){let r=J([...t.localPlatforms??[],...b(n.TAURI_RELEASE_LOCAL_PLATFORMS),...e.tauriLocalPlatforms]),i=J([...t.workflowPlatforms??[],...b(n.TAURI_RELEASE_WORKFLOW_PLATFORMS),...e.tauriWorkflowPlatforms]),a=r.filter(e=>i.includes(e));if(a.length>0)throw Error(`Build each Tauri release platform either locally or through the workflow, not both: ${a.join(`, `)}.`);return{artifactDownloadDir:n.TAURI_RELEASE_ARTIFACT_DOWNLOAD_DIR??t.artifactDownloadDir??`.coreframe/tauri-artifacts`,b2ApplicationKey:n.B2_APPLICATION_KEY??n.B2_SECRET_ACCESS_KEY,b2ApplicationKeyId:n.B2_APPLICATION_KEY_ID??n.B2_KEY_ID??n.B2_ACCESS_KEY_ID,b2Bucket:t.b2Bucket??n.B2_BUCKET,enabled:r.length>0||i.length>0,latestManifestPath:n.TAURI_RELEASE_LATEST_MANIFEST_PATH??t.latestManifestPath??`latest.json`,localPlatforms:r,platform:I(t.platform,n),publicBaseUrl:t.publicBaseUrl??n.TAURI_RELEASE_PUBLIC_BASE_URL??n.UPDATE_PUBLIC_BASE_URL,publicPathPrefix:n.TAURI_RELEASE_PUBLIC_PATH_PREFIX??t.publicPathPrefix??n.UPDATE_PUBLIC_PATH_PREFIX??``,releaseNotesPath:n.TAURI_RELEASE_NOTES_FILE??t.releaseNotesPath,releasePath:n.TAURI_RELEASE_PATH??t.releasePath??"releases/${version}",releaseUploadDir:n.TAURI_RELEASE_UPLOAD_DIR??t.releaseUploadDir??`release-upload`,signingPrivateKey:n.TAURI_SIGNING_PRIVATE_KEY,workflowName:e.tauriWorkflowName??n.TAURI_RELEASE_WORKFLOW_NAME??t.workflowName??`tauri-release-artifacts.yml`,workflowPlatforms:i,workflowPollIntervalMs:q(n.TAURI_RELEASE_WORKFLOW_POLL_SECONDS,`TAURI_RELEASE_WORKFLOW_POLL_SECONDS`)*1e3||t.workflowPollIntervalMs||15e3,workflowRef:e.tauriWorkflowRef??n.TAURI_RELEASE_WORKFLOW_REF??t.workflowRef,workflowTimeoutMs:q(n.TAURI_RELEASE_WORKFLOW_TIMEOUT_MINUTES,`TAURI_RELEASE_WORKFLOW_TIMEOUT_MINUTES`)*6e4||t.workflowTimeoutMs||36e5}}async function C(e,t=process.cwd()){if(!e.enabled)return;let n=[[`TAURI_RELEASE_PUBLIC_BASE_URL`,e.publicBaseUrl],[`B2_APPLICATION_KEY_ID`,e.b2ApplicationKeyId],[`B2_APPLICATION_KEY`,e.b2ApplicationKey],[`B2_BUCKET`,e.b2Bucket]].flatMap(([e,t])=>t?[]:[e]);if(n.length>0)throw Error(`Missing required Tauri release configuration: ${n.join(`, `)}`);try{new URL(e.publicBaseUrl)}catch{throw Error(`Invalid TAURI_RELEASE_PUBLIC_BASE_URL: ${e.publicBaseUrl}`)}if(!await X(r(t,`tauri/src-tauri/tauri.conf.json`)))throw Error(`Tauri release platforms were requested, but tauri/src-tauri/tauri.conf.json does not exist.`);if(e.localPlatforms.length>0&&!e.signingPrivateKey)throw Error(`TAURI_SIGNING_PRIVATE_KEY is required for local Tauri release builds.`);e.workflowPlatforms.length>0&&await s(`gh`,[`workflow`,`view`,e.workflowName,`--ref`,e.workflowRef??await V()],{stdin:`ignore`}),await s(`b2`,[`version`],{stdin:`ignore`})}async function w(e,t,n=process.cwd()){if(!e.enabled)return{files:[]};let i=r(n,`tauri/src-tauri/tauri.conf.json`),a=r(n,`tauri/src-tauri/Cargo.toml`),o=r(n,`tauri/src-tauri/Cargo.lock`),s=await d(i,`utf8`),c=await d(a,`utf8`),l=R(c,a),u=[{path:i,source:s},{path:a,source:c}],f=[],p=JSON.parse(s);if(p.version=t,p.bundle={...p.bundle??{},createUpdaterArtifacts:!0},f.push({path:i,source:`${JSON.stringify(p,null,2)}\n`}),f.push({path:a,source:z(c,l,t,a)}),await X(o)){let e=await d(o,`utf8`);u.push({path:o,source:e}),f.push({path:o,source:B(e,l,t,o)})}for(let e of f)await m(e.path,e.source);return{files:u}}async function T(e){for(let t of e.files)await m(t.path,t.source)}async function E({env:e,plan:t,releaseName:n,version:r}){if(!t.enabled)return;await H(t.artifactDownloadDir),await H(t.releaseUploadDir);let i=t.workflowPlatforms.length>0?await A({plan:t,releaseName:n,version:r}):void 0;for(let n of t.localPlatforms)await N(n,t,e);if(i){let e=await j(t,i.runNameMarker);await s(`gh`,[`run`,`download`,String(e),`--dir`,t.artifactDownloadDir],{stdio:`inherit`})}return k({plan:t,version:r})}async function D(e,t){await s(`b2`,[`account`,`authorize`,e.b2ApplicationKeyId,e.b2ApplicationKey],{stdin:`ignore`});let n=(await v(`**/*`,{cwd:t.uploadDir,onlyFiles:!0})).sort((e,n)=>e===t.manifestPath?1:n===t.manifestPath?-1:e.localeCompare(n));for(let i of n)await s(`b2`,[`file`,`upload`,e.b2Bucket,r(t.uploadDir,i),i],{stdin:`ignore`}),console.log(`Uploaded ${i}`)}function O({artifacts:e,notes:t,pubDate:n=new Date,version:r}){return{...t?{notes:t}:{},platforms:Object.fromEntries(e.map(e=>[e.updatePlatform,{signature:e.signature,url:e.url}])),pub_date:n.toISOString(),version:r}}async function k({plan:e,version:i}){let a=K(e.publicPathPrefix,W(e.releasePath,i)),o=K(e.publicPathPrefix,e.latestManifestPath),s=[...e.localPlatforms,...e.workflowPlatforms],c=[];for(let o of s){let s=e.platform[o],f=e.localPlatforms.includes(o)?`.`:r(e.artifactDownloadDir,`tauri-${o}-${i}`),p=await P({globs:e.localPlatforms.includes(o)?s.artifactGlobs:[`**/*.sig`],sourceRoot:f,version:i}),m=K(a,s.updatePlatform),h=K(m,t(p.artifactPath)),g=K(m,t(p.signaturePath)),_=r(e.releaseUploadDir,...h.split(`/`)),v=r(e.releaseUploadDir,...g.split(`/`)),y=(await d(p.signaturePath,`utf8`)).trim();await u(n(_),{recursive:!0}),await l(p.artifactPath,_),await l(p.signaturePath,v),c.push({signature:y,updatePlatform:s.updatePlatform,url:G(e.publicBaseUrl,h)})}let f=O({artifacts:c,notes:e.releaseNotesPath?await d(e.releaseNotesPath,`utf8`):void 0,version:i}),p=r(e.releaseUploadDir,...o.split(`/`));return await u(n(p),{recursive:!0}),await m(p,`${JSON.stringify(f,null,2)}\n`),{manifestPath:o,uploadDir:e.releaseUploadDir}}async function A({plan:t,releaseName:n,version:r}){let i=t.workflowRef??await V(),a=`coreframe-${Date.now()}-${h().slice(0,8)}`;return await s(`gh`,[`workflow`,`run`,t.workflowName,`--ref`,i,`-f`,`release_name=${n}`,`-f`,`run_id=${a}`,`-f`,`version=${r}`,...e.flatMap(e=>[`-f`,`${e}=${t.workflowPlatforms.includes(e)?`true`:`false`}`])],{stdin:`ignore`}),{runNameMarker:a}}async function j(e,t){let n=Date.now()+e.workflowTimeoutMs;for(;Date.now()<n;){let n=await M(e,t);if(n?.status===`completed`){if(n.conclusion===`success`)return n.databaseId;throw Error(`Tauri release workflow ${n.databaseId} finished with ${n.conclusion}.`)}await _(e.workflowPollIntervalMs)}throw Error(`Timed out waiting for Tauri release workflow after ${Math.round(e.workflowTimeoutMs/6e4)} minutes.`)}async function M(e,t){let{stdout:n}=await s(`gh`,[`run`,`list`,`--workflow`,e.workflowName,`--event`,`workflow_dispatch`,`--json`,`conclusion,databaseId,displayTitle,status`,`--limit`,`50`],{stdin:`ignore`});return JSON.parse(n).find(e=>e.displayTitle.includes(t))}async function N(e,t,n){await s(`pnpm`,t.platform[e].buildArgs,{env:n,stdio:`inherit`})}async function P({globs:e,sourceRoot:t,version:n}){let r=await v(e,{cwd:t,absolute:!0,onlyFiles:!0}),i=[];for(let e of r){let t=e.replace(/\.sig$/u,``);await Z(t)&&i.push({artifactPath:t,signaturePath:e})}if(i.length===0)throw Error(`Could not find a Tauri updater artifact and .sig pair under ${t}.`);return i.sort((e,t)=>{let r=F(e.artifactPath,n);return F(t.artifactPath,n)-r||e.artifactPath.localeCompare(t.artifactPath)})[0]}function F(e,t){let n=0;return e.includes(t)&&(n+=100),/\.(app\.tar\.gz|AppImage\.tar\.gz|zip)$/u.test(e)&&(n+=10),/\.(exe|msi|dmg|AppImage|deb|rpm|apk|aab|ipa)$/u.test(e)&&(n+=5),n}function I(t={},n){return Object.fromEntries(e.map(e=>{let r=t[e]??{},i={...y[e],...r},a=n[`TAURI_RELEASE_${e.toUpperCase()}_TARGET_TRIPLE`],o=n[`TAURI_RELEASE_${e.toUpperCase()}_UPDATE_PLATFORM`],s=a??i.targetTriple,c=r.buildArgs?r.buildArgs:L(e,s);return[e,{...i,artifactGlobs:i.artifactGlobs??y[e].artifactGlobs,buildArgs:c,targetTriple:s,updatePlatform:o??i.updatePlatform}]}))}function L(e,t){return e===`android`?[`tauri`,`android`,`build`,`--target`,t]:e===`ios`?[`tauri`,`ios`,`build`,`--target`,t]:[`tauri`,`build`,`--target`,t,`--`,`--locked`]}function R(e,t){let n=/^\[package\][\s\S]*?^name = "(?<name>[^"]+)"/mu.exec(e);if(!n?.groups?.name)throw Error(`${t} is missing a [package] name.`);return n.groups.name}function z(e,t,n,r){let i=RegExp(`(^\\[package\\][\\s\\S]*?^name = "${Q(t)}"[\\s\\S]*?^version = ")[^"]+(")`,`mu`),a=e.replace(i,`$1${n}$2`);if(a===e)throw Error(`${r} is missing a package version for ${t}.`);return a}function B(e,t,n,r){let i=RegExp(`(\\[\\[package\\]\\]\\nname = "${Q(t)}"\\nversion = ")[^"]+(")`,`u`),a=e.replace(i,`$1${n}$2`);if(a===e)throw Error(`${r} is missing a package lock entry for ${t}.`);return a}async function V(){let{stdout:e}=await s(`git`,[`rev-parse`,`--abbrev-ref`,`HEAD`],{stdin:`ignore`}),t=e.trim();if(t!==`HEAD`)return t;let{stdout:n}=await s(`git`,[`rev-parse`,`HEAD`],{stdin:`ignore`});return n.trim()}async function H(e){U(e),await f(e,{force:!0,recursive:!0}),await u(e,{recursive:!0})}function U(e){let t=a(e),n=i(process.cwd(),t).split(o).join(`/`);if(n===``||n===`.`||n.startsWith(`../`)||n===`tauri`||n.startsWith(`tauri/`))throw Error(`Refusing to reset unsafe Tauri release directory: ${e}`)}function W(e,t){return e.replaceAll("${version}",t)}function G(e,t){let n=new URL(e),r=K(n.pathname.replace(/\/+$/u,``),t);return n.pathname=r.startsWith(`/`)?r:`/${r}`,n.toString()}function K(...e){return e.flatMap(e=>e.split(`/`)).map(e=>e.trim()).filter(Boolean).join(`/`)}function q(e,t){if(!e)return 0;let n=Number(e);if(!Number.isInteger(n)||n<=0)throw Error(`${t} must be a positive integer.`);return n}function J(e){return[...new Set(e)]}function Y(t){return e.includes(t)}async function X(e){try{return await c(e,g.F_OK),!0}catch{return!1}}async function Z(e){try{return(await p(e)).isFile()}catch{return!1}}function Q(e){return e.replace(/[.*+?^${}()|[\]\\]/gu,`\\$&`)}export{k as createTauriReleaseUpload,O as createTauriUpdateManifest,x as parseTauriReleasePlatform,b as parseTauriReleasePlatformList,E as prepareTauriRelease,S as resolveTauriReleasePlan,T as restoreTauriReleaseMetadata,D as uploadTauriReleaseToB2,C as validateTauriReleasePlan,w as writeTauriReleaseMetadata};
@@ -0,0 +1,20 @@
1
+ //#region deploy/version.d.ts
2
+ type PackageJson = {
3
+ name?: string;
4
+ version?: string;
5
+ [key: string]: unknown;
6
+ };
7
+ type ReleasePackageJson = PackageJson & {
8
+ name: string;
9
+ version: string;
10
+ };
11
+ declare function nextReleaseVersion(currentVersion: string, now?: Date): string;
12
+ declare function releaseName(packageName: string, version: string): string;
13
+ declare function formatReleaseDate(date: Date): string;
14
+ declare function readPackageJson(path?: string): Promise<{
15
+ packageJson: ReleasePackageJson;
16
+ source: string;
17
+ }>;
18
+ declare function writePackageVersion(path: string, packageJson: PackageJson, version: string): Promise<void>;
19
+ //#endregion
20
+ export { formatReleaseDate, nextReleaseVersion, readPackageJson, releaseName, writePackageVersion };
@@ -0,0 +1 @@
1
+ import{readFile as e,writeFile as t}from"node:fs/promises";function n(e,t=new Date){let n=s(e),r=i(t),a=n.minor===r?n.patch+1:0;return`${n.major}.${r}.${a}`}function r(e,t){return`${e}@${t}`}function i(e){return`${String(e.getFullYear())}${String(e.getMonth()+1).padStart(2,`0`)}${String(e.getDate()).padStart(2,`0`)}`}async function a(t=`package.json`){let n=await e(t,`utf8`),r=JSON.parse(n);if(!r.name)throw Error(`${t} is missing a package name.`);if(!r.version)throw Error(`${t} is missing a package version.`);return{packageJson:r,source:n}}async function o(e,n,r){await t(e,`${JSON.stringify({...n,version:r},null,2)}\n`)}function s(e){let t=/^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$/u.exec(e);if(!t?.groups)throw Error(`Invalid package version "${e}". Expected major.minor.patch.`);return{major:Number(t.groups.major),minor:t.groups.minor,patch:Number(t.groups.patch)}}export{i as formatReleaseDate,n as nextReleaseVersion,a as readPackageJson,r as releaseName,o as writePackageVersion};
@@ -0,0 +1,19 @@
1
+ import { TauriReleasePlatform } from "./deploy/config.js";
2
+ import { CoreframeConfig } from "./config.js";
3
+
4
+ //#region deploy.d.ts
5
+ type DeployArgs = {
6
+ runMigrations: boolean;
7
+ skipMigrations: boolean;
8
+ skipPostDeployChecks: boolean;
9
+ tauriLocalPlatforms: TauriReleasePlatform[];
10
+ tauriWorkflowName?: string;
11
+ tauriWorkflowPlatforms: TauriReleasePlatform[];
12
+ tauriWorkflowRef?: string;
13
+ yes: boolean;
14
+ };
15
+ declare function deployProject(argv?: string[], env?: NodeJS.ProcessEnv): Promise<void>;
16
+ declare function readProjectConfig(): Promise<CoreframeConfig>;
17
+ declare function parseDeployArgs(argv: string[]): DeployArgs;
18
+ //#endregion
19
+ export { deployProject, parseDeployArgs, readProjectConfig };
package/dist/deploy.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{collectDeployMigrationFiles as e,formatMigrationList as t,formatMigrationScanFailures as n,scanDeployMigrationFiles as r}from"./deploy/migrations.js";import{createProjectDeployDatabase as i}from"./db/project.js";import{runPostDeployChecks as a}from"./deploy/checks.js";import{resolveDeployConfig as o}from"./deploy/config.js";import{filterPendingMigrations as s,readAppliedMigrationNames as c}from"./deploy/pending-migrations.js";import{parseTauriReleasePlatform as l,prepareTauriRelease as u,resolveTauriReleasePlan as d,restoreTauriReleaseMetadata as f,uploadTauriReleaseToB2 as p,validateTauriReleasePlan as m,writeTauriReleaseMetadata as h}from"./deploy/tauri-release.js";import{nextReleaseVersion as g,readPackageJson as _,releaseName as v,writePackageVersion as y}from"./deploy/version.js";import{resolve as b}from"node:path";import x from"nano-spawn";import{pathToFileURL as S}from"node:url";import{access as C,writeFile as w}from"node:fs/promises";import*as T from"@clack/prompts";const E=`package.json`;async function D(e=process.argv.slice(2),t=process.env){let s=k(e),{packageJson:c,source:l}=await _(E),b=g(c.version),S=v(c.name,b),C=t.DEPLOY_ENV??`production`,D=R(t,S,c.name,b),A=o((await O()).deploy??{},t),j=d({args:s,config:A.tauriRelease,env:t}),N=!1,B={files:[]};T.intro(`Deploy ${S}`),I(s),L(t,[`POSTHOG_API_KEY`,`POSTHOG_PROJECT_ID`]),await F(),await m(j);let V=await M(await i({env:t})),H=await r(V);if(H.length>0)throw Error(`Pending deploy migrations contain unsafe SQL. Move unsafe statements to held migrations.\n${n(H)}`);let U=await P(s,V);try{await y(E,c,b),B=await h(j,b),T.log.step(`Running pre-deploy build`),await x(`pnpm`,[`build`],{env:z(D),stdio:`inherit`});let e=j.enabled?await u({env:z(D),plan:j,releaseName:S,version:b}):void 0;T.log.step(`Running release build and uploading PostHog source maps`),N=!0,await x(`pnpm`,[`build`],{env:D,stdio:`inherit`}),U&&(T.log.step(`Running deploy-safe database migrations`),await x(`pnpm`,[`db`,`migrate`],{env:D,stdio:`inherit`})),T.log.step(`Deploying Worker and static assets to Cloudflare`),await x(`pnpm`,[`exec`,`wrangler`,`deploy`,`--strict`,`--keep-vars`,`--tag`,S,`--message`,`Deploy ${S}`,`--var`,`POSTHOG_RELEASE:${S}`],{env:D,stdio:`inherit`}),s.skipPostDeployChecks||(T.log.step(`Running post-deploy checks`),await a(A,{environment:C,productionUrl:A.productionUrl,releaseName:S,version:b})),e&&(T.log.step(`Uploading Tauri release artifacts to Backblaze B2`),await p(j,e)),T.outro(`Deployed ${S}`)}catch(e){throw N||(await w(E,l),await f(B)),e}}async function O(){let e=b(`coreframe.config.ts`);try{await C(e)}catch{return{}}return(await import(S(e).href)).default??{}}function k(e){return{runMigrations:e.includes(`--run-migrations`)||e.includes(`--yes`),skipMigrations:e.includes(`--skip-migrations`),skipPostDeployChecks:e.includes(`--skip-post-deploy-checks`),tauriLocalPlatforms:A(e,`--tauri-local-platform`).map(e=>l(e)),tauriWorkflowName:j(e,`--tauri-workflow-name`),tauriWorkflowPlatforms:A(e,`--tauri-workflow-platform`).map(e=>l(e)),tauriWorkflowRef:j(e,`--tauri-workflow-ref`),yes:e.includes(`--yes`)}}function A(e,t){let n=[];for(let r=0;r<e.length;r+=1){let i=e[r];if(i===t){let i=e[r+1];if(!i||i.startsWith(`--`))throw Error(`${t} requires a value.`);n.push(...i.split(`,`).filter(Boolean)),r+=1;continue}i.startsWith(`${t}=`)&&n.push(...i.slice(t.length+1).split(`,`).filter(Boolean))}return n}function j(e,t){let n=A(e,t);if(n.length>1)throw Error(`${t} can only be provided once.`);return n[0]}async function M(e){try{return await N(e)}finally{await e.close?.()}}async function N(t){return s(await e(),await c(t))}async function P(e,n){if(n.length===0)return T.log.info(`No pending deploy migrations.`),!1;if(T.note(t(n),`Pending deploy migrations`),e.runMigrations)return!0;if(e.skipMigrations)return!1;if(!process.stdout.isTTY)throw Error(`Pending migrations require --run-migrations or --skip-migrations in noninteractive deploys.`);let r=await T.confirm({initialValue:!0,message:`Run these deploy-safe database migrations before Cloudflare deploy?`});if(T.isCancel(r))throw T.cancel(`Deploy cancelled.`),Error(`Deploy cancelled.`);return r}async function F(){let{stdout:e}=await x(`git`,[`status`,`--porcelain`],{stdin:`ignore`});if(e.trim().length>0)throw Error(`Deploy requires a clean git tree before the release version is applied.`)}function I(e){if(e.runMigrations&&e.skipMigrations)throw Error(`Use only one of --run-migrations, --skip-migrations, or --yes.`)}function L(e,t){let n=t.filter(t=>!e[t]);if(n.length>0)throw Error(`Missing required deploy environment variables: ${n.join(`, `)}`);return Object.fromEntries(t.map(t=>[t,e[t]]))}function R(e,t,n,r){return{...e,POSTHOG_RELEASE:t,POSTHOG_RELEASE_NAME:e.POSTHOG_RELEASE_NAME??n,POSTHOG_RELEASE_VERSION:e.POSTHOG_RELEASE_VERSION??r}}function z(e){let{POSTHOG_API_KEY:t,POSTHOG_PROJECT_ID:n,...r}=e;return r}import.meta.main&&D().catch(e=>{console.error(e),process.exit(1)});export{D as deployProject,k as parseDeployArgs,O as readProjectConfig};
package/dist/dev.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { spawn as spawn$1 } from "node:child_process";
2
+
3
+ //#region dev.d.ts
4
+ type Spawn = typeof spawn$1;
5
+ type DevServerOptions = {
6
+ cwd?: string;
7
+ env?: NodeJS.ProcessEnv;
8
+ logPath?: string;
9
+ platform?: DevPlatform;
10
+ spawn?: Spawn;
11
+ };
12
+ type DevPlatform = "android" | "desktop" | "ios" | "web";
13
+ declare function runDevServer({
14
+ cwd,
15
+ env,
16
+ logPath,
17
+ platform,
18
+ spawn
19
+ }?: DevServerOptions): Promise<void>;
20
+ //#endregion
21
+ export { DevPlatform, DevServerOptions, runDevServer };
package/dist/dev.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import e from"node:path";import t from"node:process";import{mkdir as n}from"node:fs/promises";import{createWriteStream as r}from"node:fs";import{spawn as i}from"node:child_process";const a=e.resolve(`node_modules`,`.bin`),o=t.platform===`win32`?`pnpm.cmd`:`pnpm`,s=t.platform===`win32`?`react-router.cmd`:`react-router`;async function c({cwd:n=t.cwd(),env:r=t.env,logPath:c=`.agents/logs/dev-server.log`,platform:p=`web`,spawn:m=i}={}){let h=r.PATH??r.Path??``,g=`TAURI_ENV_PLATFORM`in r?void 0:await l(c);try{if(p===`web`){await f(m,o,[`db`,`ensure`],{cwd:n,env:r,log:g}),await f(m,s,[`dev`],{cwd:n,env:{...r,CLOUDFLARE_ENV:`development`,PATH:a+e.delimiter+h},log:g});return}await f(m,o,d(p),{cwd:n,env:r,log:g})}finally{await u(g)}}async function l(t){return await n(e.dirname(t),{recursive:!0}),r(t,{flags:`w`})}function u(e){return e?new Promise((t,n)=>{e.once(`error`,n),e.end(t)}):Promise.resolve()}function d(e){return[`--dir=tauri`,`tauri`,...e===`desktop`?[]:[e],`dev`]}function f(e,n,r,{cwd:i,env:a,log:o}){return new Promise((s,c)=>{let l=e(n,r,{cwd:i,env:a,stdio:o?[`inherit`,`pipe`,`pipe`]:`inherit`});if(o){if(!l.stdout||!l.stderr){c(Error(`${[n,...r].join(` `)} did not provide output streams`));return}l.stdout.on(`data`,e=>{t.stdout.write(e),o.write(e)}),l.stderr.on(`data`,e=>{t.stderr.write(e),o.write(e)})}l.on(`error`,c),l.on(`close`,(e,t)=>{if(e===0){s();return}let i=[n,...r].join(` `);c(Error(`${i} failed${t?` with signal ${t}`:` with exit code ${e??1}`}`))})})}import.meta.main&&c().catch(e=>{console.error(e),t.exit(1)});export{c as runDevServer};
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region image-generator.d.ts
4
+ declare function imageGenerator(): Plugin;
5
+ //#endregion
6
+ export { imageGenerator };
@@ -0,0 +1 @@
1
+ import e from"node:path";import t from"node:fs";import{glob as n}from"tinyglobby";function r(){let e;return{name:`image-generator`,configResolved(t){e=t.command},async configureServer(e){await i(),e.watcher.on(`add`,e=>{a(e)&&o(e)}),e.watcher.on(`change`,e=>{a(e)&&o(e,{overwrite:!0})})},applyToEnvironment(e){return e.name===`client`},async buildStart(){e===`build`&&await i()}}}async function i(){let e=await n([`src/**/*@3x.{png,webp}`,`public/**/*@3x.{png,webp}`]);console.log(`[image-generator] Found ${e.length} @3x images.`),await Promise.all(e.map(e=>o(e)))}function a(e){return/@3x\.(png|webp)$/.test(e)}async function o(n,r={}){let i=e.dirname(n),a=e.extname(n),o=e.basename(n,`@3x${a}`),c=[{outputPath:e.join(i,`${o}@2x${a}`),scale:2/3},{outputPath:e.join(i,`${o}@1x${a}`),scale:1/3}].filter(e=>r.overwrite||!t.existsSync(e.outputPath));if(c.length===0)return;let{default:l}=await import(`sharp`),u;try{u=(await l(n).metadata()).width}catch(e){console.error(`[image-generator] Failed to read metadata for ${n}`,e);return}if(!u){console.error(`[image-generator] Skipping ${n}; image width could not be read.`);return}await Promise.all(c.map(e=>s({file:n,outputPath:e.outputPath,sharp:l,width:Math.round(u*e.scale)})))}async function s(e){try{console.log(`[image-generator] Generating ${e.outputPath} (${e.width}w)`),await e.sharp(e.file).resize({width:e.width}).toFile(e.outputPath)}catch(t){console.error(`[image-generator] Failed to process ${e.file} for ${e.outputPath}`,t)}}export{r as imageGenerator};
@@ -0,0 +1,4 @@
1
+ //#region project.d.ts
2
+ declare function importProjectModule<T>(path: string): Promise<T>;
3
+ //#endregion
4
+ export { importProjectModule };
@@ -0,0 +1 @@
1
+ import{resolve as e}from"node:path";import{pathToFileURL as t}from"node:url";async function n(n){return await import(t(e(process.cwd(),n)).href)}export{n as importProjectModule};
@@ -0,0 +1,4 @@
1
+ //#region typecheck.d.ts
2
+ declare function typecheckProject(): Promise<void>;
3
+ //#endregion
4
+ export { typecheckProject };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import e from"node:path";import t from"node:process";import{access as n}from"node:fs/promises";import{supervise as r}from"procband";const i=e.resolve(`node_modules`,`.bin`);t.env.PATH=i+e.delimiter+t.env.PATH;const a=t.platform===`win32`?`tsc.cmd`:`tsc`,o=[{name:`scripts`,path:`scripts/tsconfig.json`},{name:`shared`,path:`src/shared/tsconfig.json`},{name:`api`,path:`src/api/tsconfig.json`},{name:`worker`,path:`src/api/worker/tsconfig.json`},{name:`web`,path:`src/web/tsconfig.json`}];async function s(){let e=[];for(let t of o)try{await n(t.path),e.push(t)}catch(e){if(!(e instanceof Error&&`code`in e&&e.code===`ENOENT`))throw e}let i=e.map(e=>r({name:e.name,command:a,args:[`-p`,e.path]})),s=(await Promise.all(i.map(e=>e.wait()))).filter(e=>e.exitCode!==0);s.length>0&&(t.exitCode=s[0].exitCode)}import.meta.main&&s().catch(e=>{console.error(e),t.exit(1)});export{s as typecheckProject};
@@ -0,0 +1,11 @@
1
+ //#region upgrade/check-skill-current.d.ts
2
+ type CheckUpgradeSkillOptions = {
3
+ apply: boolean;
4
+ target: string;
5
+ };
6
+ declare function checkUpgradeSkillCurrent({
7
+ apply,
8
+ target
9
+ }: CheckUpgradeSkillOptions): 0 | 20;
10
+ //#endregion
11
+ export { CheckUpgradeSkillOptions, checkUpgradeSkillCurrent };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{join as e}from"node:path";import{existsSync as t,mkdtempSync as n,readFileSync as r,rmSync as i,writeFileSync as a}from"node:fs";import{spawnSync as o}from"node:child_process";import{tmpdir as s}from"node:os";const c=`.agents/skills/coreframe-upgrade/SKILL.md`;function l(e,t={}){return o(`git`,e,t)}function u(e){return e.stderr.toString(`utf8`).trim()||e.stdout.toString(`utf8`).trim()}function d(e,t){let n=l([`show`,`${e}:${t}`],{encoding:`utf8`});if(n.status!==0){let r=u(n);throw Error(`Could not read ${t} at ${e}.${r?` ${r}`:``}`)}return n.stdout.toString()}function f(){let e=l([`rev-parse`,`--show-toplevel`],{encoding:`utf8`});if(e.status!==0){let t=u(e);throw Error(`Could not find git repository root.${t?` ${t}`:``}`)}return e.stdout.toString().trim()}function p(t,r){let o=n(e(s(),`coreframe-upgrade-skill-`)),c=e(o,`SKILL.md`);try{a(c,r);let e=u(l([`--no-pager`,`diff`,`--no-index`,`--color=never`,`--`,t,c]));e&&console.log(e)}finally{i(o,{force:!0,recursive:!0})}}function m({apply:n,target:i}){let o=e(f(),c);if(!t(o))throw Error(`Local ${c} does not exist.`);let s=r(o,`utf8`),l=d(i,c);return s===l?(console.log(`Coreframe upgrade skill matches ${i}:${c}.`),0):n?(a(o,l),console.error(`Updated ${c} from ${i}. Reread it before continuing the upgrade.`),20):(console.error(`Coreframe upgrade skill differs from ${i}:${c}.`),console.error(`Run again with --apply to update it, then reread ${c} before continuing.`),p(o,l),20)}if(import.meta.main){let e=process.argv.indexOf(`--target`),t=e===-1?null:process.argv[e+1];t||(console.error(`Usage: coreframe upgrade check-skill-current --target <git-ref> [--apply]`),process.exit(64));try{process.exit(m({apply:process.argv.includes(`--apply`),target:t}))}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(65)}}export{m as checkUpgradeSkillCurrent};
@@ -0,0 +1,11 @@
1
+ //#region upgrade/ensure-template-remote.d.ts
2
+ type EnsureTemplateRemoteOptions = {
3
+ fetch: boolean;
4
+ remote: string;
5
+ };
6
+ declare function ensureTemplateRemote({
7
+ fetch,
8
+ remote
9
+ }?: Partial<EnsureTemplateRemoteOptions>): void;
10
+ //#endregion
11
+ export { EnsureTemplateRemoteOptions, ensureTemplateRemote };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{spawnSync as e}from"node:child_process";const t=`https://github.com/airlock-labs/coreframe.git`;function n(t){return e(`git`,t,{encoding:`utf8`})}function r(e){let t=n(e);if(t.status!==0){let n=t.stderr.trim()||t.stdout.trim();throw Error(`Git command failed: git ${e.join(` `)}${n?`\n${n}`:``}`)}return t.stdout}function i(e){let t=n([`remote`,`get-url`,e]);if(t.status===2)return null;if(t.status!==0){let n=t.stderr.trim()||t.stdout.trim();throw Error(`Could not inspect remote ${e}.${n?`\n${n}`:``}`)}return t.stdout.trim()}function a({fetch:e=!0,remote:n=`template`}={}){if(n.length===0)throw Error(`Remote name must not be empty.`);r([`rev-parse`,`--show-toplevel`]);let a=i(n);if(a===null)r([`remote`,`add`,n,t]),console.log(`Added ${n} remote: ${t}`);else if(a===t)console.log(`${n} remote already points at ${t}`);else throw Error(`${n} remote points at ${a}, not ${t}.\nChoose the intended template source before continuing, then update or rename the remote manually.`);e&&(r([`fetch`,n,`--tags`,`--prune`]),console.log(`Fetched ${n}.`))}if(import.meta.main)try{a({fetch:!process.argv.includes(`--no-fetch`)})}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(65)}export{a as ensureTemplateRemote};
@@ -0,0 +1,9 @@
1
+ //#region upgrade/fast-forward-template-files.d.ts
2
+ type FastForwardTemplateFilesOptions = {
3
+ apply: boolean;
4
+ base?: string;
5
+ target: string;
6
+ };
7
+ declare function fastForwardTemplateFiles(args: FastForwardTemplateFilesOptions): void;
8
+ //#endregion
9
+ export { FastForwardTemplateFilesOptions, fastForwardTemplateFiles };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{dirname as e,join as t,resolve as n,sep as r}from"node:path";import{chmodSync as i,existsSync as a,lstatSync as o,mkdirSync as s,readFileSync as c,readlinkSync as l,rmSync as u,symlinkSync as d,writeFileSync as f}from"node:fs";import{spawnSync as p}from"node:child_process";const m=[`drizzle/`,`drizzle-hold/`],h=new Set([`100644`,`100755`,`120000`]);function g(e,t={}){return p(`git`,e,t)}function _(e){let t=g(e,{encoding:`utf8`});if(t.status!==0){let n=t.stderr.toString().trim()||t.stdout.toString().trim();throw Error(`Git command failed: git ${e.join(` `)}${n?`\n${n}`:``}`)}return t.stdout.toString()}function v(e){let t=g(e);if(t.status!==0){let n=t.stderr.toString(`utf8`).trim()||t.stdout.toString(`utf8`).trim();throw Error(`Git command failed: git ${e.join(` `)}${n?`\n${n}`:``}`)}return t.stdout}function y(){return _([`rev-parse`,`--show-toplevel`]).trim()}function b(e){let n=t(e,`package.json`);if(!a(n))throw Error(`Missing --base <git-ref>, and package.json does not exist.`);let r=JSON.parse(c(n,`utf8`)).engines?.coreframe;if(typeof r!=`string`||r.length===0)throw Error(`Missing --base <git-ref>, and package.json engines.coreframe is not set.`);return r}function x(e){let t=[],n=0;for(let r=0;r<e.length;r+=1)e[r]===0&&(t.push(e.subarray(n,r).toString(`utf8`)),n=r+1);return t.filter(e=>e.length>0)}function S(e,t){let n=x(v([`diff`,`--name-status`,`-z`,`--no-renames`,e,t,`--`])),r=[];for(let e=0;e<n.length;e+=2){let t=n[e],i=n[e+1];if(!t||!i)throw Error(`Could not parse git diff --name-status output.`);r.push({path:i,status:t})}return r}function C(e,t){let n=v([`ls-tree`,`-z`,e,`--`,t]);if(n.length===0)return null;let r=x(n)[0],i=r.indexOf(` `);if(i===-1)throw Error(`Could not parse git ls-tree output for ${e}:${t}.`);let[a,o,s]=r.slice(0,i).split(` `);if(!a||!o||!s)throw Error(`Could not parse git ls-tree metadata for ${e}:${t}.`);return{mode:a,object:s,path:t,type:o}}function w(e){return v([`cat-file`,`blob`,e.object])}function T(e,t){let i=n(e,t),a=e.endsWith(r)?e:`${e}${r}`;if(i!==e&&!i.startsWith(a))throw Error(`Refusing to access path outside repository: ${t}`);return i}function E(e){try{return o(e),!0}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return!1;throw e}}function D(e){return(e.mode&73)!=0}function O(e){return e===null||e.type===`blob`&&h.has(e.mode)}function k(e){return m.some(t=>e===t.slice(0,-1)||e.startsWith(t))}function A(e,t,n){let r=T(e,t);if(n===null)return{matches:!E(r)};if(!O(n))return{matches:!1,reason:`unsupported template entry mode ${n.mode} ${n.type}`};let i;try{i=o(r)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{matches:!1,reason:`path is missing from worktree`};throw e}let a=w(n);return n.mode===`120000`?i.isSymbolicLink()?{matches:Buffer.from(l(r),`utf8`).equals(a)}:{matches:!1,reason:`worktree path is not a symlink`}:i.isFile()?(n.mode===`100755`?D(i):!D(i))?{matches:c(r).equals(a)}:{matches:!1,reason:`worktree executable bit differs from template`}:{matches:!1,reason:`worktree path is not a regular file`}}function j(e){E(e)&&u(e,{force:!0,recursive:!1})}function M(t,n,r){let a=T(t,n);if(r===null){j(a);return}if(s(e(a),{recursive:!0}),r.mode===`120000`){j(a),d(w(r).toString(`utf8`),a);return}let c=E(a)?o(a):null;c&&!c.isFile()&&j(a),f(a,w(r)),i(a,r.mode===`100755`?493:420)}function N(e,t){return e===null&&t!==null?`add`:e!==null&&t===null?`delete`:`update`}function P(e,t){if(t.length!==0){console.log(`${e}:`);for(let e of t){let t=e.reason?` (${e.reason})`:``;console.log(` ${e.action}: ${e.path}${t}`)}console.log(``)}}function F(e){let t=y(),n=e.base??b(t),r=e.target,i=S(n,r),a=[],o=[],s=[],c=[];for(let e of i){if(k(e.path)){o.push({action:`ignored`,path:e.path,reason:`project-owned migration history`});continue}let i=C(n,e.path),l=C(r,e.path),u=N(i,l);if(!O(i)||!O(l)){s.push({action:u,path:e.path,reason:`unsupported template entry type`});continue}if(A(t,e.path,l).matches){c.push({action:`unchanged`,path:e.path});continue}let d=A(t,e.path,i);if(!d.matches){s.push({action:u,path:e.path,reason:d.reason??`downstream differs from base template`});continue}a.push({action:u,path:e.path,targetEntry:l})}if(e.apply)for(let e of a)M(t,e.path,e.targetEntry??null);console.log(`Coreframe template fast-forward: ${n} -> ${r}`),console.log(``),P(e.apply?`Applied clean template updates`:`Would apply clean template updates`,a),P(`Ignored project-owned paths`,o),P(`Skipped for agent review`,s),c.length>0&&console.log(`${c.length} changed template path(s) already matched the target state.`),i.length===0&&console.log(`No template file changes found in the requested range.`),!e.apply&&a.length>0&&(console.log(``),console.log(`Run with --apply to write the ${a.length} clean update(s).`))}if(import.meta.main){let e=process.argv.indexOf(`--target`),t=e===-1?null:process.argv[e+1];t||(console.error(`Usage: coreframe upgrade fast-forward-template-files --target <git-ref> [--base <git-ref>] [--apply]`),process.exit(64));try{let e=process.argv.indexOf(`--base`);F({apply:process.argv.includes(`--apply`),base:e===-1?void 0:process.argv[e+1],target:t})}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(65)}}export{F as fastForwardTemplateFiles};
package/package.json CHANGED
@@ -1,13 +1,58 @@
1
1
  {
2
2
  "name": "@coreframe/scripts",
3
- "version": "0.0.0",
4
- "description": "",
5
- "main": "index.js",
3
+ "version": "0.1.1",
4
+ "bin": {
5
+ "coreframe": "./bin/coreframe.js"
6
+ },
7
+ "files": [
8
+ "bin/",
9
+ "dist/"
10
+ ],
11
+ "type": "module",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/airlock-labs/coreframe.git",
15
+ "directory": "scripts"
16
+ },
17
+ "exports": {
18
+ "./*": {
19
+ "types": "./dist/*.d.ts",
20
+ "default": "./dist/*.js"
21
+ }
22
+ },
6
23
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
24
+ "build": "tsdown",
25
+ "test": "vitest run",
26
+ "typecheck": "tsc --noEmit -p tsconfig.json",
27
+ "format": "oxfmt",
28
+ "lint": "oxlint"
29
+ },
30
+ "dependencies": {
31
+ "@clack/prompts": "^1.6.0",
32
+ "cmd-ts": "0.15.0",
33
+ "drizzle-orm": "1.0.0-rc.4",
34
+ "nano-spawn": "^2.1.0",
35
+ "procband": "^0.3.5",
36
+ "tinyglobby": "^0.2.17"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^26.1.0",
40
+ "sharp": "^0.34.5",
41
+ "tsdown": "0.22.3",
42
+ "typescript": "6.0.3",
43
+ "vite": "^8.1.0",
44
+ "vitest": "^4.1.9"
45
+ },
46
+ "peerDependencies": {
47
+ "sharp": "^0.34.5",
48
+ "vite": "^8.0.0"
8
49
  },
9
- "keywords": [],
10
- "author": "",
11
- "license": "All rights reserved",
12
- "type": "commonjs"
50
+ "peerDependenciesMeta": {
51
+ "sharp": {
52
+ "optional": true
53
+ },
54
+ "vite": {
55
+ "optional": true
56
+ }
57
+ }
13
58
  }
package/readme.md DELETED
@@ -1 +0,0 @@
1
- Coming soon...