@coreframe/scripts 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/config.d.ts +18 -4
  2. package/dist/config.js +1 -3
  3. package/dist/coreframe.d.ts +243 -241
  4. package/dist/coreframe.js +1 -239
  5. package/dist/db/client.d.ts +26 -18
  6. package/dist/db/client.js +1 -29
  7. package/dist/db/config.d.ts +22 -19
  8. package/dist/db/config.js +1 -32
  9. package/dist/db/drizzle-kit.d.ts +6 -5
  10. package/dist/db/drizzle-kit.js +1 -21
  11. package/dist/db/ensure.d.ts +20 -2
  12. package/dist/db/ensure.js +1 -39
  13. package/dist/db/migrate-held.d.ts +16 -10
  14. package/dist/db/migrate-held.js +3 -112
  15. package/dist/db/migrate.d.ts +4 -2
  16. package/dist/db/migrate.js +1 -24
  17. package/dist/db/project.d.ts +19 -7
  18. package/dist/db/project.js +1 -9
  19. package/dist/db/reset.d.ts +4 -2
  20. package/dist/db/reset.js +1 -18
  21. package/dist/db/schema-push.d.ts +22 -0
  22. package/dist/db/schema-push.js +1 -0
  23. package/dist/db/seed.d.ts +6 -3
  24. package/dist/db/seed.js +1 -22
  25. package/dist/db/setup.d.ts +6 -3
  26. package/dist/db/setup.js +1 -14
  27. package/dist/deploy/check-migrations.d.ts +4 -2
  28. package/dist/deploy/check-migrations.js +1 -17
  29. package/dist/deploy/checks.d.ts +6 -2
  30. package/dist/deploy/checks.js +1 -27
  31. package/dist/deploy/config.d.ts +47 -44
  32. package/dist/deploy/config.js +1 -9
  33. package/dist/deploy/migrations.d.ts +21 -18
  34. package/dist/deploy/migrations.js +4 -203
  35. package/dist/deploy/pending-migrations.d.ts +9 -5
  36. package/dist/deploy/pending-migrations.js +1 -30
  37. package/dist/deploy/tauri-release.d.ts +93 -73
  38. package/dist/deploy/tauri-release.js +1 -512
  39. package/dist/deploy/version.d.ts +15 -13
  40. package/dist/deploy/version.js +1 -41
  41. package/dist/deploy.d.ts +16 -15
  42. package/dist/deploy.js +1 -246
  43. package/dist/dev.d.ts +23 -12
  44. package/dist/dev.js +1 -95
  45. package/dist/image-generator.d.ts +18 -2
  46. package/dist/image-generator.js +1 -59
  47. package/dist/project.d.ts +4 -1
  48. package/dist/project.js +1 -13
  49. package/dist/typecheck.d.ts +4 -2
  50. package/dist/typecheck.js +1 -44
  51. package/dist/upgrade/check-skill-current.d.ts +10 -5
  52. package/dist/upgrade/check-skill-current.js +1 -87
  53. package/dist/upgrade/ensure-template-remote.d.ts +10 -5
  54. package/dist/upgrade/ensure-template-remote.js +1 -56
  55. package/dist/upgrade/fast-forward-template-files.d.ts +8 -6
  56. package/dist/upgrade/fast-forward-template-files.js +1 -279
  57. package/package.json +7 -2
  58. package/config.test.ts +0 -15
  59. package/config.ts +0 -9
  60. package/coreframe.ts +0 -272
  61. package/db/client.ts +0 -72
  62. package/db/config.ts +0 -41
  63. package/db/drizzle-kit.test.ts +0 -27
  64. package/db/drizzle-kit.ts +0 -39
  65. package/db/ensure.ts +0 -67
  66. package/db/migrate-held.test.ts +0 -81
  67. package/db/migrate-held.ts +0 -166
  68. package/db/migrate.ts +0 -39
  69. package/db/project.ts +0 -28
  70. package/db/reset.ts +0 -24
  71. package/db/seed.ts +0 -38
  72. package/db/setup.ts +0 -18
  73. package/deploy/check-migrations.ts +0 -31
  74. package/deploy/checks.ts +0 -38
  75. package/deploy/config.test.ts +0 -24
  76. package/deploy/config.ts +0 -68
  77. package/deploy/migrations.test.ts +0 -110
  78. package/deploy/migrations.ts +0 -317
  79. package/deploy/pending-migrations.test.ts +0 -93
  80. package/deploy/pending-migrations.ts +0 -45
  81. package/deploy/tauri-release.test.ts +0 -196
  82. package/deploy/tauri-release.ts +0 -816
  83. package/deploy/version.test.ts +0 -29
  84. package/deploy/version.ts +0 -65
  85. package/deploy.test.ts +0 -101
  86. package/deploy.ts +0 -346
  87. package/dev.test.ts +0 -153
  88. package/dev.ts +0 -155
  89. package/image-generator.ts +0 -84
  90. package/project.ts +0 -6
  91. package/tsconfig.build.json +0 -12
  92. package/tsconfig.json +0 -20
  93. package/typecheck.ts +0 -55
  94. package/upgrade/check-skill-current.ts +0 -112
  95. package/upgrade/ensure-template-remote.ts +0 -79
  96. package/upgrade/fast-forward-template-files.ts +0 -408
