@coreframe/scripts 0.0.0 → 0.1.0

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/bin/coreframe.js +35 -0
  2. package/config.test.ts +15 -0
  3. package/config.ts +9 -0
  4. package/coreframe.ts +272 -0
  5. package/db/client.ts +72 -0
  6. package/db/config.ts +41 -0
  7. package/db/drizzle-kit.test.ts +27 -0
  8. package/db/drizzle-kit.ts +39 -0
  9. package/db/ensure.ts +67 -0
  10. package/db/migrate-held.test.ts +81 -0
  11. package/db/migrate-held.ts +166 -0
  12. package/db/migrate.ts +39 -0
  13. package/db/project.ts +28 -0
  14. package/db/reset.ts +24 -0
  15. package/db/seed.ts +38 -0
  16. package/db/setup.ts +18 -0
  17. package/deploy/check-migrations.ts +31 -0
  18. package/deploy/checks.ts +38 -0
  19. package/deploy/config.test.ts +24 -0
  20. package/deploy/config.ts +68 -0
  21. package/deploy/migrations.test.ts +110 -0
  22. package/deploy/migrations.ts +317 -0
  23. package/deploy/pending-migrations.test.ts +93 -0
  24. package/deploy/pending-migrations.ts +45 -0
  25. package/deploy/tauri-release.test.ts +196 -0
  26. package/deploy/tauri-release.ts +816 -0
  27. package/deploy/version.test.ts +29 -0
  28. package/deploy/version.ts +65 -0
  29. package/deploy.test.ts +101 -0
  30. package/deploy.ts +346 -0
  31. package/dev.test.ts +153 -0
  32. package/dev.ts +155 -0
  33. package/dist/config.d.ts +5 -0
  34. package/dist/config.js +3 -0
  35. package/dist/coreframe.d.ts +280 -0
  36. package/dist/coreframe.js +240 -0
  37. package/dist/db/client.d.ts +22 -0
  38. package/dist/db/client.js +29 -0
  39. package/dist/db/config.d.ts +21 -0
  40. package/dist/db/config.js +32 -0
  41. package/dist/db/drizzle-kit.d.ts +8 -0
  42. package/dist/db/drizzle-kit.js +22 -0
  43. package/dist/db/ensure.d.ts +2 -0
  44. package/dist/db/ensure.js +40 -0
  45. package/dist/db/migrate-held.d.ts +12 -0
  46. package/dist/db/migrate-held.js +119 -0
  47. package/dist/db/migrate.d.ts +2 -0
  48. package/dist/db/migrate.js +25 -0
  49. package/dist/db/project.d.ts +8 -0
  50. package/dist/db/project.js +9 -0
  51. package/dist/db/reset.d.ts +2 -0
  52. package/dist/db/reset.js +19 -0
  53. package/dist/db/seed.d.ts +3 -0
  54. package/dist/db/seed.js +23 -0
  55. package/dist/db/setup.d.ts +3 -0
  56. package/dist/db/setup.js +15 -0
  57. package/dist/deploy/check-migrations.d.ts +2 -0
  58. package/dist/deploy/check-migrations.js +18 -0
  59. package/dist/deploy/checks.d.ts +2 -0
  60. package/dist/deploy/checks.js +27 -0
  61. package/dist/deploy/config.d.ts +50 -0
  62. package/dist/deploy/config.js +9 -0
  63. package/dist/deploy/migrations.d.ts +21 -0
  64. package/dist/deploy/migrations.js +203 -0
  65. package/dist/deploy/pending-migrations.d.ts +5 -0
  66. package/dist/deploy/pending-migrations.js +30 -0
  67. package/dist/deploy/tauri-release.d.ts +85 -0
  68. package/dist/deploy/tauri-release.js +512 -0
  69. package/dist/deploy/version.d.ts +18 -0
  70. package/dist/deploy/version.js +41 -0
  71. package/dist/deploy.d.ts +17 -0
  72. package/dist/deploy.js +247 -0
  73. package/dist/dev.d.ts +13 -0
  74. package/dist/dev.js +96 -0
  75. package/dist/image-generator.d.ts +2 -0
  76. package/dist/image-generator.js +59 -0
  77. package/dist/project.d.ts +1 -0
  78. package/dist/project.js +13 -0
  79. package/dist/typecheck.d.ts +2 -0
  80. package/dist/typecheck.js +45 -0
  81. package/dist/upgrade/check-skill-current.d.ts +6 -0
  82. package/dist/upgrade/check-skill-current.js +88 -0
  83. package/dist/upgrade/ensure-template-remote.d.ts +6 -0
  84. package/dist/upgrade/ensure-template-remote.js +57 -0
  85. package/dist/upgrade/fast-forward-template-files.d.ts +7 -0
  86. package/dist/upgrade/fast-forward-template-files.js +280 -0
  87. package/image-generator.ts +84 -0
  88. package/package.json +48 -8
  89. package/project.ts +6 -0
  90. package/tsconfig.build.json +12 -0
  91. package/tsconfig.json +20 -0
  92. package/typecheck.ts +55 -0
  93. package/upgrade/check-skill-current.ts +112 -0
  94. package/upgrade/ensure-template-remote.ts +79 -0
  95. package/upgrade/fast-forward-template-files.ts +408 -0
  96. package/readme.md +0 -1
