@take-out/scripts 0.4.0 → 0.4.1-1775539666978
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/package.json +3 -3
- package/src/env-update.ts +10 -16
- package/src/helpers/deploy-lock.ts +50 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@take-out/scripts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1-1775539666978",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/cmd.ts",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@clack/prompts": "^0.8.2",
|
|
32
|
-
"@take-out/helpers": "0.4.
|
|
33
|
-
"@take-out/run": "0.4.
|
|
32
|
+
"@take-out/helpers": "0.4.1-1775539666978",
|
|
33
|
+
"@take-out/run": "0.4.1-1775539666978",
|
|
34
34
|
"picocolors": "^1.1.1"
|
|
35
35
|
}
|
|
36
36
|
}
|
package/src/env-update.ts
CHANGED
|
@@ -24,33 +24,27 @@ await cmd`sync environment variables from src/env.ts to matching files`
|
|
|
24
24
|
.run(async ({ args }) => {
|
|
25
25
|
// import env var definitions from src/env.ts (the single source of truth)
|
|
26
26
|
const envModule = await import(path.resolve('src/env.ts'))
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
const production = envModule.production as Record<string, string | symbol>
|
|
28
|
+
const versions = (envModule.versions || {}) as Record<string, string>
|
|
29
29
|
|
|
30
|
-
if (!
|
|
31
|
-
console.error('no
|
|
30
|
+
if (!production || typeof production !== 'object') {
|
|
31
|
+
console.error('no production export found in src/env.ts')
|
|
32
32
|
process.exit(1)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// the expected symbol from @take-out/env
|
|
36
36
|
const expectedSymbol = Symbol.for('take-out/env/expected')
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// merge resolved versions into the var registry
|
|
39
39
|
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
|
|
40
|
-
const depResolved
|
|
41
|
-
for (const [key,
|
|
42
|
-
|
|
43
|
-
if (version) {
|
|
44
|
-
depResolved[key] = version
|
|
45
|
-
// add dep-resolved vars to envVars with resolved value
|
|
46
|
-
envVars[key] = version
|
|
47
|
-
}
|
|
40
|
+
const depResolved = { ...versions }
|
|
41
|
+
for (const [key, value] of Object.entries(versions)) {
|
|
42
|
+
production[key] = value
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
// normalize
|
|
51
|
-
// for compatibility with strategy functions
|
|
45
|
+
// normalize: expected symbol → required, string → defaultValue
|
|
52
46
|
type EnvEntry = { key: string; required: boolean; defaultValue: string }
|
|
53
|
-
const entries: EnvEntry[] = Object.entries(
|
|
47
|
+
const entries: EnvEntry[] = Object.entries(production)
|
|
54
48
|
.map(([key, value]) => ({
|
|
55
49
|
key,
|
|
56
50
|
required: value === expectedSymbol,
|
|
@@ -56,3 +56,53 @@ export async function releaseDeployLock(
|
|
|
56
56
|
// best-effort, lock will auto-expire
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* run `fn` with the deploy lock held, releasing on ANY exit path —
|
|
62
|
+
* normal return, thrown error, or process signal (SIGTERM from CI cancel,
|
|
63
|
+
* SIGINT from ctrl-c, SIGHUP from terminal close).
|
|
64
|
+
*
|
|
65
|
+
* prefer this over raw acquire/release: a bare try/finally does NOT catch
|
|
66
|
+
* signals, so a CI-cancelled deploy would leak the lock for 15 minutes and
|
|
67
|
+
* block the next run. this helper installs signal handlers that release
|
|
68
|
+
* before exit, then removes them once `fn` completes.
|
|
69
|
+
*/
|
|
70
|
+
export async function withDeployLock<T>(
|
|
71
|
+
ssh: string,
|
|
72
|
+
fn: () => Promise<T>,
|
|
73
|
+
opts?: DeployLockOptions,
|
|
74
|
+
): Promise<T> {
|
|
75
|
+
await acquireDeployLock(ssh, opts)
|
|
76
|
+
|
|
77
|
+
let released = false
|
|
78
|
+
const release = async () => {
|
|
79
|
+
if (released) return
|
|
80
|
+
released = true
|
|
81
|
+
await releaseDeployLock(ssh, opts)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// signal handlers — fire-and-forget release with a safety timeout so the
|
|
85
|
+
// process always exits even if the ssh release hangs
|
|
86
|
+
const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP']
|
|
87
|
+
const handlers = new Map<NodeJS.Signals, () => void>()
|
|
88
|
+
for (const sig of signals) {
|
|
89
|
+
const handler = () => {
|
|
90
|
+
const done = release().catch(() => {})
|
|
91
|
+
const timeout = new Promise<void>((r) => setTimeout(r, 5_000))
|
|
92
|
+
Promise.race([done, timeout]).finally(() => {
|
|
93
|
+
process.exit(sig === 'SIGINT' ? 130 : 143)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
handlers.set(sig, handler)
|
|
97
|
+
process.once(sig, handler)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return await fn()
|
|
102
|
+
} finally {
|
|
103
|
+
for (const [sig, handler] of handlers) {
|
|
104
|
+
process.off(sig, handler)
|
|
105
|
+
}
|
|
106
|
+
await release()
|
|
107
|
+
}
|
|
108
|
+
}
|