@take-out/scripts 0.1.15 → 0.1.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@take-out/scripts",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "type": "module",
5
5
  "main": "./src/run.ts",
6
6
  "sideEffects": false,
@@ -29,11 +29,11 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^0.8.2",
32
- "@take-out/helpers": "0.1.15",
32
+ "@take-out/helpers": "0.1.16",
33
33
  "picocolors": "^1.1.1"
34
34
  },
35
35
  "peerDependencies": {
36
- "vxrn": "^1.6.13"
36
+ "vxrn": "^1.6.16"
37
37
  },
38
38
  "peerDependenciesMeta": {
39
39
  "vxrn": {
@@ -41,6 +41,6 @@
41
41
  }
42
42
  },
43
43
  "devDependencies": {
44
- "vxrn": "1.6.13"
44
+ "vxrn": "1.6.16"
45
45
  }
46
46
  }
@@ -37,7 +37,7 @@ export async function execWithEnvironment(
37
37
  ...(USE_LOCAL_SERVER && {
38
38
  ONE_SERVER_URL: devEnv?.ONE_SERVER_URL,
39
39
  // vite reads from .env.production for us unless defined into process.env
40
- VITE_ZERO_HOST: 'start.chat',
40
+ VITE_ZERO_HOSTNAME: 'start.chat',
41
41
  }),
42
42
  } as any as Record<string, string>
43
43
 
@@ -0,0 +1,58 @@
1
+ /**
2
+ * deploy lock helper — prevents concurrent deploys with auto-expiry
3
+ * shared across repos via @take-out/scripts/helpers/deploy-lock
4
+ *
5
+ * uses mkdir for atomic lock acquisition (no race window)
6
+ */
7
+
8
+ import { run } from './run'
9
+
10
+ const DEFAULT_LOCK_PATH = '/tmp/deploy.lock'
11
+ const DEFAULT_MAX_AGE_MIN = 15
12
+
13
+ interface DeployLockOptions {
14
+ path?: string
15
+ maxAgeMin?: number
16
+ }
17
+
18
+ export async function acquireDeployLock(
19
+ ssh: string,
20
+ opts?: DeployLockOptions
21
+ ): Promise<void> {
22
+ const lockPath = opts?.path || DEFAULT_LOCK_PATH
23
+ const maxAge = opts?.maxAgeMin || DEFAULT_MAX_AGE_MIN
24
+
25
+ // mkdir is atomic — if two deploys race, only one succeeds
26
+ const lockCmd = [
27
+ // clean stale locks first
28
+ `find ${lockPath} -maxdepth 0 -mmin +${maxAge} -exec rm -rf {} \\; 2>/dev/null || true`,
29
+ // atomic acquire via mkdir (fails if dir already exists)
30
+ `mkdir ${lockPath} 2>/dev/null && echo "OK" || echo "LOCKED"`,
31
+ ].join('; ')
32
+
33
+ const { stdout } = await run(`${ssh} "${lockCmd}"`, {
34
+ captureOutput: true,
35
+ silent: true,
36
+ })
37
+ const result = stdout.trim()
38
+
39
+ if (result === 'LOCKED') {
40
+ throw new Error(
41
+ `another deploy is in progress (${lockPath} exists on server). ` +
42
+ `if stale, it will auto-expire after ${maxAge} minutes, ` +
43
+ `or remove manually: ${ssh} "rm -rf ${lockPath}"`
44
+ )
45
+ }
46
+ }
47
+
48
+ export async function releaseDeployLock(
49
+ ssh: string,
50
+ opts?: DeployLockOptions
51
+ ): Promise<void> {
52
+ const lockPath = opts?.path || DEFAULT_LOCK_PATH
53
+ try {
54
+ await run(`${ssh} "rm -rf ${lockPath}"`, { silent: true })
55
+ } catch {
56
+ // best-effort, lock will auto-expire
57
+ }
58
+ }
@@ -29,6 +29,9 @@ export async function getTestEnv() {
29
29
  ZERO_LOG_LEVEL: 'warn',
30
30
  }),
