@stacksjs/buddy 0.70.274 → 0.70.275
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/commands/migrate.d.ts +15 -0
- package/dist/commands/migrate.js +2 -2
- package/package.json +43 -43
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import { confirm } from '@stacksjs/cli';
|
|
2
2
|
import type { CLI } from '@stacksjs/types';
|
|
3
|
+
/**
|
|
4
|
+
* Refuse to replay a migration corpus against a database it was not written
|
|
5
|
+
* for.
|
|
6
|
+
*
|
|
7
|
+
* Stacks ships ONE set of migration files under `database/migrations`, emitted
|
|
8
|
+
* for a single dialect, and `buddy new` copies them verbatim into every new
|
|
9
|
+
* project. Setting DB_CONNECTION=postgres therefore used to fail with a raw
|
|
10
|
+
* `syntax error at or near "AUTOINCREMENT"` on file 1 of 121, which says
|
|
11
|
+
* nothing about the actual cause.
|
|
12
|
+
*
|
|
13
|
+
* Checked in the parent, before the migrate:fresh drop confirmation and before
|
|
14
|
+
* any action subprocess starts, so a mismatch cannot drop the framework tables
|
|
15
|
+
* and only then discover it has nothing to rebuild them with.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateMigrationDialect(cwd?: unknown): { valid: boolean, error?: string };
|
|
3
18
|
export declare function migrate(buddy: CLI): void;
|
|
4
19
|
declare type FreshGuard = 'allow' | 'confirm' | 'disabled';
|
package/dist/commands/migrate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{closeSync,existsSync,mkdirSync,openSync,readdirSync,readFileSync,rmSync}from"node:fs";import process from"node:process";import{confirm,intro,log,onUnknownSubcommand,outro,text}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{hasTTY,isCI}from"@stacksjs/env";import{appPath,frameworkPath,frameworkRuntimePath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{preflightDatabase}from"../database-preflight";import{DDL_CONSTRAINT_OVERRIDE_ENV,DIALECT_OVERRIDE_ENV,auditDdlConstraints,auditMigrationCorpus,dialectCapabilities,formatDdlConstraintError,formatMigrationDialectError,stripSqlNoise}from"@stacksjs/database";import{resultFailed}from"../result";let _runAction;async function runAction(...args){if(!_runAction)_runAction=(await import("@stacksjs/actions")).runAction;return _runAction(...args)}function acquireMigrationLock(){const lockDir=frameworkRuntimePath(),lockFile=`${lockDir}/migrations.lock`;try{if(!existsSync(lockDir))mkdirSync(lockDir,{recursive:!0});const fd=openSync(lockFile,"wx");try{closeSync(fd)}catch{}return{acquired:!0,release:()=>{try{rmSync(lockFile,{force:!0})}catch{}}}}catch{return{acquired:!1,release:()=>{}}}}async function ensureDatabaseOrExit(){try{const{ensureDatabaseReady}=await import("@stacksjs/database");await ensureDatabaseReady()}catch(error){log.syncError(error instanceof Error?error.message:String(error));process.exit(ExitCode.FatalError)}}function readMigrateMarker(){const file=frameworkRuntimePath("last-migrate-result.json");if(!existsSync(file))return null;try{const raw=readFileSync(file,"utf8"),parsed=JSON.parse(raw),n=typeof parsed.appliedCount==="number"?parsed.appliedCount:null;if(n===null||!Number.isFinite(n))return null;return{appliedCount:Math.max(0,Math.floor(n))}}catch{return null}finally{try{rmSync(file,{force:!0})}catch{}}}function countModelFiles(dir){if(!existsSync(dir))return 0;let count=0;const entries=readdirSync(dir,{withFileTypes:!0});for(const entry of entries)if(entry.isDirectory())count+=countModelFiles(`${dir}/${entry.name}`);else if(entry.name.endsWith(".ts")&&!entry.name.startsWith(".")&&!entry.name.startsWith("index"))count++;return count}function validateModelsExist(){const userModelsPath=appPath("Models"),defaultModelsPath=frameworkPath("defaults/app/Models"),userModelCount=countModelFiles(userModelsPath),defaultModelCount=countModelFiles(defaultModelsPath);if(userModelCount===0&&defaultModelCount===0)return{valid:!1,error:"No models found. Please create models in app/Models or ensure framework defaults exist."};return{valid:!0}}function validateMigrationDialect(){const driver=String(process.env.DB_CONNECTION||"sqlite").toLowerCase(),dir=
|
|
1
|
+
import{closeSync,existsSync,mkdirSync,openSync,readdirSync,readFileSync,rmSync}from"node:fs";import process from"node:process";import{confirm,intro,log,onUnknownSubcommand,outro,text}from"@stacksjs/cli";import{Action}from"@stacksjs/enums";import{hasTTY,isCI}from"@stacksjs/env";import{appPath,frameworkPath,frameworkRuntimePath,projectPath}from"@stacksjs/path";import{ExitCode}from"@stacksjs/types";import{preflightDatabase}from"../database-preflight";import{DDL_CONSTRAINT_OVERRIDE_ENV,DIALECT_OVERRIDE_ENV,auditDdlConstraints,auditMigrationCorpus,dialectCapabilities,formatDdlConstraintError,formatMigrationDialectError,relativeMigrationDirectory,resolveMigrationDirectory,stripSqlNoise}from"@stacksjs/database";import{resultFailed}from"../result";let _runAction;async function runAction(...args){if(!_runAction)_runAction=(await import("@stacksjs/actions")).runAction;return _runAction(...args)}function acquireMigrationLock(){const lockDir=frameworkRuntimePath(),lockFile=`${lockDir}/migrations.lock`;try{if(!existsSync(lockDir))mkdirSync(lockDir,{recursive:!0});const fd=openSync(lockFile,"wx");try{closeSync(fd)}catch{}return{acquired:!0,release:()=>{try{rmSync(lockFile,{force:!0})}catch{}}}}catch{return{acquired:!1,release:()=>{}}}}async function ensureDatabaseOrExit(){try{const{ensureDatabaseReady}=await import("@stacksjs/database");await ensureDatabaseReady()}catch(error){log.syncError(error instanceof Error?error.message:String(error));process.exit(ExitCode.FatalError)}}function readMigrateMarker(){const file=frameworkRuntimePath("last-migrate-result.json");if(!existsSync(file))return null;try{const raw=readFileSync(file,"utf8"),parsed=JSON.parse(raw),n=typeof parsed.appliedCount==="number"?parsed.appliedCount:null;if(n===null||!Number.isFinite(n))return null;return{appliedCount:Math.max(0,Math.floor(n))}}catch{return null}finally{try{rmSync(file,{force:!0})}catch{}}}function countModelFiles(dir){if(!existsSync(dir))return 0;let count=0;const entries=readdirSync(dir,{withFileTypes:!0});for(const entry of entries)if(entry.isDirectory())count+=countModelFiles(`${dir}/${entry.name}`);else if(entry.name.endsWith(".ts")&&!entry.name.startsWith(".")&&!entry.name.startsWith("index"))count++;return count}function validateModelsExist(){const userModelsPath=appPath("Models"),defaultModelsPath=frameworkPath("defaults/app/Models"),userModelCount=countModelFiles(userModelsPath),defaultModelCount=countModelFiles(defaultModelsPath);if(userModelCount===0&&defaultModelCount===0)return{valid:!1,error:"No models found. Please create models in app/Models or ensure framework defaults exist."};return{valid:!0}}export function validateMigrationDialect(cwd=process.cwd()){const driver=String(process.env.DB_CONNECTION||"sqlite").toLowerCase(),dir=resolveMigrationDirectory(driver,{cwd}),relativeDir=relativeMigrationDirectory(dir,cwd);if(process.env[DIALECT_OVERRIDE_ENV]!=="1"){const caps=dialectCapabilities(driver),target=caps.wire==="mysql"?"mysql":caps.wire==="postgres"?"postgres":"sqlite";if(driver==="sqlite"||driver==="postgres"||caps.wire==="mysql"){const audit=auditMigrationCorpus({dir,target});if(!audit.empty&&audit.incompatible.length>0)return{valid:!1,error:formatMigrationDialectError(audit,target,relativeDir)}}}if(process.env[DDL_CONSTRAINT_OVERRIDE_ENV]!=="1"){const constraints=auditDdlConstraints({dir,dialect:driver});if(!constraints.empty&&constraints.violations.length>0)return{valid:!1,error:formatDdlConstraintError(constraints,driver,relativeDir)}}return{valid:!0}}async function reportMissingForeignKeys(){try{const{auditForeignKeys}=await import("@stacksjs/database"),result=await auditForeignKeys();if(result.missing.length===0)return;const sample=result.missing.slice(0,5).map((fk)=>` \u2022 ${fk.fromTable}.${fk.fromColumn} \u2192 ${fk.toTable}.${fk.toColumn} (${fk.model})`).join(`
|
|
2
2
|
`),more=result.missing.length>5?`
|
|
3
3
|
+ ${result.missing.length-5} more \u2014 run \`./buddy doctor\` for the full list.`:"";log.warn(`${result.missing.length} of ${result.declared.length} declared foreign keys are missing from the live schema:
|
|
4
4
|
${sample}${more}
|
|
@@ -21,7 +21,7 @@ ${sample}${more}
|
|
|
21
21
|
Unknown target driver "${driver}". Allowed: sqlite, mysql, vitess, postgres.
|
|
22
22
|
`);await outro("Aborted.",{startTime:perf,useSeconds:!0});process.exit(ExitCode.FatalError)}const current=(process.env.DB_CONNECTION||"sqlite").toLowerCase();if(current===target){console.log(`
|
|
23
23
|
DB_CONNECTION is already "${target}". Nothing to switch.
|
|
24
|
-
`);await outro("No-op.",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)}const missingEnv=({sqlite:["DB_DATABASE"],mysql:["DB_HOST","DB_PORT","DB_USERNAME","DB_PASSWORD","DB_DATABASE"],vitess:["DB_HOST","DB_PORT","DB_USERNAME","DB_PASSWORD","DB_DATABASE"],postgres:["DB_HOST","DB_PORT","DB_USERNAME","DB_PASSWORD","DB_DATABASE"]}[target]??[]).filter((k)=>!process.env[k]);let alterCount=0,uniqueIdxCount=0;try{const migrationsDir=
|
|
24
|
+
`);await outro("No-op.",{startTime:perf,useSeconds:!0});process.exit(ExitCode.Success)}const missingEnv=({sqlite:["DB_DATABASE"],mysql:["DB_HOST","DB_PORT","DB_USERNAME","DB_PASSWORD","DB_DATABASE"],vitess:["DB_HOST","DB_PORT","DB_USERNAME","DB_PASSWORD","DB_DATABASE"],postgres:["DB_HOST","DB_PORT","DB_USERNAME","DB_PASSWORD","DB_DATABASE"]}[target]??[]).filter((k)=>!process.env[k]);let alterCount=0,uniqueIdxCount=0;try{const migrationsDir=resolveMigrationDirectory(target);if(existsSync(migrationsDir)){const files=readdirSync(migrationsDir).filter((f)=>f.endsWith(".sql"));for(const f of files){const content=stripSqlNoise(readFileSync(`${migrationsDir}/${f}`,"utf-8")).toLowerCase();if(/alter\s+table[\s\S]*add\s+constraint/.test(content))alterCount++;if(/^\s*create\s+unique\s+index/m.test(content))uniqueIdxCount++}}}catch{}const auditTarget=target==="vitess"?"mysql":target,switchAudit=auditMigrationCorpus({dir:resolveMigrationDirectory(target),target:auditTarget}),switchBlocked=switchAudit.incompatible.length>0,blockedFiles=new Set(switchAudit.incompatible.map((m)=>m.file)).size,dialectNote=switchBlocked?`
|
|
25
25
|
\u26D4 ${blockedFiles} of ${switchAudit.total} migration files are ${switchAudit.inferred??"another dialect"}-flavoured and will NOT run on ${target}.
|
|
26
26
|
Regenerate them against ${target} before migrating, or this switch will fail on the first file.`:"",sqliteFkNote=current==="sqlite"&&alterCount>0?`
|
|
27
27
|
(These were skipped on SQLite per stacksjs/stacks#1916 and survive on disk for replay.)`:"",boolNote=current==="sqlite"&&(target==="postgres"||target==="mysql"||target==="vitess")?`
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/buddy",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.275",
|
|
6
6
|
"description": "Meet Buddy. The Stacks runtime.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -95,53 +95,53 @@
|
|
|
95
95
|
"prepublishOnly": "bun run build"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@stacksjs/actions": "^0.70.
|
|
99
|
-
"@stacksjs/ai": "^0.70.
|
|
100
|
-
"@stacksjs/alias": "^0.70.
|
|
101
|
-
"@stacksjs/arrays": "^0.70.
|
|
102
|
-
"@stacksjs/auth": "^0.70.
|
|
103
|
-
"@stacksjs/build": "^0.70.
|
|
104
|
-
"@stacksjs/cache": "^0.70.
|
|
105
|
-
"@stacksjs/cli": "^0.70.
|
|
98
|
+
"@stacksjs/actions": "^0.70.275",
|
|
99
|
+
"@stacksjs/ai": "^0.70.275",
|
|
100
|
+
"@stacksjs/alias": "^0.70.275",
|
|
101
|
+
"@stacksjs/arrays": "^0.70.275",
|
|
102
|
+
"@stacksjs/auth": "^0.70.275",
|
|
103
|
+
"@stacksjs/build": "^0.70.275",
|
|
104
|
+
"@stacksjs/cache": "^0.70.275",
|
|
105
|
+
"@stacksjs/cli": "^0.70.275",
|
|
106
106
|
"@stacksjs/clapp": "^0.2.12",
|
|
107
|
-
"@stacksjs/cloud": "^0.70.
|
|
108
|
-
"@stacksjs/collections": "^0.70.
|
|
109
|
-
"@stacksjs/config": "^0.70.
|
|
110
|
-
"@stacksjs/database": "^0.70.
|
|
111
|
-
"@stacksjs/desktop-build": "^0.70.
|
|
112
|
-
"@stacksjs/dns": "^0.70.
|
|
113
|
-
"@stacksjs/email": "^0.70.
|
|
114
|
-
"@stacksjs/enums": "^0.70.
|
|
115
|
-
"@stacksjs/error-handling": "^0.70.
|
|
116
|
-
"@stacksjs/events": "^0.70.
|
|
117
|
-
"@stacksjs/git": "^0.70.
|
|
107
|
+
"@stacksjs/cloud": "^0.70.275",
|
|
108
|
+
"@stacksjs/collections": "^0.70.275",
|
|
109
|
+
"@stacksjs/config": "^0.70.275",
|
|
110
|
+
"@stacksjs/database": "^0.70.275",
|
|
111
|
+
"@stacksjs/desktop-build": "^0.70.275",
|
|
112
|
+
"@stacksjs/dns": "^0.70.275",
|
|
113
|
+
"@stacksjs/email": "^0.70.275",
|
|
114
|
+
"@stacksjs/enums": "^0.70.275",
|
|
115
|
+
"@stacksjs/error-handling": "^0.70.275",
|
|
116
|
+
"@stacksjs/events": "^0.70.275",
|
|
117
|
+
"@stacksjs/git": "^0.70.275",
|
|
118
118
|
"@stacksjs/gitit": "^0.2.5",
|
|
119
|
-
"@stacksjs/health": "^0.70.
|
|
119
|
+
"@stacksjs/health": "^0.70.275",
|
|
120
120
|
"@stacksjs/dnsx": "^0.2.3",
|
|
121
121
|
"@stacksjs/httx": "^0.1.10",
|
|
122
|
-
"@stacksjs/image": "^0.70.
|
|
123
|
-
"@stacksjs/lint": "^0.70.
|
|
124
|
-
"@stacksjs/logging": "^0.70.
|
|
125
|
-
"@stacksjs/notifications": "^0.70.
|
|
126
|
-
"@stacksjs/objects": "^0.70.
|
|
127
|
-
"@stacksjs/orm": "^0.70.
|
|
128
|
-
"@stacksjs/path": "^0.70.
|
|
129
|
-
"@stacksjs/skills": "^0.70.
|
|
130
|
-
"@stacksjs/payments": "^0.70.
|
|
131
|
-
"@stacksjs/realtime": "^0.70.
|
|
132
|
-
"@stacksjs/router": "^0.70.
|
|
122
|
+
"@stacksjs/image": "^0.70.275",
|
|
123
|
+
"@stacksjs/lint": "^0.70.275",
|
|
124
|
+
"@stacksjs/logging": "^0.70.275",
|
|
125
|
+
"@stacksjs/notifications": "^0.70.275",
|
|
126
|
+
"@stacksjs/objects": "^0.70.275",
|
|
127
|
+
"@stacksjs/orm": "^0.70.275",
|
|
128
|
+
"@stacksjs/path": "^0.70.275",
|
|
129
|
+
"@stacksjs/skills": "^0.70.275",
|
|
130
|
+
"@stacksjs/payments": "^0.70.275",
|
|
131
|
+
"@stacksjs/realtime": "^0.70.275",
|
|
132
|
+
"@stacksjs/router": "^0.70.275",
|
|
133
133
|
"@stacksjs/rpx": "^0.11.42",
|
|
134
|
-
"@stacksjs/search-engine": "^0.70.
|
|
135
|
-
"@stacksjs/security": "^0.70.
|
|
136
|
-
"@stacksjs/server": "^0.70.
|
|
137
|
-
"@stacksjs/storage": "^0.70.
|
|
138
|
-
"@stacksjs/strings": "^0.70.
|
|
139
|
-
"@stacksjs/testing": "^0.70.
|
|
140
|
-
"@stacksjs/tunnel": "^0.70.
|
|
141
|
-
"@stacksjs/types": "^0.70.
|
|
142
|
-
"@stacksjs/ui": "^0.70.
|
|
143
|
-
"@stacksjs/utils": "^0.70.
|
|
144
|
-
"@stacksjs/validation": "^0.70.
|
|
134
|
+
"@stacksjs/search-engine": "^0.70.275",
|
|
135
|
+
"@stacksjs/security": "^0.70.275",
|
|
136
|
+
"@stacksjs/server": "^0.70.275",
|
|
137
|
+
"@stacksjs/storage": "^0.70.275",
|
|
138
|
+
"@stacksjs/strings": "^0.70.275",
|
|
139
|
+
"@stacksjs/testing": "^0.70.275",
|
|
140
|
+
"@stacksjs/tunnel": "^0.70.275",
|
|
141
|
+
"@stacksjs/types": "^0.70.275",
|
|
142
|
+
"@stacksjs/ui": "^0.70.275",
|
|
143
|
+
"@stacksjs/utils": "^0.70.275",
|
|
144
|
+
"@stacksjs/validation": "^0.70.275",
|
|
145
145
|
"@stacksjs/ts-cloud": "^0.7.103",
|
|
146
146
|
"ajv": "^8.20.0",
|
|
147
147
|
"ajv-formats": "^3.0.1",
|