drizzle-auto 1.1.23 → 1.1.25
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/index.js +112 -121
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,185 +1,176 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* 🔹
|
|
6
|
-
* 🔹
|
|
7
|
-
* 🔹
|
|
4
|
+
* 🍵 Drizzle-Auto v2.1 - Fail-Fast Edition
|
|
5
|
+
* 🔹 Strictly catches WebSocket/Neon ENOTFOUND errors
|
|
6
|
+
* 🔹 Stops immediately on any failure
|
|
7
|
+
* 🔹 Debounced watcher to prevent race conditions
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const {
|
|
10
|
+
const { spawn } = require("child_process");
|
|
11
11
|
const path = require("path");
|
|
12
12
|
const fs = require("fs");
|
|
13
13
|
|
|
14
|
-
// Optional
|
|
15
|
-
try { require("ts-node").register({ transpileOnly: true }); } catch(e){}
|
|
14
|
+
// Optional TS support for config parsing
|
|
15
|
+
try { require("ts-node").register({ transpileOnly: true }); } catch (e) {}
|
|
16
16
|
|
|
17
|
-
// ---------------------- COLORS ----------------------
|
|
17
|
+
// ---------------------- COLORS & UI ----------------------
|
|
18
18
|
const T = {
|
|
19
19
|
reset: "\x1b[0m",
|
|
20
20
|
bold: "\x1b[1m",
|
|
21
21
|
cyan: "\x1b[38;5;51m",
|
|
22
|
-
pink: "\x1b[38;5;201m",
|
|
23
22
|
lime: "\x1b[38;5;118m",
|
|
24
23
|
yellow: "\x1b[38;5;226m",
|
|
25
24
|
red: "\x1b[38;5;196m",
|
|
26
25
|
gray: "\x1b[38;5;244m",
|
|
27
26
|
};
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
const frames = ["⠋","⠙","⠹","⠸","⠼","⠴","⠦","⠧","⠇","⠏"];
|
|
28
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
31
29
|
let spinIdx = 0, spinInterval;
|
|
32
30
|
|
|
33
|
-
function startLoading(msg){
|
|
31
|
+
function startLoading(msg) {
|
|
34
32
|
stopLoading();
|
|
35
|
-
spinInterval = setInterval(()=>{
|
|
33
|
+
spinInterval = setInterval(() => {
|
|
36
34
|
process.stdout.write(`\r${T.cyan}${frames[spinIdx++ % frames.length]}${T.reset} ${T.bold}${msg}${T.reset} `);
|
|
37
35
|
}, 80);
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
function stopLoading(){
|
|
41
|
-
if(spinInterval){
|
|
38
|
+
function stopLoading() {
|
|
39
|
+
if (spinInterval) {
|
|
42
40
|
clearInterval(spinInterval);
|
|
43
41
|
spinInterval = null;
|
|
44
42
|
process.stdout.write("\r\x1b[K");
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
// ----------------------
|
|
49
|
-
const
|
|
46
|
+
// ---------------------- UTILS ----------------------
|
|
47
|
+
const PKG_PATH = path.join(process.cwd(), "package.json");
|
|
50
48
|
function injectScript() {
|
|
51
|
-
if (!fs.existsSync(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
pkg.scripts.tea
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
injectScript();
|
|
61
|
-
|
|
62
|
-
// ---------------------- STRICT CONFIG DETECTION ----------------------
|
|
63
|
-
function findDrizzleConfig(root){
|
|
64
|
-
const allowed = [
|
|
65
|
-
"drizzle.config.js",
|
|
66
|
-
"drizzle.config.ts",
|
|
67
|
-
"drizzle.config.mjs",
|
|
68
|
-
"drizzle.config.cjs"
|
|
69
|
-
];
|
|
70
|
-
const files = [];
|
|
71
|
-
function walk(dir){
|
|
72
|
-
fs.readdirSync(dir,{withFileTypes:true}).forEach(file=>{
|
|
73
|
-
const full = path.join(dir,file.name);
|
|
74
|
-
if(file.isDirectory() && !/node_modules|\.git|\.next|dist/.test(file.name)) walk(full);
|
|
75
|
-
else if(file.isFile() && allowed.includes(file.name)) files.push(full);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
walk(root);
|
|
79
|
-
return files;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------- VALIDATE SCHEMA ----------------------
|
|
83
|
-
function validateSchema(){
|
|
84
|
-
const configs = findDrizzleConfig(process.cwd());
|
|
85
|
-
if(configs.length === 0)
|
|
86
|
-
return { ok:false, msg:"Missing strict drizzle.config.js / ts / mjs / cjs" };
|
|
87
|
-
|
|
88
|
-
for(const cfg of configs){
|
|
89
|
-
try {
|
|
90
|
-
let schemaPath;
|
|
91
|
-
const mod = require(cfg);
|
|
92
|
-
schemaPath = mod.default?.schema || mod.schema;
|
|
93
|
-
|
|
94
|
-
if(!schemaPath) continue;
|
|
95
|
-
|
|
96
|
-
const schemaFull = path.resolve(path.dirname(cfg), schemaPath);
|
|
97
|
-
if(!fs.existsSync(schemaFull))
|
|
98
|
-
return { ok:false, msg:`Schema not found: ${schemaPath} in ${cfg}` };
|
|
99
|
-
|
|
100
|
-
} catch(e){
|
|
101
|
-
return { ok:false, msg:`Config parse error in ${cfg}: ${e.message}` };
|
|
49
|
+
if (!fs.existsSync(PKG_PATH)) return;
|
|
50
|
+
try {
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, "utf8"));
|
|
52
|
+
pkg.scripts ||= {};
|
|
53
|
+
if (!pkg.scripts.tea) {
|
|
54
|
+
pkg.scripts.tea = "drizzle-auto";
|
|
55
|
+
fs.writeFileSync(PKG_PATH, JSON.stringify(pkg, null, 2));
|
|
56
|
+
console.log(`${T.lime}🍵 Script added → npm run tea${T.reset}`);
|
|
102
57
|
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return { ok:true, config:configs };
|
|
58
|
+
} catch (e) {}
|
|
106
59
|
}
|
|
107
60
|
|
|
108
|
-
// ----------------------
|
|
109
|
-
function runCommand(cmd, label){
|
|
61
|
+
// ---------------------- COMMAND RUNNER (STRICT) ----------------------
|
|
62
|
+
function runCommand(cmd, label) {
|
|
110
63
|
return new Promise((resolve, reject) => {
|
|
111
64
|
startLoading(label);
|
|
112
|
-
|
|
113
|
-
|
|
65
|
+
|
|
66
|
+
// We pipe stderr to catch hidden WebSocket/Neon errors that don't trigger exit codes
|
|
67
|
+
const child = spawn(cmd, { shell: true, stdio: ["inherit", "inherit", "pipe"] });
|
|
68
|
+
let errorOutput = "";
|
|
69
|
+
|
|
70
|
+
child.stderr.on("data", (data) => {
|
|
71
|
+
const str = data.toString();
|
|
72
|
+
errorOutput += str;
|
|
73
|
+
process.stderr.write(str); // Still show the error to the user
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
child.on("close", (code) => {
|
|
114
77
|
stopLoading();
|
|
115
|
-
|
|
116
|
-
|
|
78
|
+
|
|
79
|
+
// Strict failure check:
|
|
80
|
+
// 1. Non-zero exit code
|
|
81
|
+
// 2. Presence of "ErrorEvent" or "ENOTFOUND" in stderr (Neon driver specific)
|
|
82
|
+
const hasDriverError = /ErrorEvent|ENOTFOUND|failed to connect/i.test(errorOutput);
|
|
83
|
+
|
|
84
|
+
if (code !== 0 || hasDriverError) {
|
|
85
|
+
reject(new Error(`${label} failed execution.`));
|
|
86
|
+
} else {
|
|
87
|
+
resolve();
|
|
88
|
+
}
|
|
117
89
|
});
|
|
118
|
-
|
|
90
|
+
|
|
91
|
+
child.on("error", (err) => {
|
|
119
92
|
stopLoading();
|
|
120
|
-
reject(
|
|
93
|
+
reject(err);
|
|
121
94
|
});
|
|
122
95
|
});
|
|
123
96
|
}
|
|
124
97
|
|
|
125
98
|
// ---------------------- ENGINE ----------------------
|
|
126
|
-
let
|
|
127
|
-
|
|
128
|
-
if(running) return;
|
|
129
|
-
running = true;
|
|
130
|
-
|
|
131
|
-
const audit = validateSchema();
|
|
132
|
-
if(!audit.ok){
|
|
133
|
-
console.log(`${T.red}${T.bold}👾 CRITICAL FAILURE:${T.reset} ${audit.msg}`);
|
|
134
|
-
running = false;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
99
|
+
let isRunning = false;
|
|
100
|
+
let isBroken = false;
|
|
137
101
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
{name:"Push Database", cmd:"drizzle-kit push"}
|
|
142
|
-
];
|
|
102
|
+
async function triggerEngine() {
|
|
103
|
+
if (isRunning) return;
|
|
104
|
+
isRunning = true;
|
|
143
105
|
|
|
144
106
|
try {
|
|
145
|
-
|
|
107
|
+
const steps = [
|
|
108
|
+
{ name: "Integrity Check", cmd: "npx drizzle-kit check" },
|
|
109
|
+
{ name: "Generate Migration", cmd: "npx drizzle-kit generate" },
|
|
110
|
+
{ name: "Push Database", cmd: "npx drizzle-kit push" }
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
for (const step of steps) {
|
|
146
114
|
await runCommand(step.cmd, step.name);
|
|
147
115
|
}
|
|
148
|
-
console.log(`${T.lime}${T.bold}🎉 All steps successful!${T.reset}`);
|
|
149
|
-
} catch(err){
|
|
150
|
-
console.log(`${T.red}🚨 ERROR: ${err.message}${T.reset}`);
|
|
151
|
-
}
|
|
152
116
|
|
|
153
|
-
|
|
117
|
+
console.log(`\n${T.lime}${T.bold}🎉 SUCCESS: Database synced.${T.reset}\n`);
|
|
118
|
+
isBroken = false;
|
|
119
|
+
} catch (err) {
|
|
120
|
+
isBroken = true;
|
|
121
|
+
console.log(`\n${T.red}${T.bold}🚨 CRITICAL STOP:${T.reset} ${err.message}`);
|
|
122
|
+
console.log(`${T.yellow}Execution paused. Fix the error and save any file to retry.${T.reset}\n`);
|
|
123
|
+
|
|
124
|
+
if (process.argv.includes('--postinstall')) {
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
isRunning = false;
|
|
129
|
+
}
|
|
154
130
|
}
|
|
155
131
|
|
|
156
|
-
// ---------------------- WATCHER ----------------------
|
|
132
|
+
// ---------------------- MAIN / WATCHER ----------------------
|
|
133
|
+
injectScript();
|
|
134
|
+
|
|
157
135
|
const skipWatcher = process.argv.includes('--postinstall');
|
|
158
136
|
|
|
159
|
-
if(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
137
|
+
if (skipWatcher) {
|
|
138
|
+
triggerEngine();
|
|
139
|
+
} else {
|
|
140
|
+
triggerEngine();
|
|
163
141
|
|
|
142
|
+
try {
|
|
164
143
|
const chokidar = require("chokidar");
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
let debounceTimer;
|
|
145
|
+
|
|
146
|
+
const watcher = chokidar.watch(".", {
|
|
147
|
+
ignored: [/node_modules/, /\.git/, /\.next/, /dist/, /out/],
|
|
148
|
+
ignoreInitial: true,
|
|
149
|
+
usePolling: true,
|
|
150
|
+
interval: 300
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
watcher.on("all", (event, path) => {
|
|
154
|
+
clearTimeout(debounceTimer);
|
|
155
|
+
debounceTimer = setTimeout(() => {
|
|
156
|
+
if (isBroken) console.log(`${T.cyan}🔄 Change detected in ${path}. Retrying...${T.reset}`);
|
|
157
|
+
triggerEngine();
|
|
158
|
+
}, 500);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
console.log(`${T.gray}👀 Watching for schema changes...${T.reset}`);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.log(`${T.yellow}⚠ Chokidar not found. Watch mode disabled.${T.reset}`);
|
|
173
164
|
}
|
|
174
165
|
}
|
|
175
166
|
|
|
176
|
-
// ---------------------- GLOBAL ERROR
|
|
177
|
-
process.on("uncaughtException", err=>{
|
|
178
|
-
console.log(
|
|
179
|
-
|
|
167
|
+
// ---------------------- GLOBAL ERROR CATCH ----------------------
|
|
168
|
+
process.on("uncaughtException", (err) => {
|
|
169
|
+
console.log(`\n${T.red}${T.bold}🛡️ UNCAUGHT ERROR:${T.reset} ${err.message}`);
|
|
170
|
+
process.exit(1);
|
|
180
171
|
});
|
|
181
172
|
|
|
182
|
-
process.on("SIGINT", ()=>{
|
|
183
|
-
console.log(
|
|
173
|
+
process.on("SIGINT", () => {
|
|
174
|
+
console.log(`\n${T.yellow}Shutting down Drizzle-Auto...${T.reset}`);
|
|
184
175
|
process.exit(0);
|
|
185
|
-
});
|
|
176
|
+
});
|