@o/scripts 0.16.19
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/LICENSE +21 -0
- package/package.json +57 -0
- package/src/build-initial.ts +93 -0
- package/src/clean.ts +29 -0
- package/src/cmd.ts +105 -0
- package/src/dev-tunnel.ts +162 -0
- package/src/ensure-port.ts +79 -0
- package/src/ensure-tunnel.ts +19 -0
- package/src/ensure-zero-sqlite.ts +31 -0
- package/src/env-pull.ts +58 -0
- package/src/env-update.ts +342 -0
- package/src/exec-with-env.ts +61 -0
- package/src/helpers/args.ts +97 -0
- package/src/helpers/check-port.ts +22 -0
- package/src/helpers/deploy-health.ts +227 -0
- package/src/helpers/deploy-lock.ts +231 -0
- package/src/helpers/ensure-s3-bucket.ts +88 -0
- package/src/helpers/env-load.ts +43 -0
- package/src/helpers/get-docker-host.ts +37 -0
- package/src/helpers/get-test-env.ts +46 -0
- package/src/helpers/github-tail.ts +249 -0
- package/src/helpers/multipass.ts +174 -0
- package/src/helpers/parse-env-file.ts +71 -0
- package/src/helpers/process-compose-env.ts +57 -0
- package/src/helpers/resolve-dep-version.ts +11 -0
- package/src/helpers/run.ts +340 -0
- package/src/helpers/ssh.ts +50 -0
- package/src/helpers/uncloud-deploy.ts +162 -0
- package/src/helpers/uncloud.ts +197 -0
- package/src/helpers/wait-for-port.ts +33 -0
- package/src/helpers/zero-get-version.ts +8 -0
- package/src/release.ts +544 -0
- package/src/sst-get-environment.ts +38 -0
- package/src/typecheck.ts +21 -0
- package/src/up.ts +440 -0
- package/src/update-changelog.ts +92 -0
- package/src/wait-for-dev.ts +41 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { run } from './run'
|
|
6
|
+
|
|
7
|
+
export interface UncloudConfig {
|
|
8
|
+
context?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function ucCmd(config?: UncloudConfig): string {
|
|
12
|
+
return config?.context ? `uc -c ${config.context}` : 'uc'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function checkUncloudCLI(): Promise<void> {
|
|
16
|
+
try {
|
|
17
|
+
// uc has no --version flag; `uc version` is the cobra subcommand
|
|
18
|
+
const { stdout } = await run('uc version', { silent: true, captureOutput: true })
|
|
19
|
+
console.info(`ā
uncloud cli installed (${stdout.trim().split('\n')[0]})`)
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`uncloud cli check failed (install: curl -fsS https://get.uncloud.run/install.sh | sh): ${error}`
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ensureUncloudContext(host: string, sshKey: string, contextName: string): void {
|
|
28
|
+
const configDir = join(homedir(), '.config', 'uncloud')
|
|
29
|
+
const configPath = join(configDir, 'config.yaml')
|
|
30
|
+
|
|
31
|
+
if (!fs.existsSync(configDir)) {
|
|
32
|
+
fs.mkdirSync(configDir, { recursive: true })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// check if config already has context pointing to correct host
|
|
36
|
+
if (fs.existsSync(configPath)) {
|
|
37
|
+
const existing = fs.readFileSync(configPath, 'utf-8')
|
|
38
|
+
const hostname = host.split('@')[1]
|
|
39
|
+
if (hostname && existing.includes(`${contextName}:`) && existing.includes(hostname)) {
|
|
40
|
+
console.info(`ā
uncloud config already has ${contextName} context for ${host}`)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// only create config if it doesn't exist - don't overwrite existing config
|
|
46
|
+
// which may have other contexts
|
|
47
|
+
if (!fs.existsSync(configPath)) {
|
|
48
|
+
const config = `current_context: ${contextName}
|
|
49
|
+
contexts:
|
|
50
|
+
${contextName}:
|
|
51
|
+
connections:
|
|
52
|
+
- ssh: ${host}
|
|
53
|
+
ssh_key_file: ${sshKey}
|
|
54
|
+
`
|
|
55
|
+
fs.writeFileSync(configPath, config)
|
|
56
|
+
console.info(`ā
created uncloud config at ${configPath}`)
|
|
57
|
+
} else {
|
|
58
|
+
console.info(`ā
using existing uncloud config (context: ${contextName})`)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function initUncloud(
|
|
63
|
+
host: string,
|
|
64
|
+
sshKey: string,
|
|
65
|
+
options: { noDNS?: boolean; noCaddy?: boolean; context?: string } = {}
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
console.info('\nš initializing uncloud...\n')
|
|
68
|
+
|
|
69
|
+
const uc = ucCmd(options)
|
|
70
|
+
const contextName = options.context || 'default'
|
|
71
|
+
|
|
72
|
+
// check if we already have local context that works
|
|
73
|
+
try {
|
|
74
|
+
await run(`${uc} ls`, { silent: true })
|
|
75
|
+
console.info('ā
local cluster context exists and connected')
|
|
76
|
+
return
|
|
77
|
+
} catch {
|
|
78
|
+
// no local context - check if server has uncloud running
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sshCmd = `ssh -i ${sshKey} -o StrictHostKeyChecking=no ${host}`
|
|
82
|
+
|
|
83
|
+
// check if server already has uncloud daemon running
|
|
84
|
+
let serverHasUncloud = false
|
|
85
|
+
try {
|
|
86
|
+
await run(`${sshCmd} "systemctl is-active uncloud.service"`, { silent: true })
|
|
87
|
+
serverHasUncloud = true
|
|
88
|
+
console.info('ā
server has uncloud daemon running')
|
|
89
|
+
} catch {
|
|
90
|
+
try {
|
|
91
|
+
await run(`${sshCmd} "test -f /usr/local/bin/uncloudd"`, { silent: true })
|
|
92
|
+
serverHasUncloud = true
|
|
93
|
+
console.info('ā
server has uncloud installed')
|
|
94
|
+
} catch {
|
|
95
|
+
// server doesn't have uncloud - needs fresh init
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (serverHasUncloud) {
|
|
100
|
+
ensureUncloudContext(host, sshKey, contextName)
|
|
101
|
+
console.info(' (skipping init to avoid cluster reset)')
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// fresh server - need to initialize
|
|
106
|
+
console.info('š¦ uncloud not installed on server - initializing...')
|
|
107
|
+
|
|
108
|
+
const flags: string[] = []
|
|
109
|
+
if (options.noDNS) flags.push('--no-dns')
|
|
110
|
+
if (options.noCaddy) flags.push('--no-caddy')
|
|
111
|
+
const flagStr = flags.join(' ')
|
|
112
|
+
|
|
113
|
+
await run(`echo "y" | uc machine init ${host} -i ${sshKey} ${flagStr}`)
|
|
114
|
+
|
|
115
|
+
console.info('ā
uncloud initialized')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function pushImage(
|
|
119
|
+
imageName: string,
|
|
120
|
+
config?: UncloudConfig
|
|
121
|
+
): Promise<void> {
|
|
122
|
+
console.info('\nš¤ pushing image to cluster...\n')
|
|
123
|
+
|
|
124
|
+
await run(`${ucCmd(config)} image push ${imageName}`)
|
|
125
|
+
console.info('ā
image pushed')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function deployStack(
|
|
129
|
+
composeFile: string,
|
|
130
|
+
options?: { profile?: string; service?: string; recreate?: boolean } & UncloudConfig
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
const service = options?.service
|
|
133
|
+
console.info(`\nš¦ deploying ${service ?? 'stack'}...\n`)
|
|
134
|
+
|
|
135
|
+
const profileFlag = options?.profile ? `--profile ${options.profile}` : ''
|
|
136
|
+
// --recreate forces fresh containers even when config is unchanged (picks up the new
|
|
137
|
+
// :latest image). pass recreate:false to let uncloud restart a service ONLY when its
|
|
138
|
+
// image/config actually changed ā used for zero, whose restarts drop every websocket
|
|
139
|
+
// connection and cause a client reconnect storm.
|
|
140
|
+
const recreateFlag = options?.recreate === false ? '' : '--recreate'
|
|
141
|
+
const serviceArg = service ? ` ${service}` : ''
|
|
142
|
+
await run(
|
|
143
|
+
`${ucCmd(options)} deploy -f ${composeFile} ${profileFlag} ${recreateFlag} --yes${serviceArg}`
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
console.info('\nā
deployment complete!')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function showStatus(config?: UncloudConfig): Promise<void> {
|
|
150
|
+
console.info('\nš deployment status:\n')
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
await run(`${ucCmd(config)} ls`)
|
|
154
|
+
} catch {
|
|
155
|
+
console.error('could not fetch status')
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function showContainers(config?: UncloudConfig): Promise<void> {
|
|
160
|
+
console.info('\nš¦ container status:\n')
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
await run(`${ucCmd(config)} ps`)
|
|
164
|
+
} catch {
|
|
165
|
+
console.error('could not fetch container status')
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function startService(
|
|
170
|
+
service?: string,
|
|
171
|
+
config?: UncloudConfig
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
const target = service || 'all services'
|
|
174
|
+
console.info(`\nā¶ļø starting ${target}...\n`)
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await run(`${ucCmd(config)} start${service ? ` ${service}` : ''}`)
|
|
178
|
+
console.info(`ā
${target} started`)
|
|
179
|
+
} catch {
|
|
180
|
+
throw new Error(`failed to start ${target}`)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function stopService(
|
|
185
|
+
service?: string,
|
|
186
|
+
config?: UncloudConfig
|
|
187
|
+
): Promise<void> {
|
|
188
|
+
const target = service || 'all services'
|
|
189
|
+
console.info(`\nā¹ļø stopping ${target}...\n`)
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await run(`${ucCmd(config)} stop${service ? ` ${service}` : ''}`)
|
|
193
|
+
console.info(`ā
${target} stopped`)
|
|
194
|
+
} catch {
|
|
195
|
+
throw new Error(`failed to stop ${target}`)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { checkPort } from './check-port'
|
|
2
|
+
|
|
3
|
+
interface WaitForPortOptions {
|
|
4
|
+
host?: string
|
|
5
|
+
intervalMs?: number
|
|
6
|
+
timeoutMs?: number
|
|
7
|
+
retries?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function waitForPort(
|
|
11
|
+
port: number,
|
|
12
|
+
options: WaitForPortOptions = {}
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
const {
|
|
15
|
+
host = '127.0.0.1',
|
|
16
|
+
intervalMs = 1000,
|
|
17
|
+
timeoutMs = 30000,
|
|
18
|
+
retries = Math.floor(timeoutMs / intervalMs),
|
|
19
|
+
} = options
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < retries; i++) {
|
|
22
|
+
const isOpen = await checkPort(port, host)
|
|
23
|
+
if (isOpen) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (i < retries - 1) {
|
|
28
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
throw new Error(`Port ${port} on ${host} did not become available after ${timeoutMs}ms`)
|
|
33
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
export function getZeroVersion() {
|
|
5
|
+
const packageJsonPath = join(process.cwd(), 'package.json')
|
|
6
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
|
|
7
|
+
return packageJson.dependencies?.['@rocicorp/zero']?.replace(/^[\^~]/, '')
|
|
8
|
+
}
|