@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.
- package/dist/config.d.ts +18 -4
- package/dist/config.js +1 -3
- package/dist/coreframe.d.ts +243 -241
- package/dist/coreframe.js +1 -239
- package/dist/db/client.d.ts +26 -18
- package/dist/db/client.js +1 -29
- package/dist/db/config.d.ts +22 -19
- package/dist/db/config.js +1 -32
- package/dist/db/drizzle-kit.d.ts +6 -5
- package/dist/db/drizzle-kit.js +1 -21
- package/dist/db/ensure.d.ts +20 -2
- package/dist/db/ensure.js +1 -39
- package/dist/db/migrate-held.d.ts +16 -10
- package/dist/db/migrate-held.js +3 -112
- package/dist/db/migrate.d.ts +4 -2
- package/dist/db/migrate.js +1 -24
- package/dist/db/project.d.ts +19 -7
- package/dist/db/project.js +1 -9
- package/dist/db/reset.d.ts +4 -2
- package/dist/db/reset.js +1 -18
- package/dist/db/schema-push.d.ts +22 -0
- package/dist/db/schema-push.js +1 -0
- package/dist/db/seed.d.ts +6 -3
- package/dist/db/seed.js +1 -22
- package/dist/db/setup.d.ts +6 -3
- package/dist/db/setup.js +1 -14
- package/dist/deploy/check-migrations.d.ts +4 -2
- package/dist/deploy/check-migrations.js +1 -17
- package/dist/deploy/checks.d.ts +6 -2
- package/dist/deploy/checks.js +1 -27
- package/dist/deploy/config.d.ts +47 -44
- package/dist/deploy/config.js +1 -9
- package/dist/deploy/migrations.d.ts +21 -18
- package/dist/deploy/migrations.js +4 -203
- package/dist/deploy/pending-migrations.d.ts +9 -5
- package/dist/deploy/pending-migrations.js +1 -30
- package/dist/deploy/tauri-release.d.ts +93 -73
- package/dist/deploy/tauri-release.js +1 -512
- package/dist/deploy/version.d.ts +15 -13
- package/dist/deploy/version.js +1 -41
- package/dist/deploy.d.ts +16 -15
- package/dist/deploy.js +1 -246
- package/dist/dev.d.ts +23 -12
- package/dist/dev.js +1 -95
- package/dist/image-generator.d.ts +18 -2
- package/dist/image-generator.js +1 -59
- package/dist/project.d.ts +4 -1
- package/dist/project.js +1 -13
- package/dist/typecheck.d.ts +4 -2
- package/dist/typecheck.js +1 -44
- package/dist/upgrade/check-skill-current.d.ts +10 -5
- package/dist/upgrade/check-skill-current.js +1 -87
- package/dist/upgrade/ensure-template-remote.d.ts +10 -5
- package/dist/upgrade/ensure-template-remote.js +1 -56
- package/dist/upgrade/fast-forward-template-files.d.ts +8 -6
- package/dist/upgrade/fast-forward-template-files.js +1 -279
- package/package.json +7 -2
- package/config.test.ts +0 -15
- package/config.ts +0 -9
- package/coreframe.ts +0 -272
- package/db/client.ts +0 -72
- package/db/config.ts +0 -41
- package/db/drizzle-kit.test.ts +0 -27
- package/db/drizzle-kit.ts +0 -39
- package/db/ensure.ts +0 -67
- package/db/migrate-held.test.ts +0 -81
- package/db/migrate-held.ts +0 -166
- package/db/migrate.ts +0 -39
- package/db/project.ts +0 -28
- package/db/reset.ts +0 -24
- package/db/seed.ts +0 -38
- package/db/setup.ts +0 -18
- package/deploy/check-migrations.ts +0 -31
- package/deploy/checks.ts +0 -38
- package/deploy/config.test.ts +0 -24
- package/deploy/config.ts +0 -68
- package/deploy/migrations.test.ts +0 -110
- package/deploy/migrations.ts +0 -317
- package/deploy/pending-migrations.test.ts +0 -93
- package/deploy/pending-migrations.ts +0 -45
- package/deploy/tauri-release.test.ts +0 -196
- package/deploy/tauri-release.ts +0 -816
- package/deploy/version.test.ts +0 -29
- package/deploy/version.ts +0 -65
- package/deploy.test.ts +0 -101
- package/deploy.ts +0 -346
- package/dev.test.ts +0 -153
- package/dev.ts +0 -155
- package/image-generator.ts +0 -84
- package/project.ts +0 -6
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -20
- package/typecheck.ts +0 -55
- package/upgrade/check-skill-current.ts +0 -112
- package/upgrade/ensure-template-remote.ts +0 -79
- package/upgrade/fast-forward-template-files.ts +0 -408
package/db/migrate-held.ts
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createHash } from "node:crypto"
|
|
3
|
-
import { readFile } from "node:fs/promises"
|
|
4
|
-
|
|
5
|
-
import { collectHeldMigrationFiles, formatMigrationList } from "../deploy/migrations.ts"
|
|
6
|
-
import type { CoreframeDatabaseClient, CoreframeDatabaseDialect } from "./client.ts"
|
|
7
|
-
import { createProjectDeployDatabase } from "./project.ts"
|
|
8
|
-
|
|
9
|
-
const CONFIRM_FLAG = "--confirm=run-held-migrations"
|
|
10
|
-
const HELD_HEADER_FIELDS = ["-- Held migration:", "-- Safe after:", "-- Verification:"] as const
|
|
11
|
-
|
|
12
|
-
type MigrateHeldOptions = {
|
|
13
|
-
argv?: string[]
|
|
14
|
-
database?: CoreframeDatabaseClient
|
|
15
|
-
env?: NodeJS.ProcessEnv
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function migrateHeldMigrations({
|
|
19
|
-
argv = process.argv.slice(2),
|
|
20
|
-
database,
|
|
21
|
-
env = process.env,
|
|
22
|
-
}: MigrateHeldOptions = {}) {
|
|
23
|
-
const migrations = await collectHeldMigrationFiles()
|
|
24
|
-
|
|
25
|
-
console.log("Held migration files:")
|
|
26
|
-
console.log(formatMigrationList(migrations))
|
|
27
|
-
|
|
28
|
-
if (!argv.includes(CONFIRM_FLAG)) {
|
|
29
|
-
throw new Error(`Held migrations require explicit confirmation. Re-run with ${CONFIRM_FLAG}.`)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (migrations.length === 0) {
|
|
33
|
-
console.log("No held migrations to run.")
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const activeDatabase = database ?? (await createProjectDeployDatabase({ env }))
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
await ensureHeldMigrationJournal(activeDatabase)
|
|
41
|
-
|
|
42
|
-
for (const migration of migrations) {
|
|
43
|
-
const sql = await readFile(migration.path, "utf8")
|
|
44
|
-
validateHeldMigrationHeader(sql, migration.relativePath)
|
|
45
|
-
const sha256 = hashSql(sql)
|
|
46
|
-
const applied = await activeDatabase.execute<{ sha256: string }>(
|
|
47
|
-
heldMigrationSelectQuery(activeDatabase.dialect, migration.id),
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
if (applied.rows.length === 1) {
|
|
51
|
-
if (applied.rows[0]?.sha256 !== sha256) {
|
|
52
|
-
throw new Error(`Held migration ${migration.relativePath} changed after it was applied.`)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.log(`Skipping already-applied held migration ${migration.relativePath}`)
|
|
56
|
-
continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
console.log(`Applying held migration ${migration.relativePath}`)
|
|
60
|
-
await runMigrationTransaction(activeDatabase, async (transactionDatabase) => {
|
|
61
|
-
await transactionDatabase.execute(sql)
|
|
62
|
-
await transactionDatabase.execute(
|
|
63
|
-
heldMigrationInsertQuery(activeDatabase.dialect, migration.id, sha256),
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
} finally {
|
|
68
|
-
if (!database) {
|
|
69
|
-
await activeDatabase.close?.()
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function validateHeldMigrationHeader(sql: string, relativePath: string) {
|
|
75
|
-
const missing = HELD_HEADER_FIELDS.filter((field) => !sql.includes(field))
|
|
76
|
-
|
|
77
|
-
if (missing.length > 0) {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`${relativePath} is missing held migration header fields: ${missing.join(", ")}`,
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function hashSql(sql: string) {
|
|
85
|
-
return createHash("sha256").update(sql).digest("hex")
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function ensureHeldMigrationJournal(database: CoreframeDatabaseClient) {
|
|
89
|
-
for (const statement of heldMigrationJournalStatements(database.dialect)) {
|
|
90
|
-
await database.execute(statement)
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function heldMigrationJournalStatements(dialect: CoreframeDatabaseDialect) {
|
|
95
|
-
switch (dialect) {
|
|
96
|
-
case "postgres":
|
|
97
|
-
return [
|
|
98
|
-
"create schema if not exists drizzle",
|
|
99
|
-
`create table if not exists drizzle.__held_migrations (
|
|
100
|
-
id text primary key,
|
|
101
|
-
sha256 text not null,
|
|
102
|
-
applied_at timestamptz not null default now()
|
|
103
|
-
)`,
|
|
104
|
-
]
|
|
105
|
-
case "sqlite":
|
|
106
|
-
return [
|
|
107
|
-
`create table if not exists __held_migrations (
|
|
108
|
-
id text primary key,
|
|
109
|
-
sha256 text not null,
|
|
110
|
-
applied_at text not null default current_timestamp
|
|
111
|
-
)`,
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function heldMigrationSelectQuery(dialect: CoreframeDatabaseDialect, id: string) {
|
|
117
|
-
return `select sha256 from ${heldMigrationJournalTable(dialect)} where id = ${sqlString(id)}`
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function heldMigrationInsertQuery(
|
|
121
|
-
dialect: CoreframeDatabaseDialect,
|
|
122
|
-
id: string,
|
|
123
|
-
sha256: string,
|
|
124
|
-
) {
|
|
125
|
-
return `insert into ${heldMigrationJournalTable(dialect)} (id, sha256) values (${sqlString(id)}, ${sqlString(sha256)})`
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function heldMigrationJournalTable(dialect: CoreframeDatabaseDialect) {
|
|
129
|
-
switch (dialect) {
|
|
130
|
-
case "postgres":
|
|
131
|
-
return "drizzle.__held_migrations"
|
|
132
|
-
case "sqlite":
|
|
133
|
-
return "__held_migrations"
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function runMigrationTransaction(
|
|
138
|
-
database: CoreframeDatabaseClient,
|
|
139
|
-
run: (database: CoreframeDatabaseClient) => Promise<void>,
|
|
140
|
-
) {
|
|
141
|
-
if (database.transaction) {
|
|
142
|
-
await database.transaction(run)
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
await database.execute("begin")
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
await run(database)
|
|
150
|
-
await database.execute("commit")
|
|
151
|
-
} catch (error) {
|
|
152
|
-
await database.execute("rollback")
|
|
153
|
-
throw error
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function sqlString(value: string) {
|
|
158
|
-
return `'${value.replaceAll("'", "''")}'`
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (import.meta.main) {
|
|
162
|
-
migrateHeldMigrations().catch((error) => {
|
|
163
|
-
console.error(error)
|
|
164
|
-
process.exit(1)
|
|
165
|
-
})
|
|
166
|
-
}
|
package/db/migrate.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { access } from "node:fs/promises"
|
|
3
|
-
import { resolve } from "node:path"
|
|
4
|
-
|
|
5
|
-
import { migrate } from "drizzle-orm/pglite/migrator"
|
|
6
|
-
|
|
7
|
-
import { importProjectModule } from "../project.ts"
|
|
8
|
-
import { LOCAL_DB_DIR, MIGRATIONS_FOLDER } from "./config.ts"
|
|
9
|
-
|
|
10
|
-
const migrationsFolder = resolve(MIGRATIONS_FOLDER)
|
|
11
|
-
|
|
12
|
-
type LocalDbModule = {
|
|
13
|
-
createLocalDb(dataDir?: string): {
|
|
14
|
-
$client: {
|
|
15
|
-
close(): Promise<void>
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function migrateLocalDb() {
|
|
21
|
-
await access(migrationsFolder)
|
|
22
|
-
const { createLocalDb } = await importProjectModule<LocalDbModule>("src/db/local.ts")
|
|
23
|
-
|
|
24
|
-
const db = createLocalDb(LOCAL_DB_DIR)
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
await migrate(db as never, { migrationsFolder })
|
|
28
|
-
console.log(`Applied local migrations from ${migrationsFolder}`)
|
|
29
|
-
} finally {
|
|
30
|
-
await db.$client.close()
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (import.meta.main) {
|
|
35
|
-
migrateLocalDb().catch((error) => {
|
|
36
|
-
console.error(error)
|
|
37
|
-
process.exit(1)
|
|
38
|
-
})
|
|
39
|
-
}
|
package/db/project.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { importProjectModule } from "../project.ts"
|
|
2
|
-
import type { CoreframeDatabaseClient } from "./client.ts"
|
|
3
|
-
|
|
4
|
-
const projectDatabaseConfigPath = "scripts/coreframe.db.ts"
|
|
5
|
-
|
|
6
|
-
export type CreateProjectDatabaseOptions = {
|
|
7
|
-
env?: NodeJS.ProcessEnv
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type ProjectDatabaseModule = {
|
|
11
|
-
createDeployDatabase?(
|
|
12
|
-
options?: CreateProjectDatabaseOptions,
|
|
13
|
-
): CoreframeDatabaseClient | Promise<CoreframeDatabaseClient>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function createProjectDeployDatabase(
|
|
17
|
-
options: CreateProjectDatabaseOptions = {},
|
|
18
|
-
): Promise<CoreframeDatabaseClient> {
|
|
19
|
-
const module = await importProjectModule<ProjectDatabaseModule>(projectDatabaseConfigPath)
|
|
20
|
-
|
|
21
|
-
if (!module.createDeployDatabase) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`${projectDatabaseConfigPath} must export createDeployDatabase() for Coreframe database workflows.`,
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return module.createDeployDatabase(options)
|
|
28
|
-
}
|
package/db/reset.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { rm } from "node:fs/promises"
|
|
3
|
-
import { relative, resolve } from "node:path"
|
|
4
|
-
|
|
5
|
-
import { LOCAL_DB_DIR } from "./config.ts"
|
|
6
|
-
|
|
7
|
-
export async function resetLocalDb() {
|
|
8
|
-
const dataDir = resolve(LOCAL_DB_DIR)
|
|
9
|
-
const relativeDataDir = relative(process.cwd(), dataDir)
|
|
10
|
-
|
|
11
|
-
if (relativeDataDir === "" || relativeDataDir.startsWith("..")) {
|
|
12
|
-
throw new Error(`Refusing to reset local database outside the project: ${dataDir}`)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
await rm(dataDir, { force: true, recursive: true })
|
|
16
|
-
console.log(`Removed local PGlite data at ${dataDir}`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (import.meta.main) {
|
|
20
|
-
resetLocalDb().catch((error) => {
|
|
21
|
-
console.error(error)
|
|
22
|
-
process.exit(1)
|
|
23
|
-
})
|
|
24
|
-
}
|
package/db/seed.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { importProjectModule } from "../project.ts"
|
|
3
|
-
import { getSeedProfile, LOCAL_DB_DIR } from "./config.ts"
|
|
4
|
-
import type { SeedProfile } from "./config.ts"
|
|
5
|
-
|
|
6
|
-
type LocalDbModule = {
|
|
7
|
-
createLocalDb(dataDir?: string): {
|
|
8
|
-
$client: {
|
|
9
|
-
close(): Promise<void>
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type SeedUsersModule = {
|
|
15
|
-
seedUsers(db: unknown, profile: SeedProfile): Promise<void>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function seedLocalDb(profile: SeedProfile = getSeedProfile()) {
|
|
19
|
-
const [{ createLocalDb }, { seedUsers }] = await Promise.all([
|
|
20
|
-
importProjectModule<LocalDbModule>("src/db/local.ts"),
|
|
21
|
-
importProjectModule<SeedUsersModule>("scripts/db/seeds/users.ts"),
|
|
22
|
-
])
|
|
23
|
-
const db = createLocalDb(LOCAL_DB_DIR)
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
await seedUsers(db, profile)
|
|
27
|
-
console.log(`Seeded local database with "${profile}" profile`)
|
|
28
|
-
} finally {
|
|
29
|
-
await db.$client.close()
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (import.meta.main) {
|
|
34
|
-
seedLocalDb().catch((error) => {
|
|
35
|
-
console.error(error)
|
|
36
|
-
process.exit(1)
|
|
37
|
-
})
|
|
38
|
-
}
|
package/db/setup.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import type { SeedProfile } from "./config.ts"
|
|
3
|
-
import { migrateLocalDb } from "./migrate.ts"
|
|
4
|
-
import { resetLocalDb } from "./reset.ts"
|
|
5
|
-
import { seedLocalDb } from "./seed.ts"
|
|
6
|
-
|
|
7
|
-
export async function setupLocalDb(profile: SeedProfile = "small") {
|
|
8
|
-
await resetLocalDb()
|
|
9
|
-
await migrateLocalDb()
|
|
10
|
-
await seedLocalDb(profile)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (import.meta.main) {
|
|
14
|
-
setupLocalDb().catch((error) => {
|
|
15
|
-
console.error(error)
|
|
16
|
-
process.exit(1)
|
|
17
|
-
})
|
|
18
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
collectDeployMigrationFiles,
|
|
4
|
-
formatMigrationList,
|
|
5
|
-
formatMigrationScanFailures,
|
|
6
|
-
scanDeployMigrationFiles,
|
|
7
|
-
} from "./migrations.ts"
|
|
8
|
-
|
|
9
|
-
export async function checkDeployMigrations() {
|
|
10
|
-
const migrations = await collectDeployMigrationFiles()
|
|
11
|
-
|
|
12
|
-
console.log("Deploy migration files:")
|
|
13
|
-
console.log(formatMigrationList(migrations))
|
|
14
|
-
|
|
15
|
-
const failures = await scanDeployMigrationFiles(migrations)
|
|
16
|
-
|
|
17
|
-
if (failures.length > 0) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
`Deploy migrations contain unsafe SQL. Move unsafe statements to held migrations.\n${formatMigrationScanFailures(failures)}`,
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
console.log("Deploy migrations passed safety checks.")
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (import.meta.main) {
|
|
27
|
-
checkDeployMigrations().catch((error) => {
|
|
28
|
-
console.error(error)
|
|
29
|
-
process.exit(1)
|
|
30
|
-
})
|
|
31
|
-
}
|
package/deploy/checks.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { DeployContext, ResolvedDeployConfig } from "./config.ts"
|
|
2
|
-
|
|
3
|
-
export async function runPostDeployChecks(config: ResolvedDeployConfig, context: DeployContext) {
|
|
4
|
-
const productionUrl = context.productionUrl ?? config.productionUrl
|
|
5
|
-
|
|
6
|
-
if (config.smokeChecks.length > 0 && !productionUrl) {
|
|
7
|
-
console.log("Skipping HTTP smoke checks. Set DEPLOY_URL or productionUrl in deploy config.")
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
for (const check of config.smokeChecks) {
|
|
11
|
-
if (!productionUrl && !check.url) {
|
|
12
|
-
continue
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const url = check.url ?? new URL(check.path ?? "/", productionUrl).toString()
|
|
16
|
-
const response = await fetch(url)
|
|
17
|
-
|
|
18
|
-
if (!statusMatches(response.status, check.expectedStatus ?? "2xx")) {
|
|
19
|
-
throw new Error(`${check.name} smoke check failed: ${url} returned ${response.status}.`)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
console.log(`${check.name} smoke check passed: ${response.status} ${url}`)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
await config.afterDeploy?.({ ...context, productionUrl })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function statusMatches(status: number, expected: "2xx" | number | number[]) {
|
|
29
|
-
if (expected === "2xx") {
|
|
30
|
-
return status >= 200 && status < 300
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (Array.isArray(expected)) {
|
|
34
|
-
return expected.includes(status)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return status === expected
|
|
38
|
-
}
|
package/deploy/config.test.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest"
|
|
2
|
-
|
|
3
|
-
import { resolveDeployConfig } from "./config.ts"
|
|
4
|
-
|
|
5
|
-
describe("resolveDeployConfig", () => {
|
|
6
|
-
it("uses a generic homepage smoke check by default", () => {
|
|
7
|
-
expect(resolveDeployConfig({}, {}).smokeChecks).toEqual([{ name: "home", path: "/" }])
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it("lets project config override smoke checks", () => {
|
|
11
|
-
const config = resolveDeployConfig(
|
|
12
|
-
{ smokeChecks: [{ name: "api", path: "/api/health", expectedStatus: 204 }] },
|
|
13
|
-
{},
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
expect(config.smokeChecks).toEqual([{ name: "api", path: "/api/health", expectedStatus: 204 }])
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it("falls back to DEPLOY_URL for production checks", () => {
|
|
20
|
-
expect(resolveDeployConfig({}, { DEPLOY_URL: "https://example.com" }).productionUrl).toBe(
|
|
21
|
-
"https://example.com",
|
|
22
|
-
)
|
|
23
|
-
})
|
|
24
|
-
})
|
package/deploy/config.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
export type DeployContext = {
|
|
2
|
-
environment: string
|
|
3
|
-
productionUrl?: string
|
|
4
|
-
releaseName: string
|
|
5
|
-
version: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export const tauriReleasePlatforms = ["macos", "windows", "linux", "ios", "android"] as const
|
|
9
|
-
|
|
10
|
-
export type TauriReleasePlatform = (typeof tauriReleasePlatforms)[number]
|
|
11
|
-
|
|
12
|
-
export type TauriReleasePlatformConfig = {
|
|
13
|
-
artifactGlobs?: string[]
|
|
14
|
-
buildArgs?: string[]
|
|
15
|
-
targetTriple?: string
|
|
16
|
-
updatePlatform?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type TauriReleaseConfig = {
|
|
20
|
-
artifactDownloadDir?: string
|
|
21
|
-
b2Bucket?: string
|
|
22
|
-
latestManifestPath?: string
|
|
23
|
-
localPlatforms?: TauriReleasePlatform[]
|
|
24
|
-
platform?: Partial<Record<TauriReleasePlatform, TauriReleasePlatformConfig>>
|
|
25
|
-
publicBaseUrl?: string
|
|
26
|
-
publicPathPrefix?: string
|
|
27
|
-
releaseNotesPath?: string
|
|
28
|
-
releasePath?: string
|
|
29
|
-
releaseUploadDir?: string
|
|
30
|
-
workflowPlatforms?: TauriReleasePlatform[]
|
|
31
|
-
workflowName?: string
|
|
32
|
-
workflowPollIntervalMs?: number
|
|
33
|
-
workflowRef?: string
|
|
34
|
-
workflowTimeoutMs?: number
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type DeploySmokeCheck = {
|
|
38
|
-
name: string
|
|
39
|
-
path?: string
|
|
40
|
-
url?: string
|
|
41
|
-
expectedStatus?: "2xx" | number | number[]
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export type DeployConfig = {
|
|
45
|
-
afterDeploy?: (context: DeployContext) => Promise<void> | void
|
|
46
|
-
productionUrl?: string
|
|
47
|
-
smokeChecks?: DeploySmokeCheck[]
|
|
48
|
-
tauriRelease?: TauriReleaseConfig
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export type ResolvedDeployConfig = {
|
|
52
|
-
afterDeploy?: (context: DeployContext) => Promise<void> | void
|
|
53
|
-
productionUrl?: string
|
|
54
|
-
smokeChecks: DeploySmokeCheck[]
|
|
55
|
-
tauriRelease: TauriReleaseConfig
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function resolveDeployConfig(
|
|
59
|
-
config: DeployConfig,
|
|
60
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
61
|
-
): ResolvedDeployConfig {
|
|
62
|
-
return {
|
|
63
|
-
afterDeploy: config.afterDeploy,
|
|
64
|
-
productionUrl: config.productionUrl ?? env.DEPLOY_URL ?? env.PRODUCTION_URL,
|
|
65
|
-
smokeChecks: config.smokeChecks ?? [{ name: "home", path: "/" }],
|
|
66
|
-
tauriRelease: config.tauriRelease ?? {},
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, mkdir, 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
|
-
collectDeployMigrationFiles,
|
|
9
|
-
scanDeployMigrationFiles,
|
|
10
|
-
scanMigrationSql,
|
|
11
|
-
} from "./migrations.ts"
|
|
12
|
-
|
|
13
|
-
describe("scanMigrationSql", () => {
|
|
14
|
-
it("allows additive schema created inside the same migration", () => {
|
|
15
|
-
const risks = scanMigrationSql(`
|
|
16
|
-
CREATE TABLE "users" (
|
|
17
|
-
"id" uuid PRIMARY KEY,
|
|
18
|
-
"email" text NOT NULL UNIQUE
|
|
19
|
-
);
|
|
20
|
-
CREATE TABLE "profiles" (
|
|
21
|
-
"user_id" uuid PRIMARY KEY,
|
|
22
|
-
"display_name" text DEFAULT '' NOT NULL
|
|
23
|
-
);
|
|
24
|
-
CREATE UNIQUE INDEX "profiles_user_id_uidx" ON "profiles" ("user_id");
|
|
25
|
-
ALTER TABLE "profiles" ADD CONSTRAINT "profiles_user_id_users_id_fkey"
|
|
26
|
-
FOREIGN KEY ("user_id") REFERENCES "users"("id");
|
|
27
|
-
`)
|
|
28
|
-
|
|
29
|
-
expect(risks).toEqual([])
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it("blocks destructive and contract-style SQL", () => {
|
|
33
|
-
const risks = scanMigrationSql(`
|
|
34
|
-
ALTER TABLE "users" DROP COLUMN "name";
|
|
35
|
-
ALTER TABLE "users" RENAME COLUMN "email" TO "primary_email";
|
|
36
|
-
ALTER TABLE "users" ALTER COLUMN "active" SET NOT NULL;
|
|
37
|
-
ALTER TABLE "users" ALTER COLUMN "created_at" TYPE text;
|
|
38
|
-
UPDATE "users" SET "active" = false;
|
|
39
|
-
DELETE FROM "users" WHERE "active" = false;
|
|
40
|
-
DROP INDEX "users_email_idx";
|
|
41
|
-
`)
|
|
42
|
-
|
|
43
|
-
expect(risks.map((risk) => risk.code)).toEqual([
|
|
44
|
-
"drop-column",
|
|
45
|
-
"rename-table-or-column",
|
|
46
|
-
"set-not-null",
|
|
47
|
-
"rewrite-column-type",
|
|
48
|
-
"broad-update",
|
|
49
|
-
"broad-delete",
|
|
50
|
-
"drop-schema-object",
|
|
51
|
-
])
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it("blocks constraint enforcement on existing tables", () => {
|
|
55
|
-
const risks = scanMigrationSql(`
|
|
56
|
-
ALTER TABLE "users" ADD COLUMN "handle" text NOT NULL;
|
|
57
|
-
ALTER TABLE "users" ADD CONSTRAINT "users_handle_unique" UNIQUE ("handle");
|
|
58
|
-
CREATE UNIQUE INDEX "users_handle_idx" ON "users" ("handle");
|
|
59
|
-
`)
|
|
60
|
-
|
|
61
|
-
expect(risks.map((risk) => risk.code)).toEqual([
|
|
62
|
-
"add-not-null-column",
|
|
63
|
-
"add-constraint",
|
|
64
|
-
"create-unique-index",
|
|
65
|
-
])
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
it("ignores unsafe words inside comments and string literals", () => {
|
|
69
|
-
const risks = scanMigrationSql(`
|
|
70
|
-
-- ALTER TABLE users DROP COLUMN name;
|
|
71
|
-
CREATE TABLE "audit" (
|
|
72
|
-
"id" uuid PRIMARY KEY,
|
|
73
|
-
"message" text DEFAULT 'DROP TABLE users' NOT NULL
|
|
74
|
-
);
|
|
75
|
-
`)
|
|
76
|
-
|
|
77
|
-
expect(risks).toEqual([])
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
describe("collectDeployMigrationFiles", () => {
|
|
82
|
-
it("collects Drizzle directory migrations in release order", async () => {
|
|
83
|
-
const root = await mkdtemp(join(tmpdir(), "coreframe-migrations-"))
|
|
84
|
-
await mkdir(join(root, "drizzle", "20260703120000_first"), { recursive: true })
|
|
85
|
-
await mkdir(join(root, "drizzle", "20260703130000_second"), { recursive: true })
|
|
86
|
-
await writeFile(join(root, "drizzle", "20260703130000_second", "migration.sql"), "select 2;")
|
|
87
|
-
await writeFile(join(root, "drizzle", "20260703120000_first", "migration.sql"), "select 1;")
|
|
88
|
-
|
|
89
|
-
const migrations = await collectDeployMigrationFiles(root)
|
|
90
|
-
|
|
91
|
-
expect(migrations.map((migration) => migration.relativePath)).toEqual([
|
|
92
|
-
"drizzle/20260703120000_first/migration.sql",
|
|
93
|
-
"drizzle/20260703130000_second/migration.sql",
|
|
94
|
-
])
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it("reports unsafe SQL from collected files", async () => {
|
|
98
|
-
const root = await mkdtemp(join(tmpdir(), "coreframe-migration-scan-"))
|
|
99
|
-
await mkdir(join(root, "drizzle", "20260703120000_drop_name"), { recursive: true })
|
|
100
|
-
await writeFile(
|
|
101
|
-
join(root, "drizzle", "20260703120000_drop_name", "migration.sql"),
|
|
102
|
-
'ALTER TABLE "users" DROP COLUMN "name";',
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
const failures = await scanDeployMigrationFiles(await collectDeployMigrationFiles(root))
|
|
106
|
-
|
|
107
|
-
expect(failures).toHaveLength(1)
|
|
108
|
-
expect(failures[0]?.risks[0]?.code).toBe("drop-column")
|
|
109
|
-
})
|
|
110
|
-
})
|