31
31
  DO_NOT_TRACK: '1',
32
+ // force localhost urls so cross-subdomain cookies don't activate in test
33
+ BETTER_AUTH_URL: `http://localhost:${appPort}`,
34
+ ONE_SERVER_URL: `http://localhost:${appPort}`,
32
35
  ZERO_MUTATE_URL: `http://${dockerHost}:${appPort}/api/zero/push`,
33
36
  ZERO_QUERY_URL: `http://${dockerHost}:${appPort}/api/zero/pull`,
34
37
  ZERO_UPSTREAM_DB: `${dockerDbBase}/postgres`,
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * generic uncloud deployment helpers for ci/cd
3
+ * shared across repos via @take-out/scripts/helpers/uncloud-deploy
3
4
  */
4
5
 
5
- import fs from 'node:fs'
6
+ import { existsSync } from 'node:fs'
7
+ import { mkdir, writeFile } from 'node:fs/promises'
6
8
  import { homedir } from 'node:os'
7
9
  import { join } from 'node:path'
8
10
 
@@ -82,26 +84,47 @@ export async function installUncloudCLI(version = '0.16.0') {
82
84
  }
83
85
  }
84
86
 
85
- export async function setupSSHKey() {
86
- if (!process.env.DEPLOY_SSH_KEY) {
87
+ export interface SetupSSHKeyOptions {
88
+ // env var containing the key path or content (default: DEPLOY_SSH_KEY)
89
+ envVar?: string
90
+ // filename to write in ~/.ssh/ (default: uncloud_deploy)
91
+ keyName?: string
92
+ // host to add to known_hosts (default: process.env.DEPLOY_HOST)
93
+ host?: string
94
+ }
95
+
96
+ /**
97
+ * resolve an ssh key from env - handles both file paths (local) and
98
+ * raw/base64 key content (CI). writes to ~/.ssh/{keyName} and updates
99
+ * the env var to point to the file.
100
+ */
101
+ export async function setupSSHKey(options: SetupSSHKeyOptions = {}) {
102
+ const envVar = options.envVar || 'DEPLOY_SSH_KEY'
103
+ const keyName = options.keyName || 'uncloud_deploy'
104
+ const host = options.host || process.env.DEPLOY_HOST
105
+
106
+ if (!process.env[envVar]) {
87
107
  return
88
108
  }
89
109
 
90
- const sshKeyValue = process.env.DEPLOY_SSH_KEY
110
+ // expand ~ to home directory (node doesn't do this automatically)
111
+ const sshKeyValue = process.env[envVar]!.replace(/^~/, homedir())
91
112
 
92
113
  // check if it's a path to an existing file (local usage) or key content (CI usage)
93
- if (fs.existsSync(sshKeyValue)) {
114
+ if (existsSync(sshKeyValue)) {
115
+ // local usage - ensure env has resolved path (not ~ prefix)
116
+ process.env[envVar] = sshKeyValue
94
117
  console.info(` using ssh key from: ${sshKeyValue}`)
95
118
  return
96
119
  }
97
120
 
98
- // CI usage - DEPLOY_SSH_KEY contains the actual key content
121
+ // CI usage - env var contains the actual key content
99
122
  console.info('🔑 setting up ssh key from environment...')
100
123
  const sshDir = join(homedir(), '.ssh')
101
- const keyPath = join(sshDir, 'uncloud_deploy')
124
+ const keyPath = join(sshDir, keyName)
102
125
 
103
- if (!fs.existsSync(sshDir)) {
104
- await fs.promises.mkdir(sshDir, { recursive: true })
126
+ if (!existsSync(sshDir)) {
127
+ await mkdir(sshDir, { recursive: true })
105
128
  }
106
129
 
107
130
  // decode base64-encoded keys (github secrets often store keys as base64)
@@ -119,13 +142,13 @@ export async function setupSSHKey() {
119
142
  keyContent += '\n'
120
143
  }
121
144
 
122
- await fs.promises.writeFile(keyPath, keyContent, { mode: 0o600 })
145
+ await writeFile(keyPath, keyContent, { mode: 0o600 })
123
146
 
124
147
  // add host to known_hosts
125
- if (process.env.DEPLOY_HOST) {
148
+ if (host) {
126
149
  try {
127
150
  await run(
128
- `ssh-keyscan -H ${process.env.DEPLOY_HOST} >> ${join(sshDir, 'known_hosts')}`,
151
+ `ssh-keyscan -H ${host} >> ${join(sshDir, 'known_hosts')}`,
129
152
  { silent: true, timeout: time.ms.seconds(10) }
130
153
  )
131
154
  } catch {
@@ -134,6 +157,6 @@ export async function setupSSHKey() {
134
157
  }
135
158
 
136
159
  // override env var to point to the file we created
137
- process.env.DEPLOY_SSH_KEY = keyPath
160
+ process.env[envVar] = keyPath
138
161
  console.info(` ssh key written to ${keyPath}`)
139
162
  }
package/src/run.ts CHANGED
@@ -261,7 +261,7 @@ const runScript = async (
261
261
  prefixLabel: string = name,
262
262
  restarts = 0,
263
263
  extraArgs: string[] = [],
264
- managedIndex?: number,
264
+ managedIndex?: number
265
265
  ) => {
266
266
  const index = managedIndex ?? managedProcesses.length
267
267
 
@@ -389,9 +389,7 @@ function computeShortcuts() {
389
389
  const lengths = new Array(managedProcesses.length).fill(1) as number[]
390
390
 
391
391
  for (let round = 0; round < 5; round++) {
392
- const shortcuts = initials.map(
393
- (init, i) => init.slice(0, lengths[i]) || init
394
- )
392
+ const shortcuts = initials.map((init, i) => init.slice(0, lengths[i]) || init)
395
393
 
396
394
  let hasCollision = false
397
395
  const groups = new Map<string, number[]>()
@@ -457,7 +455,9 @@ async function killProcess(index: number) {
457
455
  if (!managed) return
458
456
 
459
457
  if (managed.killing) {
460
- console.info(`\x1b[2m ${managed.shortcut} ${managed.prefixLabel} already stopped\x1b[0m`)
458
+ console.info(
459
+ `\x1b[2m ${managed.shortcut} ${managed.prefixLabel} already stopped\x1b[0m`
460
+ )
461
461
  return
462
462
  }
463
463
 
@@ -518,7 +518,9 @@ function setupKeyboardShortcuts() {
518
518
  for (const managed of managedProcesses) {
519
519
  const color = colors[managed.index % colors.length]
520
520
  const stopped = managed.killing ? `${dim} (stopped)` : ''
521
- console.info(`${dim} ${reset}${color}${managed.shortcut}${reset}${dim} ${managed.prefixLabel}${stopped}${reset}`)
521
+ console.info(
522
+ `${dim} ${reset}${color}${managed.shortcut}${reset}${dim} ${managed.prefixLabel}${stopped}${reset}`
523
+ )
522
524
  }
523
525
  console.info()
524
526
  }
@@ -615,7 +617,9 @@ function printShortcutHint() {
615
617
  if (managedProcesses.length === 0) return
616
618
 
617
619
  const dim = '\x1b[2m'
618
- console.info(`${dim} ctrl+r restart · ctrl+k kill · ctrl+l clear · ctrl+c exit${reset}`)
620
+ console.info(
621
+ `${dim} ctrl+r restart · ctrl+k kill · ctrl+l clear · ctrl+c exit${reset}`
622
+ )
619
623
  console.info()
620
624
  }
621
625