oneworks 0.0.0 → 0.1.0-alpha.0
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/__tests__/adapter-package-cache.spec.ts +109 -0
- package/__tests__/cli.spec.ts +210 -0
- package/__tests__/desktop-app.spec.ts +106 -0
- package/__tests__/npm-package.spec.ts +149 -0
- package/__tests__/package-launcher.spec.ts +23 -0
- package/__tests__/runtime-package.spec.ts +211 -0
- package/cli.js +5 -0
- package/package-version-refresh-worker.cjs +324 -0
- package/package.json +26 -13
- package/src/adapter-package-cache.ts +432 -0
- package/src/cli.ts +5 -0
- package/src/desktop-app.ts +44 -0
- package/src/desktop-install.ts +177 -0
- package/src/desktop-mode.ts +92 -0
- package/src/desktop-release.ts +198 -0
- package/src/npm-package-cache.ts +185 -0
- package/src/npm-package-install.ts +139 -0
- package/src/npm-package.ts +151 -0
- package/src/package-config.ts +23 -0
- package/src/package-launcher.ts +85 -0
- package/src/paths.ts +19 -0
- package/src/process-utils.ts +126 -0
- package/src/program.ts +286 -0
- package/src/progress.ts +174 -0
- package/src/runtime-package.ts +126 -0
- package/README.md +0 -3
- package/index.js +0 -3
package/src/progress.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
const BAR_WIDTH = 24
|
|
4
|
+
const PULSE_WIDTH = 7
|
|
5
|
+
const TICK_MS = 120
|
|
6
|
+
const SPINNER_FRAMES = ['-', '\\', '|', '/']
|
|
7
|
+
|
|
8
|
+
export interface BootstrapProgress {
|
|
9
|
+
fail: (summary?: string) => void
|
|
10
|
+
finish: (summary?: string) => void
|
|
11
|
+
setTotal: (total: number | undefined) => void
|
|
12
|
+
update: (completed: number, total?: number) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const isTestRun = () => process.env.VITEST != null || process.env.VITEST_WORKER_ID != null
|
|
16
|
+
|
|
17
|
+
const formatElapsed = (startedAt: number) => {
|
|
18
|
+
const totalSeconds = Math.max(0, Math.floor((Date.now() - startedAt) / 1000))
|
|
19
|
+
const minutes = Math.floor(totalSeconds / 60)
|
|
20
|
+
const seconds = totalSeconds % 60
|
|
21
|
+
return `${minutes}:${seconds.toString().padStart(2, '0')}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const formatBytes = (bytes: number) => {
|
|
25
|
+
const normalized = Math.max(0, bytes)
|
|
26
|
+
if (normalized < 1024) return `${normalized} B`
|
|
27
|
+
|
|
28
|
+
const units = ['KB', 'MB', 'GB', 'TB']
|
|
29
|
+
let value = normalized / 1024
|
|
30
|
+
let unitIndex = 0
|
|
31
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
32
|
+
value /= 1024
|
|
33
|
+
unitIndex += 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return `${value >= 10 ? value.toFixed(1) : value.toFixed(2)} ${units[unitIndex]}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const normalizeTotal = (total: number | undefined) => (
|
|
40
|
+
total != null && Number.isFinite(total) && total > 0 ? total : undefined
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const trimToWidth = (value: string, width: number) => (
|
|
44
|
+
value.length <= width ? value : `${value.slice(0, Math.max(0, width - 3))}...`
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const buildDeterminateBar = (completed: number, total: number) => {
|
|
48
|
+
const ratio = Math.max(0, Math.min(1, completed / total))
|
|
49
|
+
const filled = Math.max(0, Math.min(BAR_WIDTH, Math.round(ratio * BAR_WIDTH)))
|
|
50
|
+
return `${'#'.repeat(filled)}${'.'.repeat(BAR_WIDTH - filled)}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const buildIndeterminateBar = (frame: number) => {
|
|
54
|
+
const position = frame % (BAR_WIDTH + PULSE_WIDTH)
|
|
55
|
+
const start = position - PULSE_WIDTH
|
|
56
|
+
return Array.from({ length: BAR_WIDTH }, (_, index) => (
|
|
57
|
+
index >= start && index < position ? '#' : '.'
|
|
58
|
+
)).join('')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const createBootstrapProgress = (params: {
|
|
62
|
+
enabled?: boolean
|
|
63
|
+
label: string
|
|
64
|
+
total?: number
|
|
65
|
+
}): BootstrapProgress => {
|
|
66
|
+
const enabled = params.enabled !== false && !isTestRun()
|
|
67
|
+
const startedAt = Date.now()
|
|
68
|
+
const tty = Boolean(process.stderr.isTTY)
|
|
69
|
+
let completed = 0
|
|
70
|
+
let failed = false
|
|
71
|
+
let frame = 0
|
|
72
|
+
let interval: NodeJS.Timeout | undefined
|
|
73
|
+
let stopped = false
|
|
74
|
+
let total = normalizeTotal(params.total)
|
|
75
|
+
let wrote = false
|
|
76
|
+
|
|
77
|
+
const buildLine = (summary?: string) => {
|
|
78
|
+
const hasTotal = total != null
|
|
79
|
+
const progressText = hasTotal
|
|
80
|
+
? `${Math.round(Math.max(0, Math.min(1, completed / total)) * 100)}% ${formatBytes(completed)}/${
|
|
81
|
+
formatBytes(total)
|
|
82
|
+
}`
|
|
83
|
+
: stopped && !failed
|
|
84
|
+
? 'done'
|
|
85
|
+
: 'working'
|
|
86
|
+
const bar = failed
|
|
87
|
+
? `${'#'.repeat(Math.max(1, Math.round(BAR_WIDTH / 3)))}${
|
|
88
|
+
'.'.repeat(
|
|
89
|
+
BAR_WIDTH - Math.max(1, Math.round(BAR_WIDTH / 3))
|
|
90
|
+
)
|
|
91
|
+
}`
|
|
92
|
+
: hasTotal
|
|
93
|
+
? buildDeterminateBar(completed, total)
|
|
94
|
+
: stopped
|
|
95
|
+
? '#'.repeat(BAR_WIDTH)
|
|
96
|
+
: buildIndeterminateBar(frame)
|
|
97
|
+
const marker = failed ? '!' : stopped ? ' ' : SPINNER_FRAMES[frame % SPINNER_FRAMES.length]
|
|
98
|
+
return `[bootstrap] ${marker} [${bar}] ${progressText} ${trimToWidth(summary ?? params.label, 72)} ${
|
|
99
|
+
formatElapsed(startedAt)
|
|
100
|
+
}`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const stopTicker = () => {
|
|
104
|
+
if (interval == null) return
|
|
105
|
+
clearInterval(interval)
|
|
106
|
+
interval = undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const render = (summary?: string) => {
|
|
110
|
+
if (!enabled) return
|
|
111
|
+
wrote = true
|
|
112
|
+
|
|
113
|
+
if (tty) {
|
|
114
|
+
process.stderr.write(`\r${buildLine(summary)}\x1B[K`)
|
|
115
|
+
frame += 1
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!stopped && frame > 0) return
|
|
120
|
+
process.stderr.write(`${buildLine(summary)}\n`)
|
|
121
|
+
frame += 1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const finishLine = (summary?: string) => {
|
|
125
|
+
if (!enabled || !wrote) return
|
|
126
|
+
if (tty) {
|
|
127
|
+
process.stderr.write(`\r${buildLine(summary)}\x1B[K\n`)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
process.stderr.write(`${buildLine(summary)}\n`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (enabled) {
|
|
134
|
+
render()
|
|
135
|
+
if (tty) {
|
|
136
|
+
interval = setInterval(() => render(), TICK_MS)
|
|
137
|
+
interval.unref()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
setTotal(nextTotal) {
|
|
143
|
+
total = normalizeTotal(nextTotal)
|
|
144
|
+
render()
|
|
145
|
+
},
|
|
146
|
+
update(nextCompleted, nextTotal) {
|
|
147
|
+
const normalizedTotal = normalizeTotal(nextTotal)
|
|
148
|
+
if (normalizedTotal != null) {
|
|
149
|
+
total = normalizedTotal
|
|
150
|
+
}
|
|
151
|
+
completed = Math.max(0, nextCompleted)
|
|
152
|
+
if (total != null) {
|
|
153
|
+
completed = Math.min(completed, total)
|
|
154
|
+
}
|
|
155
|
+
render()
|
|
156
|
+
},
|
|
157
|
+
finish(summary) {
|
|
158
|
+
if (stopped) return
|
|
159
|
+
stopped = true
|
|
160
|
+
if (total != null) {
|
|
161
|
+
completed = total
|
|
162
|
+
}
|
|
163
|
+
stopTicker()
|
|
164
|
+
finishLine(summary)
|
|
165
|
+
},
|
|
166
|
+
fail(summary) {
|
|
167
|
+
if (stopped) return
|
|
168
|
+
stopped = true
|
|
169
|
+
failed = true
|
|
170
|
+
stopTicker()
|
|
171
|
+
finishLine(summary)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { resolvePublishedPackageVersion } from './npm-package'
|
|
2
|
+
import { resolvePackageCacheDir, resolvePackageInstallDir } from './npm-package-cache'
|
|
3
|
+
import {
|
|
4
|
+
findInstalledPublishedPackageVersion,
|
|
5
|
+
installPublishedPackage,
|
|
6
|
+
readInstalledPackageVersion
|
|
7
|
+
} from './npm-package-install'
|
|
8
|
+
|
|
9
|
+
export type RuntimePackageAction = 'check' | 'install'
|
|
10
|
+
export type RuntimePackageTarget = 'cli' | 'client' | 'server' | 'web'
|
|
11
|
+
|
|
12
|
+
export interface RuntimePackageOptions {
|
|
13
|
+
version?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface RuntimePackageStatus {
|
|
17
|
+
installed: boolean
|
|
18
|
+
installedVersion?: string
|
|
19
|
+
latestInstalled: boolean
|
|
20
|
+
latestVersion: string
|
|
21
|
+
packageName: string
|
|
22
|
+
requestedVersion?: string
|
|
23
|
+
target: RuntimePackageTarget
|
|
24
|
+
updateAvailable: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const RUNTIME_PACKAGE_NAMES: Record<RuntimePackageTarget, string> = {
|
|
28
|
+
cli: '@oneworks/cli',
|
|
29
|
+
client: '@oneworks/client',
|
|
30
|
+
server: '@oneworks/server',
|
|
31
|
+
web: '@oneworks/web'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const RUNTIME_PACKAGE_TARGETS = Object.keys(RUNTIME_PACKAGE_NAMES) as RuntimePackageTarget[]
|
|
35
|
+
|
|
36
|
+
const isRuntimePackageTarget = (value: string): value is RuntimePackageTarget => (
|
|
37
|
+
RUNTIME_PACKAGE_TARGETS.includes(value as RuntimePackageTarget)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[\da-z.-]+)?(?:\+[\da-z.-]+)?$/i
|
|
41
|
+
|
|
42
|
+
const normalizeRequestedVersion = (value: string | undefined) => {
|
|
43
|
+
const normalized = value?.trim()
|
|
44
|
+
if (normalized == null || normalized === '') return undefined
|
|
45
|
+
if (!EXACT_VERSION_PATTERN.test(normalized)) {
|
|
46
|
+
throw new Error(`Runtime package version must be an exact semver version: ${normalized}.`)
|
|
47
|
+
}
|
|
48
|
+
return normalized
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const resolveRuntimePackageTarget = (value: string | undefined): RuntimePackageTarget => {
|
|
52
|
+
const normalized = value?.trim().toLowerCase() || 'cli'
|
|
53
|
+
if (isRuntimePackageTarget(normalized)) return normalized
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Unsupported runtime package target: ${value ?? ''}. Supported targets: ${RUNTIME_PACKAGE_TARGETS.join(', ')}.`
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const readVersionInstalledAt = async (packageName: string, version: string) => {
|
|
60
|
+
const packageDir = resolvePackageInstallDir(resolvePackageCacheDir(packageName, version), packageName)
|
|
61
|
+
return await readInstalledPackageVersion(packageDir)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const resolveRuntimePackageVersion = async (packageName: string, options: RuntimePackageOptions) => (
|
|
65
|
+
normalizeRequestedVersion(options.version) ??
|
|
66
|
+
await resolvePublishedPackageVersion(packageName, { cacheFirst: false })
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const createRuntimePackageStatus = async (
|
|
70
|
+
target: RuntimePackageTarget,
|
|
71
|
+
packageName: string,
|
|
72
|
+
targetVersion: string,
|
|
73
|
+
requestedVersion: string | undefined
|
|
74
|
+
): Promise<RuntimePackageStatus> => {
|
|
75
|
+
const installedVersion = await findInstalledPublishedPackageVersion(packageName)
|
|
76
|
+
const targetInstalled = await readVersionInstalledAt(packageName, targetVersion) === targetVersion
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
installed: installedVersion != null,
|
|
80
|
+
...(installedVersion != null ? { installedVersion } : {}),
|
|
81
|
+
latestInstalled: targetInstalled,
|
|
82
|
+
latestVersion: targetVersion,
|
|
83
|
+
packageName,
|
|
84
|
+
...(requestedVersion != null ? { requestedVersion } : {}),
|
|
85
|
+
target,
|
|
86
|
+
updateAvailable: requestedVersion != null ? !targetInstalled : installedVersion !== targetVersion
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const checkRuntimePackage = async (
|
|
91
|
+
targetValue: string | undefined,
|
|
92
|
+
options: RuntimePackageOptions = {}
|
|
93
|
+
): Promise<RuntimePackageStatus> => {
|
|
94
|
+
const target = resolveRuntimePackageTarget(targetValue)
|
|
95
|
+
const packageName = RUNTIME_PACKAGE_NAMES[target]
|
|
96
|
+
const requestedVersion = normalizeRequestedVersion(options.version)
|
|
97
|
+
const targetVersion = requestedVersion ?? await resolveRuntimePackageVersion(packageName, options)
|
|
98
|
+
return await createRuntimePackageStatus(target, packageName, targetVersion, requestedVersion)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const installRuntimePackage = async (
|
|
102
|
+
targetValue: string | undefined,
|
|
103
|
+
options: RuntimePackageOptions = {}
|
|
104
|
+
): Promise<RuntimePackageStatus> => {
|
|
105
|
+
const target = resolveRuntimePackageTarget(targetValue)
|
|
106
|
+
const packageName = RUNTIME_PACKAGE_NAMES[target]
|
|
107
|
+
const requestedVersion = normalizeRequestedVersion(options.version)
|
|
108
|
+
const targetVersion = requestedVersion ?? await resolveRuntimePackageVersion(packageName, options)
|
|
109
|
+
await installPublishedPackage(packageName, targetVersion)
|
|
110
|
+
return await createRuntimePackageStatus(target, packageName, targetVersion, requestedVersion)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const formatRuntimePackageStatus = (status: RuntimePackageStatus) => {
|
|
114
|
+
const current = status.installedVersion ?? 'not installed'
|
|
115
|
+
if (status.requestedVersion != null) {
|
|
116
|
+
return status.latestInstalled
|
|
117
|
+
? `${status.packageName}@${status.requestedVersion} cached`
|
|
118
|
+
: `${status.packageName}@${status.requestedVersion} not cached (${current})`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const suffix = status.updateAvailable
|
|
122
|
+
? `update available: ${current} -> ${status.latestVersion}`
|
|
123
|
+
: `up to date: ${status.latestVersion}`
|
|
124
|
+
|
|
125
|
+
return `${status.packageName} ${suffix}`
|
|
126
|
+
}
|
package/README.md
DELETED
package/index.js
DELETED