@@ -0,0 +1,22 @@
1
+ import { type SQL } from "drizzle-orm";
2
+ export type CoreframeDatabaseDialect = "postgres" | "sqlite";
3
+ export type CoreframeQueryResult<Row extends Record<string, unknown> = Record<string, unknown>> = {
4
+ rowCount?: number | null;
5
+ rows: Row[];
6
+ };
7
+ export type CoreframeDatabaseClient = {
8
+ dialect: CoreframeDatabaseDialect;
9
+ execute<Row extends Record<string, unknown> = Record<string, unknown>>(statement: string): Promise<CoreframeQueryResult<Row>>;
10
+ transaction?<Result>(run: (database: CoreframeDatabaseClient) => Promise<Result>): Promise<Result>;
11
+ close?(): Promise<void>;
12
+ };
13
+ export type CoreframeDrizzleExecutor = {
14
+ execute<Result = unknown>(query: SQL): Result | Promise<Result>;
15
+ };
16
+ export type CreateDrizzleDatabaseClientOptions = {
17
+ close?(): Promise<void>;
18
+ database: CoreframeDrizzleExecutor;
19
+ dialect: CoreframeDatabaseDialect;
20
+ };
21
+ export declare function createDrizzleDatabaseClient({ close, database, dialect, }: CreateDrizzleDatabaseClientOptions): CoreframeDatabaseClient;
22
+ export declare function normalizeQueryResult<Row extends Record<string, unknown> = Record<string, unknown>>(result: unknown): CoreframeQueryResult<Row>;
@@ -0,0 +1,29 @@
1
+ import { sql } from "drizzle-orm";
2
+ export function createDrizzleDatabaseClient({ close, database, dialect, }) {
3
+ return {
4
+ dialect,
5
+ async execute(statement) {
6
+ return normalizeQueryResult(await database.execute(sql.raw(statement)));
7
+ },
8
+ close,
9
+ };
10
+ }
11
+ export function normalizeQueryResult(result) {
12
+ if (Array.isArray(result)) {
13
+ return {
14
+ rows: result,
15
+ };
16
+ }
17
+ if (isQueryResultLike(result)) {
18
+ return {
19
+ rowCount: typeof result.rowCount === "number" ? result.rowCount : null,
20
+ rows: result.rows,
21
+ };
22
+ }
23
+ return {
24
+ rows: [],
25
+ };
26
+ }
27
+ function isQueryResultLike(result) {
28
+ return (typeof result === "object" && result !== null && "rows" in result && Array.isArray(result.rows));
29
+ }
@@ -0,0 +1,21 @@
1
+ export declare const LOCAL_DB_DIR: string;
2
+ export declare const MIGRATIONS_FOLDER = "drizzle";
3
+ export declare const HELD_MIGRATIONS_FOLDER = "drizzle-hold";
4
+ export declare const NOW: Date;
5
+ export declare const SEED_IDS: {
6
+ readonly adminUser: "00000000-0000-4000-8000-000000000001";
7
+ readonly viewerUser: "00000000-0000-4000-8000-000000000002";
8
+ readonly inactiveUser: "00000000-0000-4000-8000-000000000003";
9
+ readonly unicodeUser: "00000000-0000-4000-8000-000000000004";
10
+ readonly longNameUser: "00000000-0000-4000-8000-000000000005";
11
+ };
12
+ export declare const SEED_EMAILS: {
13
+ readonly adminUser: "admin@example.com";
14
+ readonly viewerUser: "viewer@example.com";
15
+ readonly inactiveUser: "inactive@example.com";
16
+ readonly unicodeUser: "unicode@example.com";
17
+ readonly longNameUser: "long-name@example.com";
18
+ };
19
+ export type SeedProfile = "small" | "large" | "demo" | "test";
20
+ export declare function getSeedProfile(argv?: string[]): SeedProfile;
21
+ export declare function generatedUserId(index: number): string;
@@ -0,0 +1,32 @@
1
+ export const LOCAL_DB_DIR = process.env.PGLITE_DATA_DIR ?? ".pglite";
2
+ export const MIGRATIONS_FOLDER = "drizzle";
3
+ export const HELD_MIGRATIONS_FOLDER = "drizzle-hold";
4
+ export const NOW = new Date("2026-01-15T12:00:00.000Z");
5
+ export const SEED_IDS = {
6
+ adminUser: "00000000-0000-4000-8000-000000000001",
7
+ viewerUser: "00000000-0000-4000-8000-000000000002",
8
+ inactiveUser: "00000000-0000-4000-8000-000000000003",
9
+ unicodeUser: "00000000-0000-4000-8000-000000000004",
10
+ longNameUser: "00000000-0000-4000-8000-000000000005",
11
+ };
12
+ export const SEED_EMAILS = {
13
+ adminUser: "admin@example.com",
14
+ viewerUser: "viewer@example.com",
15
+ inactiveUser: "inactive@example.com",
16
+ unicodeUser: "unicode@example.com",
17
+ longNameUser: "long-name@example.com",
18
+ };
19
+ export function getSeedProfile(argv = process.argv.slice(2)) {
20
+ if (argv.includes("--large")) {
21
+ return "large";
22
+ }
23
+ const profileArg = argv.find((arg) => arg.startsWith("--profile="));
24
+ const profile = profileArg?.slice("--profile=".length) ?? "small";
25
+ if (profile === "small" || profile === "large" || profile === "demo" || profile === "test") {
26
+ return profile;
27
+ }
28
+ throw new Error(`Unknown seed profile "${profile}". Expected small, large, demo, or test.`);
29
+ }
30
+ export function generatedUserId(index) {
31
+ return `00000000-0000-4000-8000-${String(1000 + index).padStart(12, "0")}`;
32
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
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
+ export declare function runDrizzleKitCommand(command: DrizzleKitCommand, args?: string[], spawn?: Spawn): Promise<void>;
8
+ export {};
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ import nanoSpawn from "nano-spawn";
5
+ const nodeModulesBinPath = path.resolve("node_modules", ".bin");
6
+ const drizzleKitCommand = process.platform === "win32" ? "drizzle-kit.cmd" : "drizzle-kit";
7
+ const pathEnvValue = process.env.PATH ?? process.env.Path ?? "";
8
+ export async function runDrizzleKitCommand(command, args = [], spawn = nanoSpawn) {
9
+ await spawn(drizzleKitCommand, [command, ...args], {
10
+ env: {
11
+ ...process.env,
12
+ PATH: nodeModulesBinPath + path.delimiter + pathEnvValue,
13
+ },
14
+ stdio: "inherit",
15
+ });
16
+ }
17
+ if (import.meta.main) {
18
+ runDrizzleKitCommand(process.argv[2], process.argv.slice(3)).catch((error) => {
19
+ console.error(error);
20
+ process.exit(1);
21
+ });
22
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function ensureLocalDb(): Promise<void>;
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { eq } from "drizzle-orm";
3
+ import { importProjectModule } from "../project.js";
4
+ import { LOCAL_DB_DIR, SEED_EMAILS, SEED_IDS } from "./config.js";
5
+ import { setupLocalDb } from "./setup.js";
6
+ async function hasSeededAdminUser() {
7
+ const [{ createLocalDb }, { users }] = await Promise.all([
8
+ importProjectModule("src/db/local.ts"),
9
+ importProjectModule("src/db/schema.ts"),
10
+ ]);
11
+ const db = createLocalDb(LOCAL_DB_DIR);
12
+ try {
13
+ const [adminUser] = await db
14
+ .select({ id: users.id })
15
+ .from(users)
16
+ .where(eq(users.email, SEED_EMAILS.adminUser))
17
+ .limit(1);
18
+ return adminUser?.id === SEED_IDS.adminUser;
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ finally {
24
+ await db.$client.close();
25
+ }
26
+ }
27
+ export async function ensureLocalDb() {
28
+ if (await hasSeededAdminUser()) {
29
+ console.log("Local database already set up");
30
+ return;
31
+ }
32
+ console.log("Local database is missing or unseeded; running setup");
33
+ await setupLocalDb();
34
+ }
35
+ if (import.meta.main) {
36
+ ensureLocalDb().catch((error) => {
37
+ console.error(error);
38
+ process.exit(1);
39
+ });
40
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import type { CoreframeDatabaseClient, CoreframeDatabaseDialect } from "./client.ts";
3
+ type MigrateHeldOptions = {
4
+ argv?: string[];
5
+ database?: CoreframeDatabaseClient;
6
+ env?: NodeJS.ProcessEnv;
7
+ };
8
+ export declare function migrateHeldMigrations({ argv, database, env, }?: MigrateHeldOptions): Promise<void>;
9
+ export declare function validateHeldMigrationHeader(sql: string, relativePath: string): void;
10
+ export declare function heldMigrationSelectQuery(dialect: CoreframeDatabaseDialect, id: string): string;
11
+ export declare function heldMigrationInsertQuery(dialect: CoreframeDatabaseDialect, id: string, sha256: string): string;
12
+ export {};
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from "node:crypto";
3
+ import { readFile } from "node:fs/promises";
4
+ import { collectHeldMigrationFiles, formatMigrationList } from "../deploy/migrations.js";
5
+ import { createProjectDeployDatabase } from "./project.js";
6
+ const CONFIRM_FLAG = "--confirm=run-held-migrations";
7
+ const HELD_HEADER_FIELDS = ["-- Held migration:", "-- Safe after:", "-- Verification:"];
8
+ export async function migrateHeldMigrations({ argv = process.argv.slice(2), database, env = process.env, } = {}) {
9
+ const migrations = await collectHeldMigrationFiles();
10
+ console.log("Held migration files:");
11
+ console.log(formatMigrationList(migrations));
12
+ if (!argv.includes(CONFIRM_FLAG)) {
13
+ throw new Error(`Held migrations require explicit confirmation. Re-run with ${CONFIRM_FLAG}.`);
14
+ }
15
+ if (migrations.length === 0) {
16
+ console.log("No held migrations to run.");
17
+ return;
18
+ }
19
+ const activeDatabase = database ?? (await createProjectDeployDatabase({ env }));
20
+ try {
21
+ await ensureHeldMigrationJournal(activeDatabase);
22
+ for (const migration of migrations) {
23
+ const sql = await readFile(migration.path, "utf8");
24
+ validateHeldMigrationHeader(sql, migration.relativePath);
25
+ const sha256 = hashSql(sql);
26
+ const applied = await activeDatabase.execute(heldMigrationSelectQuery(activeDatabase.dialect, migration.id));
27
+ if (applied.rows.length === 1) {
28
+ if (applied.rows[0]?.sha256 !== sha256) {
29
+ throw new Error(`Held migration ${migration.relativePath} changed after it was applied.`);
30
+ }
31
+ console.log(`Skipping already-applied held migration ${migration.relativePath}`);
32
+ continue;
33
+ }
34
+ console.log(`Applying held migration ${migration.relativePath}`);
35
+ await runMigrationTransaction(activeDatabase, async (transactionDatabase) => {
36
+ await transactionDatabase.execute(sql);
37
+ await transactionDatabase.execute(heldMigrationInsertQuery(activeDatabase.dialect, migration.id, sha256));
38
+ });
39
+ }
40
+ }
41
+ finally {
42
+ if (!database) {
43
+ await activeDatabase.close?.();
44
+ }
45
+ }
46
+ }
47
+ export function validateHeldMigrationHeader(sql, relativePath) {
48
+ const missing = HELD_HEADER_FIELDS.filter((field) => !sql.includes(field));
49
+ if (missing.length > 0) {
50
+ throw new Error(`${relativePath} is missing held migration header fields: ${missing.join(", ")}`);
51
+ }
52
+ }
53
+ function hashSql(sql) {
54
+ return createHash("sha256").update(sql).digest("hex");
55
+ }
56
+ async function ensureHeldMigrationJournal(database) {
57
+ for (const statement of heldMigrationJournalStatements(database.dialect)) {
58
+ await database.execute(statement);
59
+ }
60
+ }
61
+ function heldMigrationJournalStatements(dialect) {
62
+ switch (dialect) {
63
+ case "postgres":
64
+ return [
65
+ "create schema if not exists drizzle",
66
+ `create table if not exists drizzle.__held_migrations (
67
+ id text primary key,
68
+ sha256 text not null,
69
+ applied_at timestamptz not null default now()
70
+ )`,
71
+ ];
72
+ case "sqlite":
73
+ return [
74
+ `create table if not exists __held_migrations (
75
+ id text primary key,
76
+ sha256 text not null,
77
+ applied_at text not null default current_timestamp
78
+ )`,
79
+ ];
80
+ }
81
+ }
82
+ export function heldMigrationSelectQuery(dialect, id) {
83
+ return `select sha256 from ${heldMigrationJournalTable(dialect)} where id = ${sqlString(id)}`;
84
+ }
85
+ export function heldMigrationInsertQuery(dialect, id, sha256) {
86
+ return `insert into ${heldMigrationJournalTable(dialect)} (id, sha256) values (${sqlString(id)}, ${sqlString(sha256)})`;
87
+ }
88
+ function heldMigrationJournalTable(dialect) {
89
+ switch (dialect) {
90
+ case "postgres":
91
+ return "drizzle.__held_migrations";
92
+ case "sqlite":
93
+ return "__held_migrations";
94
+ }
95
+ }
96
+ async function runMigrationTransaction(database, run) {
97
+ if (database.transaction) {
98
+ await database.transaction(run);
99
+ return;
100
+ }
101
+ await database.execute("begin");
102
+ try {
103
+ await run(database);
104
+ await database.execute("commit");
105
+ }
106
+ catch (error) {
107
+ await database.execute("rollback");
108
+ throw error;
109
+ }
110
+ }
111
+ function sqlString(value) {
112
+ return `'${value.replaceAll("'", "''")}'`;
113
+ }
114
+ if (import.meta.main) {
115
+ migrateHeldMigrations().catch((error) => {
116
+ console.error(error);
117
+ process.exit(1);
118
+ });
119
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function migrateLocalDb(): Promise<void>;
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { access } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { migrate } from "drizzle-orm/pglite/migrator";
5
+ import { importProjectModule } from "../project.js";
6
+ import { LOCAL_DB_DIR, MIGRATIONS_FOLDER } from "./config.js";
7
+ const migrationsFolder = resolve(MIGRATIONS_FOLDER);
8
+ export async function migrateLocalDb() {
9
+ await access(migrationsFolder);
10
+ const { createLocalDb } = await importProjectModule("src/db/local.ts");
11
+ const db = createLocalDb(LOCAL_DB_DIR);
12
+ try {
13
+ await migrate(db, { migrationsFolder });
14
+ console.log(`Applied local migrations from ${migrationsFolder}`);
15
+ }
16
+ finally {
17
+ await db.$client.close();
18
+ }
19
+ }
20
+ if (import.meta.main) {
21
+ migrateLocalDb().catch((error) => {
22
+ console.error(error);
23
+ process.exit(1);
24
+ });
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { CoreframeDatabaseClient } from "./client.ts";
2
+ export type CreateProjectDatabaseOptions = {
3
+ env?: NodeJS.ProcessEnv;
4
+ };
5
+ export type ProjectDatabaseModule = {
6
+ createDeployDatabase?(options?: CreateProjectDatabaseOptions): CoreframeDatabaseClient | Promise<CoreframeDatabaseClient>;
7
+ };
8
+ export declare function createProjectDeployDatabase(options?: CreateProjectDatabaseOptions): Promise<CoreframeDatabaseClient>;
@@ -0,0 +1,9 @@
1
+ import { importProjectModule } from "../project.js";
2
+ const projectDatabaseConfigPath = "scripts/coreframe.db.ts";
3
+ export async function createProjectDeployDatabase(options = {}) {
4
+ const module = await importProjectModule(projectDatabaseConfigPath);
5
+ if (!module.createDeployDatabase) {
6
+ throw new Error(`${projectDatabaseConfigPath} must export createDeployDatabase() for Coreframe database workflows.`);
7
+ }
8
+ return module.createDeployDatabase(options);
9
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function resetLocalDb(): Promise<void>;
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { rm } from "node:fs/promises";
3
+ import { relative, resolve } from "node:path";
4
+ import { LOCAL_DB_DIR } from "./config.js";
5
+ export async function resetLocalDb() {
6
+ const dataDir = resolve(LOCAL_DB_DIR);
7
+ const relativeDataDir = relative(process.cwd(), dataDir);
8
+ if (relativeDataDir === "" || relativeDataDir.startsWith("..")) {
9
+ throw new Error(`Refusing to reset local database outside the project: ${dataDir}`);
10
+ }
11
+ await rm(dataDir, { force: true, recursive: true });
12
+ console.log(`Removed local PGlite data at ${dataDir}`);
13
+ }
14
+ if (import.meta.main) {
15
+ resetLocalDb().catch((error) => {
16
+ console.error(error);
17
+ process.exit(1);
18
+ });
19
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import type { SeedProfile } from "./config.ts";
3
+ export declare function seedLocalDb(profile?: SeedProfile): Promise<void>;
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { importProjectModule } from "../project.js";
3
+ import { getSeedProfile, LOCAL_DB_DIR } from "./config.js";
4
+ export async function seedLocalDb(profile = getSeedProfile()) {
5
+ const [{ createLocalDb }, { seedUsers }] = await Promise.all([
6
+ importProjectModule("src/db/local.ts"),
7
+ importProjectModule("scripts/db/seeds/users.ts"),
8
+ ]);
9
+ const db = createLocalDb(LOCAL_DB_DIR);
10
+ try {
11
+ await seedUsers(db, profile);
12
+ console.log(`Seeded local database with "${profile}" profile`);
13
+ }
14
+ finally {
15
+ await db.$client.close();
16
+ }
17
+ }
18
+ if (import.meta.main) {
19
+ seedLocalDb().catch((error) => {
20
+ console.error(error);
21
+ process.exit(1);
22
+ });
23
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import type { SeedProfile } from "./config.ts";
3
+ export declare function setupLocalDb(profile?: SeedProfile): Promise<void>;
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { migrateLocalDb } from "./migrate.js";
3
+ import { resetLocalDb } from "./reset.js";
4
+ import { seedLocalDb } from "./seed.js";
5
+ export async function setupLocalDb(profile = "small") {
6
+ await resetLocalDb();
7
+ await migrateLocalDb();
8
+ await seedLocalDb(profile);
9
+ }
10
+ if (import.meta.main) {
11
+ setupLocalDb().catch((error) => {
12
+ console.error(error);
13
+ process.exit(1);
14
+ });
15
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function checkDeployMigrations(): Promise<void>;
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { collectDeployMigrationFiles, formatMigrationList, formatMigrationScanFailures, scanDeployMigrationFiles, } from "./migrations.js";
3
+ export async function checkDeployMigrations() {
4
+ const migrations = await collectDeployMigrationFiles();
5
+ console.log("Deploy migration files:");
6
+ console.log(formatMigrationList(migrations));
7
+ const failures = await scanDeployMigrationFiles(migrations);
8
+ if (failures.length > 0) {
9
+ throw new Error(`Deploy migrations contain unsafe SQL. Move unsafe statements to held migrations.\n${formatMigrationScanFailures(failures)}`);
10
+ }
11
+ console.log("Deploy migrations passed safety checks.");
12
+ }
13
+ if (import.meta.main) {
14
+ checkDeployMigrations().catch((error) => {
15
+ console.error(error);
16
+ process.exit(1);
17
+ });
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { DeployContext, ResolvedDeployConfig } from "./config.ts";
2
+ export declare function runPostDeployChecks(config: ResolvedDeployConfig, context: DeployContext): Promise<void>;
@@ -0,0 +1,27 @@
1
+ export async function runPostDeployChecks(config, context) {
2
+ const productionUrl = context.productionUrl ?? config.productionUrl;
3
+ if (config.smokeChecks.length > 0 && !productionUrl) {
4
+ console.log("Skipping HTTP smoke checks. Set DEPLOY_URL or productionUrl in deploy config.");
5
+ }
6
+ for (const check of config.smokeChecks) {
7
+ if (!productionUrl && !check.url) {
8
+ continue;
9
+ }
10
+ const url = check.url ?? new URL(check.path ?? "/", productionUrl).toString();
11
+ const response = await fetch(url);
12
+ if (!statusMatches(response.status, check.expectedStatus ?? "2xx")) {
13
+ throw new Error(`${check.name} smoke check failed: ${url} returned ${response.status}.`);
14
+ }
15
+ console.log(`${check.name} smoke check passed: ${response.status} ${url}`);
16
+ }
17
+ await config.afterDeploy?.({ ...context, productionUrl });
18
+ }
19
+ function statusMatches(status, expected) {
20
+ if (expected === "2xx") {
21
+ return status >= 200 && status < 300;
22
+ }
23
+ if (Array.isArray(expected)) {
24
+ return expected.includes(status);
25
+ }
26
+ return status === expected;
27
+ }
@@ -0,0 +1,50 @@
1
+ export type DeployContext = {
2
+ environment: string;
3
+ productionUrl?: string;
4
+ releaseName: string;
5
+ version: string;
6
+ };
7
+ export declare const tauriReleasePlatforms: readonly ["macos", "windows", "linux", "ios", "android"];
8
+ export type TauriReleasePlatform = (typeof tauriReleasePlatforms)[number];
9
+ export type TauriReleasePlatformConfig = {
10
+ artifactGlobs?: string[];
11
+ buildArgs?: string[];
12
+ targetTriple?: string;
13
+ updatePlatform?: string;
14
+ };
15
+ export type TauriReleaseConfig = {
16
+ artifactDownloadDir?: string;
17
+ b2Bucket?: string;
18
+ latestManifestPath?: string;
19
+ localPlatforms?: TauriReleasePlatform[];
20
+ platform?: Partial<Record<TauriReleasePlatform, TauriReleasePlatformConfig>>;
21
+ publicBaseUrl?: string;
22
+ publicPathPrefix?: string;
23
+ releaseNotesPath?: string;
24
+ releasePath?: string;
25
+ releaseUploadDir?: string;
26
+ workflowPlatforms?: TauriReleasePlatform[];
27
+ workflowName?: string;
28
+ workflowPollIntervalMs?: number;
29
+ workflowRef?: string;
30
+ workflowTimeoutMs?: number;
31
+ };
32
+ export type DeploySmokeCheck = {
33
+ name: string;
34
+ path?: string;
35
+ url?: string;
36
+ expectedStatus?: "2xx" | number | number[];
37
+ };
38
+ export type DeployConfig = {
39
+ afterDeploy?: (context: DeployContext) => Promise<void> | void;
40
+ productionUrl?: string;
41
+ smokeChecks?: DeploySmokeCheck[];
42
+ tauriRelease?: TauriReleaseConfig;
43
+ };
44
+ export type ResolvedDeployConfig = {
45
+ afterDeploy?: (context: DeployContext) => Promise<void> | void;
46
+ productionUrl?: string;
47
+ smokeChecks: DeploySmokeCheck[];
48
+ tauriRelease: TauriReleaseConfig;
49
+ };
50
+ export declare function resolveDeployConfig(config: DeployConfig, env?: NodeJS.ProcessEnv): ResolvedDeployConfig;
@@ -0,0 +1,9 @@
1
+ export const tauriReleasePlatforms = ["macos", "windows", "linux", "ios", "android"];
2
+ export function resolveDeployConfig(config, env = process.env) {
3
+ return {
4
+ afterDeploy: config.afterDeploy,
5
+ productionUrl: config.productionUrl ?? env.DEPLOY_URL ?? env.PRODUCTION_URL,
6
+ smokeChecks: config.smokeChecks ?? [{ name: "home", path: "/" }],
7
+ tauriRelease: config.tauriRelease ?? {},
8
+ };
9
+ }
@@ -0,0 +1,21 @@
1
+ export type MigrationFile = {
2
+ id: string;
3
+ path: string;
4
+ relativePath: string;
5
+ };
6
+ export type MigrationRisk = {
7
+ code: string;
8
+ line: number;
9
+ match: string;
10
+ reason: string;
11
+ };
12
+ export type MigrationScanResult = {
13
+ migration: MigrationFile;
14
+ risks: MigrationRisk[];
15
+ };
16
+ export declare function collectDeployMigrationFiles(cwd?: string): Promise<MigrationFile[]>;
17
+ export declare function collectHeldMigrationFiles(cwd?: string): Promise<MigrationFile[]>;
18
+ export declare function scanDeployMigrationFiles(migrations: MigrationFile[]): Promise<MigrationScanResult[]>;
19
+ export declare function scanMigrationSql(sql: string): MigrationRisk[];
20
+ export declare function formatMigrationScanFailures(results: MigrationScanResult[]): string;
21
+ export declare function formatMigrationList(migrations: MigrationFile[]): string;