onework 0.1.0-alpha.0 → 0.1.0-alpha.2

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.
@@ -0,0 +1,151 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { spawn } from 'node:child_process'
3
+ import { createRequire } from 'node:module'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
6
+
7
+ import {
8
+ readPublishedPackageVersionMetadata,
9
+ resolvePackageLookupTimeoutMs,
10
+ resolvePackageManagerEnv,
11
+ resolvePackageTag,
12
+ shouldUseCachedPackageVersionFirst,
13
+ writePublishedPackageVersionMetadata
14
+ } from './npm-package-cache'
15
+ import { findInstalledPublishedPackageVersion } from './npm-package-install'
16
+ import { runBufferedCommand } from './process-utils'
17
+
18
+ const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm'
19
+
20
+ export { resolvePackageManagerEnv } from './npm-package-cache'
21
+ export { installPublishedPackage, resolvePackageBinEntrypoint } from './npm-package-install'
22
+
23
+ const resolveRefreshWorkerEntrypoint = () => {
24
+ const requireFromHere = createRequire(import.meta.url)
25
+ const packageJsonPath = requireFromHere.resolve('oneworks/package.json')
26
+ return path.join(path.dirname(packageJsonPath), 'package-version-refresh-worker.cjs')
27
+ }
28
+
29
+ const spawnPackageVersionRefresh = (packageName: string) => {
30
+ if (process.env.ONEWORKS_BOOTSTRAP_DISABLE_BACKGROUND_REFRESH === '1') {
31
+ return
32
+ }
33
+
34
+ try {
35
+ const payload = Buffer.from(JSON.stringify({ packageName }), 'utf8').toString('base64url')
36
+ const child = spawn(
37
+ process.execPath,
38
+ [
39
+ resolveRefreshWorkerEntrypoint(),
40
+ payload
41
+ ],
42
+ {
43
+ cwd: process.cwd(),
44
+ detached: true,
45
+ env: resolvePackageManagerEnv(),
46
+ stdio: 'ignore'
47
+ }
48
+ )
49
+ child.unref()
50
+ } catch {
51
+ // Keep bootstrap startup independent from background metadata refresh.
52
+ }
53
+ }
54
+
55
+ const resolvePublishedPackageVersionFromRegistry = async (
56
+ packageName: string,
57
+ options: { timeoutMs?: number } = {}
58
+ ) => {
59
+ const spec = `${packageName}@${resolvePackageTag()}`
60
+ const result = await runBufferedCommand({
61
+ command: NPM_BIN,
62
+ args: ['view', spec, 'version', '--json'],
63
+ env: resolvePackageManagerEnv(),
64
+ timeoutMs: options.timeoutMs
65
+ })
66
+
67
+ if (result.timedOut === true) {
68
+ return {
69
+ spec,
70
+ timedOut: true as const
71
+ }
72
+ }
73
+
74
+ if (result.code !== 0) {
75
+ throw new Error(`Failed to resolve published version for ${spec}:\n${result.stderr.trim()}`)
76
+ }
77
+
78
+ const normalizedOutput = result.stdout.trim()
79
+ if (!normalizedOutput) {
80
+ throw new Error(`No version was returned for ${spec}.`)
81
+ }
82
+
83
+ try {
84
+ const parsed = JSON.parse(normalizedOutput) as unknown
85
+ if (typeof parsed === 'string' && parsed.trim()) {
86
+ return {
87
+ spec,
88
+ version: parsed.trim()
89
+ }
90
+ }
91
+ } catch {
92
+ // fall through
93
+ }
94
+
95
+ const unquotedOutput = normalizedOutput.replace(/^"|"$/g, '').trim()
96
+ if (!unquotedOutput) {
97
+ throw new Error(`Invalid published version for ${spec}: ${normalizedOutput}`)
98
+ }
99
+
100
+ return {
101
+ spec,
102
+ version: unquotedOutput
103
+ }
104
+ }
105
+
106
+ export const resolvePublishedPackageVersion = async (
107
+ packageName: string,
108
+ options: { cacheFirst?: boolean } = {}
109
+ ) => {
110
+ const cachedMetadata = await readPublishedPackageVersionMetadata(packageName)
111
+ if (cachedMetadata != null && (options.cacheFirst ?? shouldUseCachedPackageVersionFirst())) {
112
+ spawnPackageVersionRefresh(packageName)
113
+ return cachedMetadata.version
114
+ }
115
+
116
+ const cachedInstalledVersion = await findInstalledPublishedPackageVersion(packageName)
117
+ if (cachedInstalledVersion != null && (options.cacheFirst ?? shouldUseCachedPackageVersionFirst())) {
118
+ spawnPackageVersionRefresh(packageName)
119
+ return cachedInstalledVersion
120
+ }
121
+
122
+ const registryResult = await resolvePublishedPackageVersionFromRegistry(
123
+ packageName,
124
+ cachedMetadata == null
125
+ ? {}
126
+ : {
127
+ timeoutMs: resolvePackageLookupTimeoutMs()
128
+ }
129
+ )
130
+
131
+ if ('version' in registryResult) {
132
+ await writePublishedPackageVersionMetadata(packageName, registryResult.version)
133
+ return registryResult.version
134
+ }
135
+
136
+ if (cachedMetadata == null) {
137
+ // This is not expected because uncached lookups do not use a timeout.
138
+ const retryResult = await resolvePublishedPackageVersionFromRegistry(packageName)
139
+ if ('version' in retryResult) {
140
+ await writePublishedPackageVersionMetadata(packageName, retryResult.version)
141
+ return retryResult.version
142
+ }
143
+ throw new Error(`Failed to resolve published version for ${retryResult.spec}.`)
144
+ }
145
+
146
+ console.error(
147
+ `[bootstrap] npm view ${registryResult.spec} timed out after ${resolvePackageLookupTimeoutMs()}ms, using cached ${packageName}@${cachedMetadata.version}`
148
+ )
149
+ spawnPackageVersionRefresh(packageName)
150
+ return cachedMetadata.version
151
+ }
@@ -0,0 +1,23 @@
1
+ let packageJson: Record<string, unknown> | undefined
2
+
3
+ const getPackageJson = () => {
4
+ if (!packageJson) {
5
+ try {
6
+ // eslint-disable-next-line ts/no-require-imports
7
+ packageJson = require('../package.json') as Record<string, unknown>
8
+ } catch {
9
+ packageJson = {}
10
+ }
11
+ }
12
+
13
+ return packageJson
14
+ }
15
+
16
+ export const getBootstrapPackageConfig = (key: string, defaultValue = '') => {
17
+ const value = getPackageJson()[key]
18
+ return typeof value === 'string' ? value : defaultValue
19
+ }
20
+
21
+ export const getBootstrapVersion = () => getBootstrapPackageConfig('version', '0.0.0')
22
+
23
+ export const getBootstrapDescription = () => getBootstrapPackageConfig('description', 'One Works bootstrap launcher')
@@ -0,0 +1,85 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import { readCliAdapterPackageRequest, resolveCliAdapterPackageDir } from './adapter-package-cache'
6
+ import { installPublishedPackage, resolvePackageBinEntrypoint, resolvePublishedPackageVersion } from './npm-package'
7
+ import { resolvePackageCacheDir, resolvePackageInstallDir, sanitizePackageName } from './npm-package-cache'
8
+ import { resolveBootstrapDataDir } from './paths'
9
+ import { runNodeEntrypoint } from './process-utils'
10
+
11
+ export interface LaunchInstalledPackageOptions {
12
+ commandName?: string
13
+ forwardedArgs: string[]
14
+ packageName: string
15
+ }
16
+
17
+ const normalizeRuntimePackageDir = (value: string | undefined) => {
18
+ const trimmed = value?.trim()
19
+ return trimmed == null || trimmed === '' ? undefined : path.resolve(trimmed)
20
+ }
21
+
22
+ export const shouldResolveCliAdapterPackage = () => {
23
+ const cliPackageDir = normalizeRuntimePackageDir(process.env.__ONEWORKS_PROJECT_CLI_PACKAGE_DIR__)
24
+ const packageDir = normalizeRuntimePackageDir(process.env.__ONEWORKS_PROJECT_PACKAGE_DIR__)
25
+ return cliPackageDir == null || cliPackageDir === packageDir
26
+ }
27
+
28
+ const readActivePackageVersion = (packageName: string) => {
29
+ try {
30
+ const metadataPath = path.join(
31
+ resolveBootstrapDataDir(),
32
+ 'module-updates',
33
+ `${sanitizePackageName(packageName)}.json`
34
+ )
35
+ const parsed = JSON.parse(readFileSync(metadataPath, 'utf8')) as {
36
+ packageName?: unknown
37
+ version?: unknown
38
+ }
39
+ if (parsed.packageName !== packageName || typeof parsed.version !== 'string') {
40
+ return undefined
41
+ }
42
+
43
+ const packageDir = resolvePackageInstallDir(resolvePackageCacheDir(packageName, parsed.version), packageName)
44
+ const packageJson = JSON.parse(readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as {
45
+ name?: unknown
46
+ version?: unknown
47
+ }
48
+ return packageJson.name === packageName && packageJson.version === parsed.version
49
+ ? parsed.version
50
+ : undefined
51
+ } catch {
52
+ return undefined
53
+ }
54
+ }
55
+
56
+ export const launchInstalledPackage = async (input: LaunchInstalledPackageOptions) => {
57
+ const version = readActivePackageVersion(input.packageName) ?? await resolvePublishedPackageVersion(input.packageName)
58
+ console.error(`[bootstrap] using ${input.packageName}@${version}`)
59
+ const installedPackage = await installPublishedPackage(input.packageName, version)
60
+ const entryPath = await resolvePackageBinEntrypoint(installedPackage.packageDir, input.commandName)
61
+ const adapterPackageRequest = input.packageName === '@oneworks/cli' &&
62
+ shouldResolveCliAdapterPackage()
63
+ ? readCliAdapterPackageRequest(
64
+ input.forwardedArgs,
65
+ version,
66
+ process.env.__ONEWORKS_RUNTIME_PROTOCOL_CONSUMER_ADAPTER__
67
+ )
68
+ : undefined
69
+ const adapterPackageDir = adapterPackageRequest == null
70
+ ? undefined
71
+ : await resolveCliAdapterPackageDir(adapterPackageRequest)
72
+
73
+ return await runNodeEntrypoint(
74
+ entryPath,
75
+ input.forwardedArgs,
76
+ adapterPackageDir == null
77
+ ? {}
78
+ : {
79
+ env: {
80
+ ...process.env,
81
+ __ONEWORKS_PROJECT_CLI_PACKAGE_DIR__: adapterPackageDir
82
+ }
83
+ }
84
+ )
85
+ }
package/src/paths.ts ADDED
@@ -0,0 +1,19 @@
1
+ import os from 'node:os'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ export const resolveRealHomeDir = () => {
6
+ const realHome = process.env.__ONEWORKS_PROJECT_REAL_HOME__?.trim()
7
+ if (realHome) {
8
+ return realHome
9
+ }
10
+
11
+ return os.homedir()
12
+ }
13
+
14
+ export const resolveBootstrapDataDir = () => path.join(resolveRealHomeDir(), '.oneworks', 'bootstrap')
15
+
16
+ export const resolveBootstrapPackageCacheDir = () => {
17
+ const packageCacheDir = process.env.__ONEWORKS_PROJECT_PACKAGE_CACHE_DIR__?.trim()
18
+ return packageCacheDir || resolveBootstrapDataDir()
19
+ }
@@ -0,0 +1,126 @@
1
+ import type { Buffer } from 'node:buffer'
2
+ import { spawn } from 'node:child_process'
3
+ import process from 'node:process'
4
+
5
+ interface RunBufferedCommandOptions {
6
+ args: string[]
7
+ command: string
8
+ cwd?: string
9
+ env?: NodeJS.ProcessEnv
10
+ stdio?: 'ignore' | 'inherit' | 'pipe'
11
+ timeoutMs?: number
12
+ }
13
+
14
+ export const runBufferedCommand = async (input: RunBufferedCommandOptions) => {
15
+ const child = spawn(input.command, input.args, {
16
+ cwd: input.cwd,
17
+ env: input.env,
18
+ stdio: input.stdio ?? 'pipe'
19
+ })
20
+
21
+ let stdout = ''
22
+ let stderr = ''
23
+ let timedOut = false
24
+ let killTimeout: NodeJS.Timeout | undefined
25
+ const timeout = input.timeoutMs != null && input.timeoutMs > 0
26
+ ? setTimeout(() => {
27
+ timedOut = true
28
+ child.kill('SIGTERM')
29
+ killTimeout = setTimeout(() => {
30
+ if (child.exitCode == null && child.signalCode == null) {
31
+ child.kill('SIGKILL')
32
+ }
33
+ }, 1_000)
34
+ killTimeout.unref()
35
+ }, input.timeoutMs)
36
+ : undefined
37
+
38
+ if (input.stdio !== 'inherit') {
39
+ child.stdout?.on('data', (chunk: Buffer | string) => {
40
+ stdout += String(chunk)
41
+ })
42
+ child.stderr?.on('data', (chunk: Buffer | string) => {
43
+ stderr += String(chunk)
44
+ })
45
+ }
46
+
47
+ return await new Promise<{
48
+ code: number
49
+ stderr: string
50
+ timedOut?: boolean
51
+ stdout: string
52
+ }>((resolve, reject) => {
53
+ child.once('error', (error) => {
54
+ if (timeout != null) {
55
+ clearTimeout(timeout)
56
+ }
57
+ if (killTimeout != null) {
58
+ clearTimeout(killTimeout)
59
+ }
60
+ reject(error)
61
+ })
62
+ child.once('exit', (code) => {
63
+ if (timeout != null) {
64
+ clearTimeout(timeout)
65
+ }
66
+ if (killTimeout != null) {
67
+ clearTimeout(killTimeout)
68
+ }
69
+ resolve({
70
+ code: code ?? 0,
71
+ stderr,
72
+ timedOut,
73
+ stdout
74
+ })
75
+ })
76
+ })
77
+ }
78
+
79
+ export const runNodeEntrypoint = async (
80
+ entryPath: string,
81
+ forwardedArgs: string[],
82
+ options: { env?: NodeJS.ProcessEnv } = {}
83
+ ) => {
84
+ const child = spawn(process.execPath, [entryPath, ...forwardedArgs], {
85
+ cwd: process.cwd(),
86
+ env: options.env ?? process.env,
87
+ stdio: 'inherit'
88
+ })
89
+
90
+ const forwardSignal = (signal: NodeJS.Signals) => {
91
+ if (!child.killed) {
92
+ child.kill(signal)
93
+ }
94
+ }
95
+
96
+ const handleSigint = () => {
97
+ forwardSignal('SIGINT')
98
+ }
99
+
100
+ const handleSigterm = () => {
101
+ forwardSignal('SIGTERM')
102
+ }
103
+
104
+ const cleanup = () => {
105
+ process.off('SIGINT', handleSigint)
106
+ process.off('SIGTERM', handleSigterm)
107
+ }
108
+
109
+ process.on('SIGINT', handleSigint)
110
+ process.on('SIGTERM', handleSigterm)
111
+
112
+ return await new Promise<number>((resolve, reject) => {
113
+ child.once('error', (error) => {
114
+ cleanup()
115
+ reject(error)
116
+ })
117
+ child.once('exit', (code, signal) => {
118
+ cleanup()
119
+ if (signal != null) {
120
+ process.kill(process.pid, signal)
121
+ return
122
+ }
123
+ resolve(code ?? 0)
124
+ })
125
+ })
126
+ }
package/src/program.ts ADDED
@@ -0,0 +1,286 @@
1
+ /* eslint-disable max-lines -- bootstrap CLI command routing is intentionally centralized. */
2
+ import { Command } from 'commander'
3
+ import process from 'node:process'
4
+
5
+ import { launchDesktopApp } from './desktop-app'
6
+ import type { DesktopInstallMode, LaunchDesktopAppOptions } from './desktop-app'
7
+ import { getBootstrapDescription, getBootstrapVersion } from './package-config'
8
+ import { launchInstalledPackage } from './package-launcher'
9
+ import { checkRuntimePackage, formatRuntimePackageStatus, installRuntimePackage } from './runtime-package'
10
+ import type { RuntimePackageAction, RuntimePackageOptions, RuntimePackageStatus } from './runtime-package'
11
+
12
+ interface BootstrapCliDeps {
13
+ checkRuntimePackage: (target?: string, options?: RuntimePackageOptions) => Promise<RuntimePackageStatus>
14
+ installRuntimePackage: (target?: string, options?: RuntimePackageOptions) => Promise<RuntimePackageStatus>
15
+ launchDesktopApp: (options: LaunchDesktopAppOptions) => Promise<void>
16
+ launchInstalledPackage: (input: {
17
+ commandName?: string
18
+ forwardedArgs: string[]
19
+ packageName: string
20
+ }) => Promise<number>
21
+ }
22
+
23
+ type BootstrapTarget =
24
+ | {
25
+ forwardedArgs: string[]
26
+ installMode?: DesktopInstallMode
27
+ kind: 'desktop'
28
+ persistInstallMode?: boolean
29
+ }
30
+ | {
31
+ commandName?: string
32
+ forwardedArgs: string[]
33
+ kind: 'package'
34
+ packageName: string
35
+ }
36
+ | {
37
+ action: RuntimePackageAction
38
+ json: boolean
39
+ kind: 'runtime-package'
40
+ target?: string
41
+ version?: string
42
+ }
43
+
44
+ const DEFAULT_DEPS: BootstrapCliDeps = {
45
+ checkRuntimePackage,
46
+ installRuntimePackage,
47
+ launchDesktopApp,
48
+ launchInstalledPackage
49
+ }
50
+
51
+ const parseDesktopTarget = (args: string[]): BootstrapTarget => {
52
+ const forwardedArgs = [...args]
53
+ let installMode: DesktopInstallMode | undefined
54
+ let persistInstallMode = false
55
+
56
+ if (forwardedArgs[0] === 'cache') {
57
+ forwardedArgs.shift()
58
+ installMode = 'cache'
59
+ persistInstallMode = true
60
+ }
61
+
62
+ const noCacheIndex = forwardedArgs.indexOf('--no-cache')
63
+ if (noCacheIndex >= 0) {
64
+ forwardedArgs.splice(noCacheIndex, 1)
65
+ installMode = 'user'
66
+ persistInstallMode = true
67
+ }
68
+
69
+ return {
70
+ forwardedArgs,
71
+ installMode,
72
+ kind: 'desktop',
73
+ persistInstallMode
74
+ }
75
+ }
76
+
77
+ const parseRuntimePackageTarget = (args: string[]): BootstrapTarget => {
78
+ const forwardedArgs = [...args]
79
+ const jsonIndex = forwardedArgs.indexOf('--json')
80
+ const json = jsonIndex >= 0
81
+ if (json) {
82
+ forwardedArgs.splice(jsonIndex, 1)
83
+ }
84
+
85
+ const action = forwardedArgs.shift()
86
+ if (action !== 'check' && action !== 'install') {
87
+ throw new Error('Runtime package command must be `check` or `install`.')
88
+ }
89
+
90
+ let version: string | undefined
91
+ for (let index = 0; index < forwardedArgs.length; index += 1) {
92
+ const arg = forwardedArgs[index]
93
+ if (arg === '--version') {
94
+ const value = forwardedArgs[index + 1]
95
+ if (value == null || value.trim() === '') {
96
+ throw new Error('Runtime package --version requires a value.')
97
+ }
98
+ version = value
99
+ forwardedArgs.splice(index, 2)
100
+ index -= 1
101
+ continue
102
+ }
103
+
104
+ if (arg?.startsWith('--version=')) {
105
+ const value = arg.slice('--version='.length)
106
+ if (value.trim() === '') {
107
+ throw new Error('Runtime package --version requires a value.')
108
+ }
109
+ version = value
110
+ forwardedArgs.splice(index, 1)
111
+ index -= 1
112
+ }
113
+ }
114
+
115
+ const targetSelector = forwardedArgs.shift()
116
+ if (forwardedArgs.length > 0) {
117
+ throw new Error(`Unsupported runtime package arguments: ${forwardedArgs.join(' ')}`)
118
+ }
119
+
120
+ const selectorAtIndex = targetSelector?.lastIndexOf('@') ?? -1
121
+ const selectorTarget = selectorAtIndex > 0 ? targetSelector?.slice(0, selectorAtIndex) : targetSelector
122
+ const selectorVersion = selectorAtIndex > 0 ? targetSelector?.slice(selectorAtIndex + 1) : undefined
123
+ if (selectorVersion != null && selectorVersion.trim() === '') {
124
+ throw new Error(`Runtime package target selector requires a version: ${targetSelector}.`)
125
+ }
126
+ if (version != null && selectorVersion != null && version !== selectorVersion) {
127
+ throw new Error(`Conflicting runtime package versions: ${selectorVersion} and ${version}.`)
128
+ }
129
+ const resolvedVersion = version ?? selectorVersion
130
+
131
+ return {
132
+ action,
133
+ json,
134
+ kind: 'runtime-package',
135
+ target: selectorTarget,
136
+ ...(resolvedVersion != null ? { version: resolvedVersion } : {})
137
+ }
138
+ }
139
+
140
+ export const routeBootstrapCommand = (command: string | undefined, args: string[]): BootstrapTarget | undefined => {
141
+ if (command == null || command.trim() === '') {
142
+ return undefined
143
+ }
144
+
145
+ if (command === 'web') {
146
+ return {
147
+ commandName: 'oneworks-web',
148
+ forwardedArgs: args,
149
+ kind: 'package',
150
+ packageName: '@oneworks/web'
151
+ }
152
+ }
153
+
154
+ if (command === 'server') {
155
+ return {
156
+ commandName: 'oneworks-server',
157
+ forwardedArgs: args,
158
+ kind: 'package',
159
+ packageName: '@oneworks/server'
160
+ }
161
+ }
162
+
163
+ if (command === 'runtime') {
164
+ return parseRuntimePackageTarget(args)
165
+ }
166
+
167
+ if (command === 'app') {
168
+ return parseDesktopTarget(args)
169
+ }
170
+
171
+ if (command === 'cli') {
172
+ return {
173
+ commandName: 'oneworks',
174
+ forwardedArgs: args,
175
+ kind: 'package',
176
+ packageName: '@oneworks/cli'
177
+ }
178
+ }
179
+
180
+ return {
181
+ commandName: 'oneworks',
182
+ forwardedArgs: [command, ...args],
183
+ kind: 'package',
184
+ packageName: '@oneworks/cli'
185
+ }
186
+ }
187
+
188
+ export const createBootstrapCli = (inputDeps: Partial<BootstrapCliDeps> = {}) => {
189
+ const deps: BootstrapCliDeps = {
190
+ ...DEFAULT_DEPS,
191
+ ...inputDeps
192
+ }
193
+ const program = new Command()
194
+
195
+ program
196
+ .name('oneworks')
197
+ .description(getBootstrapDescription())
198
+ .helpOption(false)
199
+ .allowUnknownOption(true)
200
+ .allowExcessArguments(true)
201
+ .argument('[command]', 'Reserved commands: web, server, app, cli')
202
+ .argument('[args...]', 'Forwarded arguments for the delegated runtime')
203
+ .showHelpAfterError()
204
+ .addHelpText(
205
+ 'after',
206
+ `
207
+ Top-level flags:
208
+ --help, -h Show bootstrap help
209
+ --version, -V Print bootstrap version
210
+
211
+ Examples:
212
+ npx oneworks summarize the repo
213
+ npx oneworks web --port 8787
214
+ npx oneworks server --host 0.0.0.0 --allow-cors
215
+ npx oneworks runtime check cli
216
+ npx oneworks runtime check cli@0.1.0-alpha.0
217
+ npx oneworks runtime install server
218
+ npx oneworks runtime install server --version 0.1.0-alpha.0
219
+ npx oneworks app
220
+ npx oneworks app cache
221
+ npx oneworks app --no-cache
222
+ cd ~/work/project-a && npx oneworks app
223
+ cd ~/work/project-b && npx oneworks app
224
+ `
225
+ )
226
+ .action(async (command: string | undefined, args: string[] = []) => {
227
+ const target = routeBootstrapCommand(command, args)
228
+ if (target == null) {
229
+ program.outputHelp()
230
+ return
231
+ }
232
+
233
+ if (target.kind === 'desktop') {
234
+ await deps.launchDesktopApp({
235
+ forwardedArgs: target.forwardedArgs,
236
+ installMode: target.installMode,
237
+ persistInstallMode: target.persistInstallMode
238
+ })
239
+ return
240
+ }
241
+
242
+ if (target.kind === 'runtime-package') {
243
+ let status: RuntimePackageStatus
244
+ if (target.action === 'install') {
245
+ status = target.version == null
246
+ ? await deps.installRuntimePackage(target.target)
247
+ : await deps.installRuntimePackage(target.target, { version: target.version })
248
+ } else {
249
+ status = target.version == null
250
+ ? await deps.checkRuntimePackage(target.target)
251
+ : await deps.checkRuntimePackage(target.target, { version: target.version })
252
+ }
253
+ const output = target.json ? JSON.stringify(status) : formatRuntimePackageStatus(status)
254
+ process.stdout.write(`${output}\n`)
255
+ return
256
+ }
257
+
258
+ const exitCode = await deps.launchInstalledPackage({
259
+ packageName: target.packageName,
260
+ commandName: target.commandName,
261
+ forwardedArgs: target.forwardedArgs
262
+ })
263
+
264
+ if (exitCode !== 0) {
265
+ process.exit(exitCode)
266
+ }
267
+ })
268
+
269
+ return program
270
+ }
271
+
272
+ export const runBootstrapCli = async (argv = process.argv) => {
273
+ const command = createBootstrapCli()
274
+ const userArgs = argv.slice(2)
275
+ if (userArgs.length === 0 || (userArgs.length === 1 && ['-h', '--help'].includes(userArgs[0] ?? ''))) {
276
+ command.outputHelp()
277
+ return
278
+ }
279
+
280
+ if (userArgs.length === 1 && ['-V', '--version'].includes(userArgs[0] ?? '')) {
281
+ process.stdout.write(`${getBootstrapVersion()}\n`)
282
+ return
283
+ }
284
+
285
+ await command.parseAsync(argv)
286
+ }