onework 0.1.0-alpha.0 → 0.1.0-alpha.1

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,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
@@ -1,9 +0,0 @@
1
- # onework
2
-
3
- Typo redirect package for [oneworks](https://www.npmjs.com/package/oneworks).
4
-
5
- ```bash
6
- npx onework
7
- ```
8
-
9
- This package forwards to the One Works bootstrap launcher.