@@ -1,317 +0,0 @@
1
- import { access, readFile, readdir } from "node:fs/promises"
2
- import { basename, join, relative, resolve } from "node:path"
3
-
4
- import { HELD_MIGRATIONS_FOLDER, MIGRATIONS_FOLDER } from "../db/config.ts"
5
-
6
- export type MigrationFile = {
7
- id: string
8
- path: string
9
- relativePath: string
10
- }
11
-
12
- export type MigrationRisk = {
13
- code: string
14
- line: number
15
- match: string
16
- reason: string
17
- }
18
-
19
- export type MigrationScanResult = {
20
- migration: MigrationFile
21
- risks: MigrationRisk[]
22
- }
23
-
24
- type StaticPattern = {
25
- code: string
26
- pattern: RegExp
27
- reason: string
28
- }
29
-
30
- const staticBlockedPatterns: StaticPattern[] = [
31
- {
32
- code: "drop-schema-object",
33
- pattern:
34
- /\bdrop\s+(?:table|view|materialized\s+view|index|sequence|type|schema|function|trigger|policy)\b/giu,
35
- reason: "drops schema that the currently deployed app may still depend on",
36
- },
37
- {
38
- code: "drop-column",
39
- pattern: /\balter\s+table\b[^;]*?\bdrop\s+column\b/giu,
40
- reason: "removes a column that the currently deployed app may still read or write",
41
- },
42
- {
43
- code: "drop-constraint",
44
- pattern: /\balter\s+table\b[^;]*?\bdrop\s+constraint\b/giu,
45
- reason: "removes a constraint outside the staged contract phase",
46
- },
47
- {
48
- code: "truncate",
49
- pattern: /\btruncate\b/giu,
50
- reason: "removes data outside the deploy-safe migration path",
51
- },
52
- {
53
- code: "rename-table-or-column",
54
- pattern: /\balter\s+table\b[^;]*?\brename(?:\s+column)?\b/giu,
55
- reason: "renames schema in a way old and new app versions cannot both address",
56
- },
57
- {
58
- code: "rewrite-column-type",
59
- pattern: /\balter\s+table\b[^;]*?\balter\s+column\b[^;]*?\btype\b/giu,
60
- reason: "rewrites a column type outside a staged expand/contract rollout",
61
- },
62
- {
63
- code: "set-not-null",
64
- pattern: /\balter\s+table\b[^;]*?\balter\s+column\b[^;]*?\bset\s+not\s+null\b/giu,
65
- reason: "enforces NOT NULL before a separate backfill and verification step",
66
- },
67
- {
68
- code: "set-default",
69
- pattern: /\balter\s+table\b[^;]*?\balter\s+column\b[^;]*?\bset\s+default\b/giu,
70
- reason: "changes default write behavior while old app code may still be running",
71
- },
72
- {
73
- code: "broad-update",
74
- pattern: /\bupdate\b/giu,
75
- reason: "changes data outside a separately reviewed backfill or held migration",
76
- },
77
- {
78
- code: "broad-delete",
79
- pattern: /\bdelete\s+from\b/giu,
80
- reason: "removes data outside a held migration",
81
- },
82
- ]
83
-
84
- export async function collectDeployMigrationFiles(cwd = process.cwd()): Promise<MigrationFile[]> {
85
- return collectDrizzleMigrationFiles(resolve(cwd, MIGRATIONS_FOLDER), cwd)
86
- }
87
-
88
- export async function collectHeldMigrationFiles(cwd = process.cwd()): Promise<MigrationFile[]> {
89
- return collectFlatSqlFiles(resolve(cwd, HELD_MIGRATIONS_FOLDER), cwd)
90
- }
91
-
92
- export async function scanDeployMigrationFiles(
93
- migrations: MigrationFile[],
94
- ): Promise<MigrationScanResult[]> {
95
- const results = await Promise.all(
96
- migrations.map(async (migration) => {
97
- const sql = await readFile(migration.path, "utf8")
98
-
99
- return {
100
- migration,
101
- risks: scanMigrationSql(sql),
102
- }
103
- }),
104
- )
105
-
106
- return results.filter((result) => result.risks.length > 0)
107
- }
108
-
109
- export function scanMigrationSql(sql: string): MigrationRisk[] {
110
- const maskedSql = maskSql(sql)
111
- const createdTables = collectCreatedTables(maskedSql)
112
- const risks: MigrationRisk[] = []
113
-
114
- for (const blocked of staticBlockedPatterns) {
115
- for (const match of maskedSql.matchAll(blocked.pattern)) {
116
- risks.push(riskForMatch(sql, match, blocked.code, blocked.reason))
117
- }
118
- }
119
-
120
- for (const match of maskedSql.matchAll(
121
- /\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,
122
- )) {
123
- if (!createdTables.has(normalizeIdentifier(match.groups?.table))) {
124
- risks.push(
125
- riskForMatch(
126
- sql,
127
- match,
128
- "add-not-null-column",
129
- "adds a required column to an existing table before a separate compatibility rollout",
130
- ),
131
- )
132
- }
133
- }
134
-
135
- for (const match of maskedSql.matchAll(
136
- /\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,
137
- )) {
138
- if (!createdTables.has(normalizeIdentifier(match.groups?.table))) {
139
- risks.push(
140
- riskForMatch(
141
- sql,
142
- match,
143
- "add-constraint",
144
- "enforces a constraint on an existing table before a separate validation step",
145
- ),
146
- )
147
- }
148
- }
149
-
150
- for (const match of maskedSql.matchAll(
151
- /\bcreate\s+unique\s+index(?:\s+concurrently)?(?:\s+if\s+not\s+exists)?\s+(?:"[^"]+"|\w+)\s+on\s+(?<table>(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))?)/giu,
152
- )) {
153
- if (!createdTables.has(normalizeIdentifier(match.groups?.table))) {
154
- risks.push(
155
- riskForMatch(
156
- sql,
157
- match,
158
- "create-unique-index",
159
- "adds uniqueness to existing data before a separate dedupe and validation step",
160
- ),
161
- )
162
- }
163
- }
164
-
165
- return risks.sort((left, right) => left.line - right.line || left.code.localeCompare(right.code))
166
- }
167
-
168
- export function formatMigrationScanFailures(results: MigrationScanResult[]) {
169
- return results
170
- .flatMap((result) =>
171
- result.risks.map(
172
- (risk) =>
173
- `${result.migration.relativePath}:${risk.line} ${risk.code}: ${risk.reason} (${risk.match.trim()})`,
174
- ),
175
- )
176
- .join("\n")
177
- }
178
-
179
- export function formatMigrationList(migrations: MigrationFile[]) {
180
- if (migrations.length === 0) {
181
- return "No migration files found."
182
- }
183
-
184
- return migrations.map((migration) => `- ${migration.relativePath}`).join("\n")
185
- }
186
-
187
- function collectCreatedTables(sql: string) {
188
- const tables = new Set<string>()
189
-
190
- for (const match of sql.matchAll(
191
- /\bcreate\s+(?:temporary\s+|temp\s+)?table\s+(?:if\s+not\s+exists\s+)?(?<table>(?:"[^"]+"|\w+)(?:\s*\.\s*(?:"[^"]+"|\w+))?)/giu,
192
- )) {
193
- tables.add(normalizeIdentifier(match.groups?.table))
194
- }
195
-
196
- return tables
197
- }
198
-
199
- async function collectDrizzleMigrationFiles(migrationsPath: string, cwd: string) {
200
- const entries = await readDirectoryOrEmpty(migrationsPath)
201
- const migrations: MigrationFile[] = []
202
-
203
- for (const entry of entries) {
204
- const entryPath = join(migrationsPath, entry.name)
205
-
206
- if (entry.isDirectory()) {
207
- const migrationPath = join(entryPath, "migration.sql")
208
-
209
- if (await fileExists(migrationPath)) {
210
- migrations.push(migrationFileFromPath(migrationPath, cwd, entry.name))
211
- }
212
-
213
- continue
214
- }
215
-
216
- if (entry.isFile() && entry.name.endsWith(".sql")) {
217
- migrations.push(migrationFileFromPath(entryPath, cwd, entry.name.replace(/\.sql$/u, "")))
218
- }
219
- }
220
-
221
- return migrations.sort((left, right) => left.id.localeCompare(right.id))
222
- }
223
-
224
- async function collectFlatSqlFiles(directory: string, cwd: string) {
225
- const entries = await readDirectoryOrEmpty(directory)
226
- const migrations = entries
227
- .filter((entry) => entry.isFile() && entry.name.endsWith(".sql"))
228
- .map((entry) =>
229
- migrationFileFromPath(join(directory, entry.name), cwd, entry.name.replace(/\.sql$/u, "")),
230
- )
231
-
232
- return migrations.sort((left, right) => left.id.localeCompare(right.id))
233
- }
234
-
235
- async function readDirectoryOrEmpty(directory: string) {
236
- try {
237
- return await readdir(directory, { withFileTypes: true })
238
- } catch (error) {
239
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
240
- return []
241
- }
242
-
243
- throw error
244
- }
245
- }
246
-
247
- async function fileExists(path: string) {
248
- try {
249
- await access(path)
250
- return true
251
- } catch (error) {
252
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
253
- return false
254
- }
255
-
256
- throw error
257
- }
258
- }
259
-
260
- function migrationFileFromPath(
261
- path: string,
262
- cwd: string,
263
- id = basename(path, ".sql"),
264
- ): MigrationFile {
265
- return {
266
- id,
267
- path,
268
- relativePath: relative(cwd, path),
269
- }
270
- }
271
-
272
- function maskSql(sql: string) {
273
- return sql
274
- .replace(/--[^\n\r]*/gu, blank)
275
- .replace(/\/\*[\s\S]*?\*\//gu, blank)
276
- .replace(/\$([A-Za-z_][A-Za-z0-9_]*)?\$[\s\S]*?\$\1\$/gu, blank)
277
- .replace(/'(?:''|[^'])*'/gu, blank)
278
- }
279
-
280
- function blank(match: string) {
281
- return match.replace(/[^\n\r]/gu, " ")
282
- }
283
-
284
- function normalizeIdentifier(identifier: string | undefined) {
285
- return (
286
- (identifier ?? "")
287
- .split(".")
288
- .map((part) => part.trim().replace(/^"|"$/gu, "").toLowerCase())
289
- .at(-1) ?? ""
290
- )
291
- }
292
-
293
- function riskForMatch(
294
- sql: string,
295
- match: RegExpMatchArray,
296
- code: string,
297
- reason: string,
298
- ): MigrationRisk {
299
- return {
300
- code,
301
- line: lineForIndex(sql, match.index ?? 0),
302
- match: sql.slice(match.index ?? 0, (match.index ?? 0) + match[0].length),
303
- reason,
304
- }
305
- }
306
-
307
- function lineForIndex(value: string, index: number) {
308
- let line = 1
309
-
310
- for (let offset = 0; offset < index; offset += 1) {
311
- if (value[offset] === "\n") {
312
- line += 1
313
- }
314
- }
315
-
316
- return line
317
- }
@@ -1,93 +0,0 @@
1
- import { describe, expect, it } from "vitest"
2
-
3
- import type { CoreframeDatabaseClient, CoreframeDatabaseDialect } from "../db/client.ts"
4
- import type { MigrationFile } from "./migrations.ts"
5
- import {
6
- appliedMigrationsQuery,
7
- filterPendingMigrations,
8
- readAppliedMigrationNames,
9
- } from "./pending-migrations.ts"
10
-
11
- describe("filterPendingMigrations", () => {
12
- it("keeps local migrations that are absent from the database journal", () => {
13
- const migrations: MigrationFile[] = [
14
- {
15
- id: "20260703120000_first",
16
- path: "/repo/drizzle/20260703120000_first/migration.sql",
17
- relativePath: "drizzle/20260703120000_first/migration.sql",
18
- },
19
- {
20
- id: "20260703130000_second",
21
- path: "/repo/drizzle/20260703130000_second/migration.sql",
22
- relativePath: "drizzle/20260703130000_second/migration.sql",
23
- },
24
- ]
25
-
26
- expect(filterPendingMigrations(migrations, ["20260703120000_first"])).toEqual([migrations[1]])
27
- })
28
- })
29
-
30
- describe("readAppliedMigrationNames", () => {
31
- it("reads the Postgres Drizzle journal", async () => {
32
- const database = createTestDatabase("postgres", [{ name: "20260703120000_first" }])
33
-
34
- await expect(readAppliedMigrationNames(database)).resolves.toEqual(
35
- new Set(["20260703120000_first"]),
36
- )
37
- expect(database.statements).toEqual([
38
- "select name from drizzle.__drizzle_migrations where name is not null",
39
- ])
40
- })
41
-
42
- it("reads the SQLite Drizzle journal", async () => {
43
- const database = createTestDatabase("sqlite", [{ name: "20260703120000_first" }])
44
-
45
- await expect(readAppliedMigrationNames(database)).resolves.toEqual(
46
- new Set(["20260703120000_first"]),
47
- )
48
- expect(database.statements).toEqual([
49
- "select name from __drizzle_migrations where name is not null",
50
- ])
51
- })
52
-
53
- it("treats a missing journal table as no applied migrations", async () => {
54
- const database = createTestDatabase("sqlite", [], new Error("SQLITE_ERROR: no such table"))
55
-
56
- await expect(readAppliedMigrationNames(database)).resolves.toEqual(new Set())
57
- })
58
- })
59
-
60
- describe("appliedMigrationsQuery", () => {
61
- it("uses dialect-specific Drizzle journal tables", () => {
62
- expect(appliedMigrationsQuery("postgres")).toBe(
63
- "select name from drizzle.__drizzle_migrations where name is not null",
64
- )
65
- expect(appliedMigrationsQuery("sqlite")).toBe(
66
- "select name from __drizzle_migrations where name is not null",
67
- )
68
- })
69
- })
70
-
71
- function createTestDatabase(
72
- dialect: CoreframeDatabaseDialect,
73
- rows: Array<{ name: string | null }>,
74
- error?: Error,
75
- ): CoreframeDatabaseClient & { statements: string[] } {
76
- const statements: string[] = []
77
-
78
- return {
79
- dialect,
80
- statements,
81
- async execute<Row extends Record<string, unknown> = Record<string, unknown>>(
82
- statement: string,
83
- ) {
84
- statements.push(statement)
85
-
86
- if (error) {
87
- throw error
88
- }
89
-
90
- return { rows: rows as unknown as Row[] }
91
- },
92
- }
93
- }
@@ -1,45 +0,0 @@
1
- import type { CoreframeDatabaseClient, CoreframeDatabaseDialect } from "../db/client.ts"
2
- import type { MigrationFile } from "./migrations.ts"
3
-
4
- export function filterPendingMigrations(
5
- migrations: MigrationFile[],
6
- appliedNames: Iterable<string>,
7
- ) {
8
- const applied = new Set(appliedNames)
9
-
10
- return migrations.filter((migration) => !applied.has(migration.id))
11
- }
12
-
13
- export async function readAppliedMigrationNames(database: CoreframeDatabaseClient) {
14
- try {
15
- const result = await database.execute<{ name: string | null }>(
16
- appliedMigrationsQuery(database.dialect),
17
- )
18
-
19
- return new Set(result.rows.flatMap((row) => (row.name ? [row.name] : [])))
20
- } catch (error) {
21
- if (isMissingMigrationTableError(error)) {
22
- return new Set<string>()
23
- }
24
-
25
- throw error
26
- }
27
- }
28
-
29
- export function appliedMigrationsQuery(dialect: CoreframeDatabaseDialect) {
30
- switch (dialect) {
31
- case "postgres":
32
- return "select name from drizzle.__drizzle_migrations where name is not null"
33
- case "sqlite":
34
- return "select name from __drizzle_migrations where name is not null"
35
- }
36
- }
37
-
38
- function isMissingMigrationTableError(error: unknown) {
39
- return (
40
- error instanceof Error &&
41
- (("code" in error && (error.code === "42P01" || error.code === "3F000")) ||
42
- error.message.includes("no such table") ||
43
- error.message.includes("no such schema"))
44
- )
45
- }
@@ -1,196 +0,0 @@
1
- import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises"
2
- import { tmpdir } from "node:os"
3
- import { join } from "node:path"
4
-
5
- import { describe, expect, it } from "vitest"
6
-
7
- import {
8
- createTauriReleaseUpload,
9
- createTauriUpdateManifest,
10
- resolveTauriReleasePlan,
11
- restoreTauriReleaseMetadata,
12
- writeTauriReleaseMetadata,
13
- } from "./tauri-release.ts"
14
-
15
- describe("resolveTauriReleasePlan", () => {
16
- it("disables all Tauri platforms by default", () => {
17
- const plan = resolveTauriReleasePlan({
18
- args: {
19
- tauriLocalPlatforms: [],
20
- tauriWorkflowPlatforms: [],
21
- },
22
- env: {},
23
- })
24
-
25
- expect(plan.enabled).toBe(false)
26
- expect(plan.localPlatforms).toEqual([])
27
- expect(plan.workflowPlatforms).toEqual([])
28
- })
29
-
30
- it("combines CLI and env platform selections", () => {
31
- const plan = resolveTauriReleasePlan({
32
- args: {
33
- tauriLocalPlatforms: ["macos"],
34
- tauriWorkflowPlatforms: ["windows"],
35
- },
36
- config: {
37
- localPlatforms: ["android"],
38
- workflowPlatforms: ["linux"],
39
- },
40
- env: {
41
- B2_APPLICATION_KEY: "key",
42
- B2_APPLICATION_KEY_ID: "key-id",
43
- B2_BUCKET: "bucket",
44
- TAURI_RELEASE_LOCAL_PLATFORMS: "ios",
45
- TAURI_RELEASE_PUBLIC_BASE_URL: "https://downloads.example.com",
46
- TAURI_SIGNING_PRIVATE_KEY: "private-key",
47
- },
48
- })
49
-
50
- expect(plan.enabled).toBe(true)
51
- expect(plan.localPlatforms).toEqual(["android", "ios", "macos"])
52
- expect(plan.workflowPlatforms).toEqual(["linux", "windows"])
53
- expect(plan.publicBaseUrl).toBe("https://downloads.example.com")
54
- })
55
-
56
- it("rejects selecting the same platform locally and through workflow", () => {
57
- expect(() =>
58
- resolveTauriReleasePlan({
59
- args: {
60
- tauriLocalPlatforms: ["linux"],
61
- tauriWorkflowPlatforms: ["linux"],
62
- },
63
- env: {},
64
- }),
65
- ).toThrow("either locally or through the workflow")
66
- })
67
- })
68
-
69
- describe("writeTauriReleaseMetadata", () => {
70
- it("updates and restores Tauri release metadata", async () => {
71
- const root = await mkdtemp(join(tmpdir(), "coreframe-tauri-metadata-"))
72
- await mkdir(join(root, "tauri/src-tauri"), { recursive: true })
73
- const tauriConfigPath = join(root, "tauri/src-tauri/tauri.conf.json")
74
- const cargoManifestPath = join(root, "tauri/src-tauri/Cargo.toml")
75
- const cargoLockPath = join(root, "tauri/src-tauri/Cargo.lock")
76
-
77
- await writeFile(
78
- tauriConfigPath,
79
- `${JSON.stringify({ bundle: { active: true }, version: "0.0.0" }, null, 2)}\n`,
80
- )
81
- await writeFile(
82
- cargoManifestPath,
83
- '[package]\nname = "coreframe-tauri"\nversion = "0.0.0"\nedition = "2024"\n',
84
- )
85
- await writeFile(cargoLockPath, '[[package]]\nname = "coreframe-tauri"\nversion = "0.0.0"\n')
86
-
87
- const plan = resolveTauriReleasePlan({
88
- args: {
89
- tauriLocalPlatforms: ["macos"],
90
- tauriWorkflowPlatforms: [],
91
- },
92
- env: {},
93
- })
94
- const snapshot = await writeTauriReleaseMetadata(plan, "0.20260705.0", root)
95
-
96
- expect(JSON.parse(await readFile(tauriConfigPath, "utf8"))).toMatchObject({
97
- bundle: { active: true, createUpdaterArtifacts: true },
98
- version: "0.20260705.0",
99
- })
100
- expect(await readFile(cargoManifestPath, "utf8")).toContain('version = "0.20260705.0"')
101
- expect(await readFile(cargoLockPath, "utf8")).toContain('version = "0.20260705.0"')
102
-
103
- await restoreTauriReleaseMetadata(snapshot)
104
-
105
- expect(JSON.parse(await readFile(tauriConfigPath, "utf8")).version).toBe("0.0.0")
106
- expect(await readFile(cargoManifestPath, "utf8")).toContain('version = "0.0.0"')
107
- expect(await readFile(cargoLockPath, "utf8")).toContain('version = "0.0.0"')
108
- })
109
- })
110
-
111
- describe("createTauriReleaseUpload", () => {
112
- it("creates a latest manifest and upload tree from updater artifact pairs", async () => {
113
- const root = await mkdtemp(join(tmpdir(), "coreframe-tauri-upload-"))
114
- const originalCwd = process.cwd()
115
- const artifactDir = join(
116
- root,
117
- "tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage",
118
- )
119
-
120
- await mkdir(artifactDir, { recursive: true })
121
- await writeFile(join(artifactDir, "coreframe_0.20260705.0.AppImage.tar.gz"), "artifact")
122
- await writeFile(join(artifactDir, "coreframe_0.20260705.0.AppImage.tar.gz.sig"), "signature\n")
123
-
124
- try {
125
- process.chdir(root)
126
- const plan = resolveTauriReleasePlan({
127
- args: {
128
- tauriLocalPlatforms: ["linux"],
129
- tauriWorkflowPlatforms: [],
130
- },
131
- env: {
132
- B2_APPLICATION_KEY: "key",
133
- B2_APPLICATION_KEY_ID: "key-id",
134
- B2_BUCKET: "bucket",
135
- TAURI_RELEASE_PUBLIC_BASE_URL: "https://downloads.example.com",
136
- TAURI_SIGNING_PRIVATE_KEY: "private-key",
137
- },
138
- })
139
- const release = await createTauriReleaseUpload({ plan, version: "0.20260705.0" })
140
- const manifest = JSON.parse(
141
- await readFile(join(release.uploadDir, release.manifestPath), "utf8"),
142
- )
143
-
144
- expect(release.manifestPath).toBe("latest.json")
145
- expect(manifest).toMatchObject({
146
- platforms: {
147
- "linux-x86_64": {
148
- signature: "signature",
149
- url: "https://downloads.example.com/releases/0.20260705.0/linux-x86_64/coreframe_0.20260705.0.AppImage.tar.gz",
150
- },
151
- },
152
- version: "0.20260705.0",
153
- })
154
- await expect(
155
- readFile(
156
- join(
157
- release.uploadDir,
158
- "releases/0.20260705.0/linux-x86_64/coreframe_0.20260705.0.AppImage.tar.gz",
159
- ),
160
- "utf8",
161
- ),
162
- ).resolves.toBe("artifact")
163
- } finally {
164
- process.chdir(originalCwd)
165
- }
166
- })
167
- })
168
-
169
- describe("createTauriUpdateManifest", () => {
170
- it("uses Tauri static updater manifest keys", () => {
171
- expect(
172
- createTauriUpdateManifest({
173
- artifacts: [
174
- {
175
- signature: "signed",
176
- updatePlatform: "darwin-aarch64",
177
- url: "https://downloads.example.com/app.tar.gz",
178
- },
179
- ],
180
- notes: "Release notes",
181
- pubDate: new Date("2026-07-05T12:00:00.000Z"),
182
- version: "0.20260705.0",
183
- }),
184
- ).toEqual({
185
- notes: "Release notes",
186
- platforms: {
187
- "darwin-aarch64": {
188
- signature: "signed",
189
- url: "https://downloads.example.com/app.tar.gz",
190
- },
191
- },
192
- pub_date: "2026-07-05T12:00:00.000Z",
193
- version: "0.20260705.0",
194
- })
195
- })
196
- })