onework 0.1.0-beta.0 → 0.1.0-beta.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.
@@ -20,6 +20,8 @@ describe('bootstrap npm package env', () => {
20
20
  vi.stubEnv('ONEWORKS_BOOTSTRAP_DISABLE_BACKGROUND_REFRESH', '1')
21
21
  vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_CACHE_FIRST', undefined)
22
22
  vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_LOOKUP_TIMEOUT_MS', '1000')
23
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_TAG', undefined)
24
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_VERSION', undefined)
23
25
  vi.stubEnv('NPM_CONFIG_USERCONFIG', undefined)
24
26
  vi.stubEnv('npm_config_userconfig', undefined)
25
27
  })
@@ -40,8 +42,25 @@ describe('bootstrap npm package env', () => {
40
42
  `#!/usr/bin/env node
41
43
  const delay = Number.parseInt(process.env.ONEWORKS_TEST_NPM_VIEW_DELAY_MS || '0', 10)
42
44
  const version = process.env.ONEWORKS_TEST_NPM_VIEW_VERSION || '1.0.0'
45
+ const exactVersions = JSON.parse(process.env.ONEWORKS_TEST_NPM_VIEW_EXACT_VERSIONS || '{}')
46
+ const versions = JSON.parse(process.env.ONEWORKS_TEST_NPM_VIEW_VERSIONS || '[]')
43
47
  if (process.argv[2] === 'view') {
48
+ const spec = process.argv[3]
49
+ const field = process.argv[4]
44
50
  setTimeout(() => {
51
+ if (field === 'versions') {
52
+ process.stdout.write(JSON.stringify(versions.length > 0 ? versions : [version]) + '\\n')
53
+ return
54
+ }
55
+ if (Object.prototype.hasOwnProperty.call(exactVersions, spec)) {
56
+ process.stdout.write(JSON.stringify(exactVersions[spec]) + '\\n')
57
+ return
58
+ }
59
+ if (process.env.ONEWORKS_TEST_NPM_VIEW_FAIL_EXACT === '1' && /@\\d+\\.\\d+\\.\\d+/.test(spec)) {
60
+ process.stderr.write('not found\\n')
61
+ process.exitCode = 1
62
+ return
63
+ }
45
64
  process.stdout.write(JSON.stringify(version) + '\\n')
46
65
  }, delay)
47
66
  } else {
@@ -118,6 +137,7 @@ if (process.argv[2] === 'view') {
118
137
  })
119
138
 
120
139
  it('uses installed package cache before npm view when no metadata exists', async () => {
140
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_TAG', 'latest')
121
141
  await writeCachedPackage('@scope/pkg', '4.5.6')
122
142
 
123
143
  await expect(resolvePublishedPackageVersion('@scope/pkg')).resolves.toBe('4.5.6')
@@ -139,6 +159,55 @@ if (process.argv[2] === 'view') {
139
159
  expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('timed out after 20ms'))
140
160
  })
141
161
 
162
+ it('prefers the exact bootstrap prerelease version for runtime package resolution', async () => {
163
+ await installFakeNpm()
164
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_CACHE_FIRST', '0')
165
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_VERSION', '0.1.0-beta.0')
166
+ vi.stubEnv('ONEWORKS_TEST_NPM_VIEW_VERSION', '0.1.0-alpha.0')
167
+ vi.stubEnv(
168
+ 'ONEWORKS_TEST_NPM_VIEW_EXACT_VERSIONS',
169
+ JSON.stringify({
170
+ '@scope/pkg@0.1.0-beta.0': '0.1.0-beta.0'
171
+ })
172
+ )
173
+
174
+ await expect(resolvePublishedPackageVersion('@scope/pkg')).resolves.toBe('0.1.0-beta.0')
175
+ })
176
+
177
+ it('falls back to the highest same-core bootstrap prerelease version', async () => {
178
+ await installFakeNpm()
179
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_CACHE_FIRST', '0')
180
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_VERSION', '0.1.0-beta.0')
181
+ vi.stubEnv('ONEWORKS_TEST_NPM_VIEW_FAIL_EXACT', '1')
182
+ vi.stubEnv(
183
+ 'ONEWORKS_TEST_NPM_VIEW_VERSIONS',
184
+ JSON.stringify([
185
+ '0.1.0-alpha.9',
186
+ '0.1.0-beta.1',
187
+ '0.1.0-beta.2',
188
+ '0.1.0-rc.0',
189
+ '0.1.0',
190
+ '0.1.1-beta.0'
191
+ ])
192
+ )
193
+
194
+ await expect(resolvePublishedPackageVersion('@scope/pkg')).resolves.toBe('0.1.0-beta.2')
195
+ })
196
+
197
+ it('does not use an installed alpha cache for bootstrap beta resolution', async () => {
198
+ await installFakeNpm()
199
+ await writeCachedPackage('@scope/pkg', '0.1.0-alpha.0')
200
+ vi.stubEnv('ONEWORKS_BOOTSTRAP_PACKAGE_VERSION', '0.1.0-beta.0')
201
+ vi.stubEnv(
202
+ 'ONEWORKS_TEST_NPM_VIEW_EXACT_VERSIONS',
203
+ JSON.stringify({
204
+ '@scope/pkg@0.1.0-beta.0': '0.1.0-beta.0'
205
+ })
206
+ )
207
+
208
+ await expect(resolvePublishedPackageVersion('@scope/pkg')).resolves.toBe('0.1.0-beta.0')
209
+ })
210
+
142
211
  it('waits for npm view when no cached package version exists yet', async () => {
143
212
  await installFakeNpm()
144
213
  vi.stubEnv('ONEWORKS_TEST_NPM_VIEW_VERSION', '3.0.0')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onework",
3
- "version": "0.1.0-beta.0",
3
+ "version": "0.1.0-beta.1",
4
4
  "description": "One Works bootstrap launcher",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "dependencies": {
20
20
  "@clack/prompts": "^0.11.0",
21
21
  "commander": "^12.1.0",
22
- "@oneworks/cli-helper": "0.1.0-beta.0"
22
+ "@oneworks/cli-helper": "0.1.0-beta.1"
23
23
  },
24
24
  "scripts": {
25
25
  "test": "pnpm -C ../.. exec vitest run --workspace vitest.workspace.ts --project node apps/bootstrap/__tests__"
@@ -141,22 +141,29 @@ const readOptionalFile = async (filePath: string | undefined) => {
141
141
  }
142
142
  }
143
143
 
144
- const resolvePackageLookupKey = async (packageName: string) => {
144
+ const resolvePackageLookupKey = async (
145
+ packageName: string,
146
+ options: { lookupScope?: string } = {}
147
+ ) => {
145
148
  const env = resolvePackageManagerEnv()
146
149
  const userConfig = env.npm_config_userconfig ?? env.NPM_CONFIG_USERCONFIG
147
150
  const userConfigContent = await readOptionalFile(userConfig)
151
+ const packageTag = options.lookupScope?.trim() || resolvePackageTag()
148
152
 
149
153
  return JSON.stringify({
150
154
  packageName,
151
- packageTag: resolvePackageTag(),
155
+ packageTag,
152
156
  registry: env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY ?? '',
153
157
  userConfig: userConfig ?? '',
154
158
  userConfigContentHash: userConfigContent == null ? '' : hashValue(userConfigContent)
155
159
  })
156
160
  }
157
161
 
158
- const resolvePackageVersionMetadataPath = async (packageName: string) => {
159
- const lookupKey = await resolvePackageLookupKey(packageName)
162
+ const resolvePackageVersionMetadataPath = async (
163
+ packageName: string,
164
+ options: { lookupScope?: string } = {}
165
+ ) => {
166
+ const lookupKey = await resolvePackageLookupKey(packageName, options)
160
167
  return {
161
168
  lookupKey,
162
169
  metadataPath: path.join(
@@ -166,8 +173,12 @@ const resolvePackageVersionMetadataPath = async (packageName: string) => {
166
173
  }
167
174
  }
168
175
 
169
- export const readPublishedPackageVersionMetadata = async (packageName: string) => {
170
- const { lookupKey, metadataPath } = await resolvePackageVersionMetadataPath(packageName)
176
+ export const readPublishedPackageVersionMetadata = async (
177
+ packageName: string,
178
+ options: { lookupScope?: string } = {}
179
+ ) => {
180
+ const packageTag = options.lookupScope?.trim() || resolvePackageTag()
181
+ const { lookupKey, metadataPath } = await resolvePackageVersionMetadataPath(packageName, options)
171
182
 
172
183
  try {
173
184
  const content = await readFile(metadataPath, 'utf8')
@@ -175,7 +186,7 @@ export const readPublishedPackageVersionMetadata = async (packageName: string) =
175
186
  if (
176
187
  parsed.lookupKey === lookupKey &&
177
188
  parsed.packageName === packageName &&
178
- parsed.packageTag === resolvePackageTag() &&
189
+ parsed.packageTag === packageTag &&
179
190
  typeof parsed.version === 'string' &&
180
191
  parsed.version.trim()
181
192
  ) {
@@ -191,15 +202,20 @@ export const readPublishedPackageVersionMetadata = async (packageName: string) =
191
202
  return undefined
192
203
  }
193
204
 
194
- export const writePublishedPackageVersionMetadata = async (packageName: string, version: string) => {
195
- const { lookupKey, metadataPath } = await resolvePackageVersionMetadataPath(packageName)
205
+ export const writePublishedPackageVersionMetadata = async (
206
+ packageName: string,
207
+ version: string,
208
+ options: { lookupScope?: string } = {}
209
+ ) => {
210
+ const packageTag = options.lookupScope?.trim() || resolvePackageTag()
211
+ const { lookupKey, metadataPath } = await resolvePackageVersionMetadataPath(packageName, options)
196
212
  await ensureDirectory(path.dirname(metadataPath))
197
213
 
198
214
  const tempPath = `${metadataPath}.${process.pid}.${Date.now()}.tmp`
199
215
  const metadata: PublishedPackageVersionMetadata = {
200
216
  lookupKey,
201
217
  packageName,
202
- packageTag: resolvePackageTag(),
218
+ packageTag,
203
219
  resolvedAt: new Date().toISOString(),
204
220
  version
205
221
  }
@@ -39,7 +39,13 @@ export const readInstalledPackageVersion = async (packageDir: string) => {
39
39
  }
40
40
  }
41
41
 
42
- export const findInstalledPublishedPackageVersion = async (packageName: string) => {
42
+ export const findInstalledPublishedPackageVersion = async (
43
+ packageName: string,
44
+ options: {
45
+ preferredVersion?: string
46
+ versionFilter?: (version: string) => boolean
47
+ } = {}
48
+ ) => {
43
49
  let versions: string[]
44
50
  try {
45
51
  versions = (await readdir(resolvePackageCacheRootDir(packageName), { withFileTypes: true }))
@@ -51,6 +57,9 @@ export const findInstalledPublishedPackageVersion = async (packageName: string)
51
57
 
52
58
  const installedVersions: string[] = []
53
59
  for (const version of versions) {
60
+ if (options.versionFilter != null && !options.versionFilter(version)) {
61
+ continue
62
+ }
54
63
  const packageDir = resolvePackageInstallDir(resolvePackageCacheDir(packageName, version), packageName)
55
64
  const installedVersion = await readInstalledPackageVersion(packageDir)
56
65
  if (installedVersion === version) {
@@ -58,6 +67,10 @@ export const findInstalledPublishedPackageVersion = async (packageName: string)
58
67
  }
59
68
  }
60
69
 
70
+ if (options.preferredVersion != null && installedVersions.includes(options.preferredVersion)) {
71
+ return options.preferredVersion
72
+ }
73
+
61
74
  return installedVersions.sort(compareVersionLike).at(-1)
62
75
  }
63
76
 
@@ -8,14 +8,12 @@ import {
8
8
  readPublishedPackageVersionMetadata,
9
9
  resolvePackageLookupTimeoutMs,
10
10
  resolvePackageManagerEnv,
11
- resolvePackageTag,
12
11
  shouldUseCachedPackageVersionFirst,
13
12
  writePublishedPackageVersionMetadata
14
13
  } from './npm-package-cache'
15
14
  import { findInstalledPublishedPackageVersion } from './npm-package-install'
16
- import { runBufferedCommand } from './process-utils'
17
-
18
- const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm'
15
+ import { resolvePublishedPackageVersionFromRegistry } from './npm-registry'
16
+ import { resolvePackageVersionRequest } from './package-version-request'
19
17
 
20
18
  export { resolvePackageManagerEnv } from './npm-package-cache'
21
19
  export { installPublishedPackage, resolvePackageBinEntrypoint } from './npm-package-install'
@@ -52,68 +50,21 @@ const spawnPackageVersionRefresh = (packageName: string) => {
52
50
  }
53
51
  }
54
52
 
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
53
  export const resolvePublishedPackageVersion = async (
107
54
  packageName: string,
108
55
  options: { cacheFirst?: boolean } = {}
109
56
  ) => {
110
- const cachedMetadata = await readPublishedPackageVersionMetadata(packageName)
57
+ const request = resolvePackageVersionRequest(packageName)
58
+ const cachedMetadata = await readPublishedPackageVersionMetadata(packageName, { lookupScope: request.lookupScope })
111
59
  if (cachedMetadata != null && (options.cacheFirst ?? shouldUseCachedPackageVersionFirst())) {
112
60
  spawnPackageVersionRefresh(packageName)
113
61
  return cachedMetadata.version
114
62
  }
115
63
 
116
- const cachedInstalledVersion = await findInstalledPublishedPackageVersion(packageName)
64
+ const cachedInstalledVersion = await findInstalledPublishedPackageVersion(packageName, {
65
+ preferredVersion: request.exactVersion,
66
+ versionFilter: request.versionFilter
67
+ })
117
68
  if (cachedInstalledVersion != null && (options.cacheFirst ?? shouldUseCachedPackageVersionFirst())) {
118
69
  spawnPackageVersionRefresh(packageName)
119
70
  return cachedInstalledVersion
@@ -121,6 +72,7 @@ export const resolvePublishedPackageVersion = async (
121
72
 
122
73
  const registryResult = await resolvePublishedPackageVersionFromRegistry(
123
74
  packageName,
75
+ request,
124
76
  cachedMetadata == null
125
77
  ? {}
126
78
  : {
@@ -129,15 +81,17 @@ export const resolvePublishedPackageVersion = async (
129
81
  )
130
82
 
131
83
  if ('version' in registryResult) {
132
- await writePublishedPackageVersionMetadata(packageName, registryResult.version)
84
+ await writePublishedPackageVersionMetadata(packageName, registryResult.version, {
85
+ lookupScope: request.lookupScope
86
+ })
133
87
  return registryResult.version
134
88
  }
135
89
 
136
90
  if (cachedMetadata == null) {
137
91
  // This is not expected because uncached lookups do not use a timeout.
138
- const retryResult = await resolvePublishedPackageVersionFromRegistry(packageName)
92
+ const retryResult = await resolvePublishedPackageVersionFromRegistry(packageName, request)
139
93
  if ('version' in retryResult) {
140
- await writePublishedPackageVersionMetadata(packageName, retryResult.version)
94
+ await writePublishedPackageVersionMetadata(packageName, retryResult.version, { lookupScope: request.lookupScope })
141
95
  return retryResult.version
142
96
  }
143
97
  throw new Error(`Failed to resolve published version for ${retryResult.spec}.`)
@@ -0,0 +1,154 @@
1
+ import process from 'node:process'
2
+
3
+ import { resolvePackageManagerEnv } from './npm-package-cache'
4
+ import { comparePackageVersions } from './package-version-request'
5
+ import type { PackageVersionRequest } from './package-version-request'
6
+ import { runBufferedCommand } from './process-utils'
7
+
8
+ const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm'
9
+
10
+ const parseVersionOutput = (spec: string, output: string) => {
11
+ const normalizedOutput = output.trim()
12
+ if (!normalizedOutput) {
13
+ throw new Error(`No version was returned for ${spec}.`)
14
+ }
15
+
16
+ try {
17
+ const parsed = JSON.parse(normalizedOutput) as unknown
18
+ if (typeof parsed === 'string' && parsed.trim()) {
19
+ return parsed.trim()
20
+ }
21
+ } catch {
22
+ // fall through
23
+ }
24
+
25
+ const unquotedOutput = normalizedOutput.replace(/^"|"$/g, '').trim()
26
+ if (!unquotedOutput) {
27
+ throw new Error(`Invalid published version for ${spec}: ${normalizedOutput}`)
28
+ }
29
+
30
+ return unquotedOutput
31
+ }
32
+
33
+ const resolvePublishedPackageVersionBySpec = async (
34
+ spec: string,
35
+ options: { allowMissing?: boolean; timeoutMs?: number } = {}
36
+ ) => {
37
+ const result = await runBufferedCommand({
38
+ command: NPM_BIN,
39
+ args: ['view', spec, 'version', '--json'],
40
+ env: resolvePackageManagerEnv(),
41
+ timeoutMs: options.timeoutMs
42
+ })
43
+
44
+ if (result.timedOut === true) {
45
+ return {
46
+ spec,
47
+ timedOut: true as const
48
+ }
49
+ }
50
+
51
+ if (result.code !== 0) {
52
+ if (options.allowMissing === true) {
53
+ return {
54
+ missing: true as const,
55
+ spec
56
+ }
57
+ }
58
+ throw new Error(`Failed to resolve published version for ${spec}:\n${result.stderr.trim()}`)
59
+ }
60
+
61
+ return {
62
+ spec,
63
+ version: parseVersionOutput(spec, result.stdout)
64
+ }
65
+ }
66
+
67
+ const parseVersionsOutput = (spec: string, output: string) => {
68
+ const normalizedOutput = output.trim()
69
+ if (!normalizedOutput) {
70
+ throw new Error(`No versions were returned for ${spec}.`)
71
+ }
72
+
73
+ let parsed: unknown
74
+ try {
75
+ parsed = JSON.parse(normalizedOutput) as unknown
76
+ } catch {
77
+ parsed = normalizedOutput.replace(/^"|"$/g, '').trim()
78
+ }
79
+
80
+ const versions = Array.isArray(parsed)
81
+ ? parsed.filter((version): version is string => typeof version === 'string' && version.trim() !== '')
82
+ : typeof parsed === 'string' && parsed.trim() !== ''
83
+ ? [parsed.trim()]
84
+ : []
85
+ if (versions.length === 0) {
86
+ throw new Error(`Invalid published versions for ${spec}: ${normalizedOutput}`)
87
+ }
88
+ return versions
89
+ }
90
+
91
+ const resolvePublishedPackageVersionsFromRegistry = async (
92
+ packageName: string,
93
+ options: { timeoutMs?: number } = {}
94
+ ) => {
95
+ const result = await runBufferedCommand({
96
+ command: NPM_BIN,
97
+ args: ['view', packageName, 'versions', '--json'],
98
+ env: resolvePackageManagerEnv(),
99
+ timeoutMs: options.timeoutMs
100
+ })
101
+
102
+ if (result.timedOut === true) {
103
+ return {
104
+ spec: packageName,
105
+ timedOut: true as const
106
+ }
107
+ }
108
+
109
+ if (result.code !== 0) {
110
+ throw new Error(`Failed to resolve published versions for ${packageName}:\n${result.stderr.trim()}`)
111
+ }
112
+
113
+ return {
114
+ spec: packageName,
115
+ versions: parseVersionsOutput(packageName, result.stdout)
116
+ }
117
+ }
118
+
119
+ export const resolvePublishedPackageVersionFromRegistry = async (
120
+ packageName: string,
121
+ request: PackageVersionRequest,
122
+ options: { timeoutMs?: number } = {}
123
+ ) => {
124
+ if (request.exactVersion == null) {
125
+ return await resolvePublishedPackageVersionBySpec(request.packageSpec, options)
126
+ }
127
+
128
+ const exactResult = await resolvePublishedPackageVersionBySpec(request.packageSpec, {
129
+ allowMissing: true,
130
+ timeoutMs: options.timeoutMs
131
+ })
132
+ if ('version' in exactResult || 'timedOut' in exactResult) {
133
+ return exactResult
134
+ }
135
+
136
+ const versionsResult = await resolvePublishedPackageVersionsFromRegistry(packageName, options)
137
+ if ('timedOut' in versionsResult) {
138
+ return versionsResult
139
+ }
140
+
141
+ const matchedVersion = versionsResult.versions
142
+ .filter(version => request.versionFilter == null || request.versionFilter(version))
143
+ .sort(comparePackageVersions)
144
+ .at(-1)
145
+ if (matchedVersion == null) {
146
+ throw new Error(
147
+ `No published version for ${packageName} matches bootstrap runtime series ${request.exactVersion}.`
148
+ )
149
+ }
150
+ return {
151
+ spec: `${packageName} versions`,
152
+ version: matchedVersion
153
+ }
154
+ }
@@ -0,0 +1,121 @@
1
+ import { createRequire } from 'node:module'
2
+ import process from 'node:process'
3
+
4
+ import { compareVersionLike, resolvePackageTag } from './npm-package-cache'
5
+
6
+ const BOOTSTRAP_PACKAGE_VERSION_ENV = 'ONEWORKS_BOOTSTRAP_PACKAGE_VERSION'
7
+
8
+ interface ParsedSemver {
9
+ major: number
10
+ minor: number
11
+ patch: number
12
+ prerelease: string[]
13
+ }
14
+
15
+ export interface PackageVersionRequest {
16
+ exactVersion?: string
17
+ lookupScope: string
18
+ packageSpec: string
19
+ tag?: string
20
+ versionFilter?: (version: string) => boolean
21
+ }
22
+
23
+ const parseSemver = (version: string): ParsedSemver | undefined => {
24
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(version.trim())
25
+ if (match == null) return undefined
26
+ return {
27
+ major: Number(match[1]),
28
+ minor: Number(match[2]),
29
+ patch: Number(match[3]),
30
+ prerelease: match[4]?.split('.') ?? []
31
+ }
32
+ }
33
+
34
+ const comparePrereleaseIdentifiers = (left: string, right: string) => {
35
+ const leftNumber = /^\d+$/u.test(left) ? Number(left) : undefined
36
+ const rightNumber = /^\d+$/u.test(right) ? Number(right) : undefined
37
+ if (leftNumber != null && rightNumber != null) return leftNumber - rightNumber
38
+ if (leftNumber != null) return -1
39
+ if (rightNumber != null) return 1
40
+ return left.localeCompare(right)
41
+ }
42
+
43
+ const compareSemver = (left: ParsedSemver, right: ParsedSemver) => {
44
+ const coreDiff = left.major - right.major || left.minor - right.minor || left.patch - right.patch
45
+ if (coreDiff !== 0) return coreDiff
46
+ if (left.prerelease.length === 0 && right.prerelease.length === 0) return 0
47
+ if (left.prerelease.length === 0) return 1
48
+ if (right.prerelease.length === 0) return -1
49
+ const maxLength = Math.max(left.prerelease.length, right.prerelease.length)
50
+ for (let index = 0; index < maxLength; index += 1) {
51
+ const leftPart = left.prerelease[index]
52
+ const rightPart = right.prerelease[index]
53
+ if (leftPart == null) return -1
54
+ if (rightPart == null) return 1
55
+ const diff = comparePrereleaseIdentifiers(leftPart, rightPart)
56
+ if (diff !== 0) return diff
57
+ }
58
+ return 0
59
+ }
60
+
61
+ export const comparePackageVersions = (left: string, right: string) => {
62
+ const leftSemver = parseSemver(left)
63
+ const rightSemver = parseSemver(right)
64
+ if (leftSemver != null && rightSemver != null) return compareSemver(leftSemver, rightSemver)
65
+ return compareVersionLike(left, right)
66
+ }
67
+
68
+ const hasSameSemverCore = (left: ParsedSemver, right: ParsedSemver) => (
69
+ left.major === right.major && left.minor === right.minor && left.patch === right.patch
70
+ )
71
+
72
+ const readBootstrapPackageVersion = () => {
73
+ const envVersion = process.env[BOOTSTRAP_PACKAGE_VERSION_ENV]?.trim()
74
+ if (envVersion) return envVersion
75
+
76
+ try {
77
+ const requireFromHere = createRequire(import.meta.url)
78
+ const packageJsonPath = requireFromHere.resolve('oneworks/package.json')
79
+ const packageJson = requireFromHere(packageJsonPath) as { version?: unknown }
80
+ return typeof packageJson.version === 'string' && packageJson.version.trim()
81
+ ? packageJson.version.trim()
82
+ : undefined
83
+ } catch {
84
+ return undefined
85
+ }
86
+ }
87
+
88
+ export const resolvePackageVersionRequest = (packageName: string): PackageVersionRequest => {
89
+ const explicitTag = process.env.ONEWORKS_BOOTSTRAP_PACKAGE_TAG?.trim()
90
+ if (explicitTag) {
91
+ return {
92
+ lookupScope: `tag:${explicitTag}`,
93
+ packageSpec: `${packageName}@${explicitTag}`,
94
+ tag: explicitTag
95
+ }
96
+ }
97
+
98
+ const bootstrapVersion = readBootstrapPackageVersion()
99
+ const parsedBootstrapVersion = bootstrapVersion == null ? undefined : parseSemver(bootstrapVersion)
100
+ const prereleaseChannel = parsedBootstrapVersion?.prerelease[0]
101
+ if (bootstrapVersion != null && parsedBootstrapVersion != null && prereleaseChannel != null) {
102
+ return {
103
+ exactVersion: bootstrapVersion,
104
+ lookupScope: `bootstrap:${bootstrapVersion}:channel:${prereleaseChannel}`,
105
+ packageSpec: `${packageName}@${bootstrapVersion}`,
106
+ versionFilter: (version) => {
107
+ const parsedVersion = parseSemver(version)
108
+ return parsedVersion != null &&
109
+ hasSameSemverCore(parsedVersion, parsedBootstrapVersion) &&
110
+ parsedVersion.prerelease[0] === prereleaseChannel
111
+ }
112
+ }
113
+ }
114
+
115
+ const tag = resolvePackageTag()
116
+ return {
117
+ lookupScope: `tag:${tag}`,
118
+ packageSpec: `${packageName}@${tag}`,
119
+ tag
120
+ }
121
+ }