onework 0.0.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,92 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import { isCancel, select } from '@clack/prompts'
6
+
7
+ import { resolveBootstrapDataDir } from './paths'
8
+
9
+ export type DesktopInstallMode = 'cache' | 'user'
10
+
11
+ interface DesktopPreferenceState {
12
+ installMode?: DesktopInstallMode
13
+ }
14
+
15
+ const DEFAULT_INSTALL_MODE: DesktopInstallMode = 'user'
16
+
17
+ const ensureDirectory = async (targetPath: string) => {
18
+ await mkdir(targetPath, { recursive: true })
19
+ }
20
+
21
+ const resolveDesktopPreferencePath = () => path.join(resolveBootstrapDataDir(), 'desktop', 'preferences.json')
22
+
23
+ const readJsonFile = async <T>(filePath: string) => {
24
+ try {
25
+ const content = await readFile(filePath, 'utf8')
26
+ return JSON.parse(content) as T
27
+ } catch {
28
+ return undefined
29
+ }
30
+ }
31
+
32
+ const writeJsonFile = async (filePath: string, value: unknown) => {
33
+ await ensureDirectory(path.dirname(filePath))
34
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`)
35
+ }
36
+
37
+ export const readDesktopPreference = async () => (
38
+ await readJsonFile<DesktopPreferenceState>(resolveDesktopPreferencePath())
39
+ )
40
+
41
+ const writeDesktopPreference = async (installMode: DesktopInstallMode) => {
42
+ await writeJsonFile(resolveDesktopPreferencePath(), { installMode })
43
+ }
44
+
45
+ const promptDesktopInstallMode = async () => {
46
+ const selectedMode = await select<DesktopInstallMode>({
47
+ message: 'Choose how to launch the desktop app',
48
+ options: [
49
+ {
50
+ value: 'user',
51
+ label: 'User directory',
52
+ hint: 'Install into the user application directory'
53
+ },
54
+ {
55
+ value: 'cache',
56
+ label: 'Bootstrap cache',
57
+ hint: 'Keep the app inside the bootstrap cache'
58
+ }
59
+ ]
60
+ })
61
+
62
+ if (isCancel(selectedMode)) {
63
+ throw new Error('Desktop launch was cancelled.')
64
+ }
65
+
66
+ return selectedMode
67
+ }
68
+
69
+ export const resolveInstallMode = async (input: {
70
+ explicitInstallMode?: DesktopInstallMode
71
+ persistInstallMode?: boolean
72
+ }) => {
73
+ if (input.explicitInstallMode != null) {
74
+ if (input.persistInstallMode === true) {
75
+ await writeDesktopPreference(input.explicitInstallMode)
76
+ }
77
+ return input.explicitInstallMode
78
+ }
79
+
80
+ const preference = await readDesktopPreference()
81
+ if (preference?.installMode != null) {
82
+ return preference.installMode
83
+ }
84
+
85
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
86
+ return DEFAULT_INSTALL_MODE
87
+ }
88
+
89
+ const selectedMode = await promptDesktopInstallMode()
90
+ await writeDesktopPreference(selectedMode)
91
+ return selectedMode
92
+ }
@@ -0,0 +1,198 @@
1
+ import type { Buffer } from 'node:buffer'
2
+ import { createHash } from 'node:crypto'
3
+ import { createWriteStream } from 'node:fs'
4
+ import { mkdir, unlink } from 'node:fs/promises'
5
+ import https from 'node:https'
6
+ import path from 'node:path'
7
+ import process from 'node:process'
8
+
9
+ import { createBootstrapProgress } from './progress'
10
+
11
+ interface GitHubReleaseAsset {
12
+ browser_download_url?: string
13
+ digest?: string
14
+ name: string
15
+ url: string
16
+ }
17
+
18
+ interface GitHubReleaseResponse {
19
+ assets?: GitHubReleaseAsset[]
20
+ draft?: boolean
21
+ tag_name?: string
22
+ tagName?: string
23
+ }
24
+
25
+ export interface DesktopRelease {
26
+ assets: GitHubReleaseAsset[]
27
+ tagName: string
28
+ }
29
+
30
+ const GITHUB_RELEASES_API = 'https://api.github.com/repos/oneworks-ai/app/releases'
31
+ const DESKTOP_RELEASE_TAG_PREFIX = 'pkg/oneworks-desktop/v'
32
+ const RELEASE_TAG_OVERRIDE = process.env.ONEWORKS_BOOTSTRAP_DESKTOP_RELEASE_TAG?.trim()
33
+
34
+ const ensureDirectory = async (targetPath: string) => {
35
+ await mkdir(targetPath, { recursive: true })
36
+ }
37
+
38
+ const parseContentLength = (value: string | string[] | undefined) => {
39
+ const rawValue = Array.isArray(value) ? value[0] : value
40
+ if (rawValue == null) return undefined
41
+
42
+ const parsed = Number.parseInt(rawValue, 10)
43
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined
44
+ }
45
+
46
+ const requestJson = async <T>(url: string) => (
47
+ await new Promise<T>((resolve, reject) => {
48
+ https.get(url, {
49
+ headers: {
50
+ Accept: 'application/vnd.github+json',
51
+ 'User-Agent': 'oneworks'
52
+ }
53
+ }, (response) => {
54
+ const statusCode = response.statusCode ?? 0
55
+ if (statusCode < 200 || statusCode >= 300) {
56
+ response.resume()
57
+ reject(new Error(`GitHub API request failed: HTTP ${statusCode}`))
58
+ return
59
+ }
60
+
61
+ let content = ''
62
+ response.setEncoding('utf8')
63
+ response.on('data', (chunk: string) => {
64
+ content += chunk
65
+ })
66
+ response.on('end', () => {
67
+ try {
68
+ resolve(JSON.parse(content) as T)
69
+ } catch (error) {
70
+ reject(error)
71
+ }
72
+ })
73
+ }).on('error', reject)
74
+ })
75
+ )
76
+
77
+ export const fetchDesktopRelease = async (): Promise<DesktopRelease> => {
78
+ const release = RELEASE_TAG_OVERRIDE
79
+ ? await requestJson<GitHubReleaseResponse>(
80
+ `${GITHUB_RELEASES_API}/tags/${encodeURIComponent(RELEASE_TAG_OVERRIDE)}`
81
+ )
82
+ : (await requestJson<GitHubReleaseResponse[]>(`${GITHUB_RELEASES_API}?per_page=50`))
83
+ .find(item => (
84
+ item.draft !== true &&
85
+ typeof item.tag_name === 'string' &&
86
+ item.tag_name.startsWith(DESKTOP_RELEASE_TAG_PREFIX) &&
87
+ /^\d+\.\d+\.\d+$/u.test(item.tag_name.slice(DESKTOP_RELEASE_TAG_PREFIX.length))
88
+ ))
89
+ const tagName = release?.tag_name ?? release?.tagName
90
+ if (!tagName || !Array.isArray(release?.assets)) {
91
+ throw new Error('Invalid desktop release metadata returned by GitHub.')
92
+ }
93
+ return {
94
+ assets: release.assets.map(asset => ({
95
+ ...asset,
96
+ url: asset.browser_download_url ?? asset.url
97
+ })),
98
+ tagName
99
+ }
100
+ }
101
+
102
+ export const selectDesktopAsset = (release: DesktopRelease, runtime: {
103
+ arch: string
104
+ platform: NodeJS.Platform
105
+ }) => {
106
+ if (runtime.platform === 'darwin') {
107
+ return release.assets.find(asset => asset.name.endsWith(`-mac-${runtime.arch}.zip`))
108
+ }
109
+
110
+ if (runtime.platform === 'linux') {
111
+ const appImageArch = runtime.arch === 'x64' ? 'x86_64' : runtime.arch
112
+ return release.assets.find(asset => asset.name.endsWith(`-linux-${appImageArch}.AppImage`))
113
+ }
114
+
115
+ if (runtime.platform === 'win32') {
116
+ return release.assets.find(asset => asset.name.endsWith(`-win-${runtime.arch}.exe`))
117
+ }
118
+
119
+ return undefined
120
+ }
121
+
122
+ export const downloadReleaseAsset = async (asset: GitHubReleaseAsset, destinationPath: string) => {
123
+ await ensureDirectory(path.dirname(destinationPath))
124
+
125
+ return await new Promise<void>((resolve, reject) => {
126
+ const hash = createHash('sha256')
127
+ const file = createWriteStream(destinationPath)
128
+ let downloadedBytes = 0
129
+ let progress: ReturnType<typeof createBootstrapProgress> | undefined
130
+
131
+ file.on('error', (error) => {
132
+ progress?.fail(`failed to download ${asset.name}`)
133
+ reject(error)
134
+ })
135
+
136
+ const request = https.get(asset.url, {
137
+ headers: {
138
+ 'User-Agent': 'oneworks'
139
+ }
140
+ }, (response) => {
141
+ const statusCode = response.statusCode ?? 0
142
+ const redirectLocation = response.headers.location
143
+
144
+ if (statusCode >= 300 && statusCode < 400 && redirectLocation != null) {
145
+ file.close()
146
+ void unlink(destinationPath).catch(() => {})
147
+ downloadReleaseAsset({
148
+ ...asset,
149
+ url: new URL(redirectLocation, asset.url).toString()
150
+ }, destinationPath).then(resolve, reject)
151
+ return
152
+ }
153
+
154
+ if (statusCode < 200 || statusCode >= 300) {
155
+ response.resume()
156
+ file.close()
157
+ reject(new Error(`Failed to download ${asset.name}: HTTP ${statusCode}`))
158
+ return
159
+ }
160
+
161
+ progress = createBootstrapProgress({
162
+ label: `downloading ${asset.name}`,
163
+ total: parseContentLength(response.headers['content-length'])
164
+ })
165
+
166
+ response.on('data', (chunk: Buffer) => {
167
+ downloadedBytes += chunk.length
168
+ hash.update(chunk)
169
+ progress?.update(downloadedBytes)
170
+ })
171
+ response.on('error', (error) => {
172
+ progress?.fail(`failed to download ${asset.name}`)
173
+ file.close()
174
+ reject(error)
175
+ })
176
+ response.pipe(file)
177
+ file.on('finish', () => {
178
+ file.close(() => {
179
+ const expectedDigest = asset.digest?.replace(/^sha256:/, '')
180
+ if (expectedDigest && hash.digest('hex') !== expectedDigest) {
181
+ progress?.fail(`failed to verify ${asset.name}`)
182
+ reject(new Error(`Downloaded desktop asset digest mismatch for ${asset.name}.`))
183
+ return
184
+ }
185
+
186
+ progress?.finish(`downloaded ${asset.name}`)
187
+ resolve()
188
+ })
189
+ })
190
+ })
191
+
192
+ request.on('error', (error) => {
193
+ progress?.fail(`failed to download ${asset.name}`)
194
+ file.close()
195
+ reject(error)
196
+ })
197
+ })
198
+ }
@@ -0,0 +1,185 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { existsSync } from 'node:fs'
3
+ import { access, mkdir, readFile, rename, writeFile } from 'node:fs/promises'
4
+ import path from 'node:path'
5
+ import process from 'node:process'
6
+
7
+ import { resolveBootstrapPackageCacheDir, resolveRealHomeDir } from './paths'
8
+
9
+ const DEFAULT_PACKAGE_TAG = 'latest'
10
+ const DEFAULT_PACKAGE_LOOKUP_TIMEOUT_MS = 1_000
11
+ const DEFAULT_CACHE_FIRST = true
12
+
13
+ interface PublishedPackageVersionMetadata {
14
+ lookupKey: string
15
+ packageName: string
16
+ packageTag: string
17
+ resolvedAt: string
18
+ version: string
19
+ }
20
+
21
+ export const ensureDirectory = async (targetPath: string) => {
22
+ await mkdir(targetPath, { recursive: true })
23
+ }
24
+
25
+ export const sanitizePackageName = (packageName: string) => packageName.replace(/^@/, '').replace(/[\\/]/g, '__')
26
+
27
+ export const splitPackageName = (packageName: string) => packageName.split('/')
28
+
29
+ export const compareVersionLike = (left: string, right: string) => (
30
+ left.localeCompare(right, 'en', {
31
+ numeric: true,
32
+ sensitivity: 'base'
33
+ })
34
+ )
35
+
36
+ const hashValue = (value: string) => createHash('sha1').update(value).digest('hex')
37
+
38
+ export const resolvePackageTag = () => process.env.ONEWORKS_BOOTSTRAP_PACKAGE_TAG?.trim() || DEFAULT_PACKAGE_TAG
39
+
40
+ export const resolvePackageLookupTimeoutMs = () => {
41
+ const rawValue = process.env.ONEWORKS_BOOTSTRAP_PACKAGE_LOOKUP_TIMEOUT_MS?.trim()
42
+ if (!rawValue) {
43
+ return DEFAULT_PACKAGE_LOOKUP_TIMEOUT_MS
44
+ }
45
+
46
+ const parsedValue = Number.parseInt(rawValue, 10)
47
+ return Number.isFinite(parsedValue) && parsedValue > 0
48
+ ? parsedValue
49
+ : DEFAULT_PACKAGE_LOOKUP_TIMEOUT_MS
50
+ }
51
+
52
+ export const shouldUseCachedPackageVersionFirst = () => {
53
+ const rawValue = process.env.ONEWORKS_BOOTSTRAP_PACKAGE_CACHE_FIRST?.trim().toLowerCase()
54
+ if (rawValue == null || rawValue === '') {
55
+ return DEFAULT_CACHE_FIRST
56
+ }
57
+
58
+ return !['0', 'false', 'no', 'off'].includes(rawValue)
59
+ }
60
+
61
+ export const resolvePackageCacheDir = (packageName: string, version: string) => (
62
+ path.join(resolveBootstrapPackageCacheDir(), 'npm', sanitizePackageName(packageName), version)
63
+ )
64
+
65
+ export const resolvePackageCacheRootDir = (packageName: string) => (
66
+ path.join(resolveBootstrapPackageCacheDir(), 'npm', sanitizePackageName(packageName))
67
+ )
68
+
69
+ export const resolvePackageInstallDir = (cacheDir: string, packageName: string) => (
70
+ path.join(cacheDir, 'node_modules', ...splitPackageName(packageName))
71
+ )
72
+
73
+ const resolvePackageVersionMetadataDir = () => path.join(resolveBootstrapPackageCacheDir(), 'npm-version-cache')
74
+
75
+ const resolveProjectNpmrc = () => {
76
+ const projectNpmrc = path.resolve(process.cwd(), '.npmrc')
77
+ return existsSync(projectNpmrc) ? projectNpmrc : undefined
78
+ }
79
+
80
+ export const resolvePackageManagerEnv = () => {
81
+ const userConfig = process.env.npm_config_userconfig ?? process.env.NPM_CONFIG_USERCONFIG ?? resolveProjectNpmrc()
82
+
83
+ return {
84
+ ...process.env,
85
+ HOME: resolveRealHomeDir(),
86
+ USERPROFILE: resolveRealHomeDir(),
87
+ npm_config_cache: path.join(resolveBootstrapPackageCacheDir(), 'npm-cache'),
88
+ npm_config_replace_registry_host: 'never',
89
+ npm_config_update_notifier: 'false',
90
+ NPM_CONFIG_REPLACE_REGISTRY_HOST: 'never',
91
+ ...(userConfig != null
92
+ ? {
93
+ NPM_CONFIG_USERCONFIG: userConfig,
94
+ npm_config_userconfig: userConfig
95
+ }
96
+ : {})
97
+ }
98
+ }
99
+
100
+ const readOptionalFile = async (filePath: string | undefined) => {
101
+ if (filePath == null || filePath === '') {
102
+ return undefined
103
+ }
104
+
105
+ try {
106
+ return await readFile(filePath, 'utf8')
107
+ } catch {
108
+ return undefined
109
+ }
110
+ }
111
+
112
+ const resolvePackageLookupKey = async (packageName: string) => {
113
+ const env = resolvePackageManagerEnv()
114
+ const userConfig = env.npm_config_userconfig ?? env.NPM_CONFIG_USERCONFIG
115
+ const userConfigContent = await readOptionalFile(userConfig)
116
+
117
+ return JSON.stringify({
118
+ packageName,
119
+ packageTag: resolvePackageTag(),
120
+ registry: env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY ?? '',
121
+ userConfig: userConfig ?? '',
122
+ userConfigContentHash: userConfigContent == null ? '' : hashValue(userConfigContent)
123
+ })
124
+ }
125
+
126
+ const resolvePackageVersionMetadataPath = async (packageName: string) => {
127
+ const lookupKey = await resolvePackageLookupKey(packageName)
128
+ return {
129
+ lookupKey,
130
+ metadataPath: path.join(
131
+ resolvePackageVersionMetadataDir(),
132
+ `${sanitizePackageName(packageName)}-${hashValue(lookupKey)}.json`
133
+ )
134
+ }
135
+ }
136
+
137
+ export const readPublishedPackageVersionMetadata = async (packageName: string) => {
138
+ const { lookupKey, metadataPath } = await resolvePackageVersionMetadataPath(packageName)
139
+
140
+ try {
141
+ const content = await readFile(metadataPath, 'utf8')
142
+ const parsed = JSON.parse(content) as Partial<PublishedPackageVersionMetadata>
143
+ if (
144
+ parsed.lookupKey === lookupKey &&
145
+ parsed.packageName === packageName &&
146
+ parsed.packageTag === resolvePackageTag() &&
147
+ typeof parsed.version === 'string' &&
148
+ parsed.version.trim()
149
+ ) {
150
+ return {
151
+ metadataPath,
152
+ version: parsed.version.trim()
153
+ }
154
+ }
155
+ } catch {
156
+ // Ignore missing or invalid metadata and use the registry path.
157
+ }
158
+
159
+ return undefined
160
+ }
161
+
162
+ export const writePublishedPackageVersionMetadata = async (packageName: string, version: string) => {
163
+ const { lookupKey, metadataPath } = await resolvePackageVersionMetadataPath(packageName)
164
+ await ensureDirectory(path.dirname(metadataPath))
165
+
166
+ const tempPath = `${metadataPath}.${process.pid}.${Date.now()}.tmp`
167
+ const metadata: PublishedPackageVersionMetadata = {
168
+ lookupKey,
169
+ packageName,
170
+ packageTag: resolvePackageTag(),
171
+ resolvedAt: new Date().toISOString(),
172
+ version
173
+ }
174
+ await writeFile(tempPath, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8')
175
+ await rename(tempPath, metadataPath)
176
+ }
177
+
178
+ export const isExistingPath = async (targetPath: string) => {
179
+ try {
180
+ await access(targetPath)
181
+ return true
182
+ } catch {
183
+ return false
184
+ }
185
+ }
@@ -0,0 +1,139 @@
1
+ import { readFile, readdir, rename, rm } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import {
6
+ compareVersionLike,
7
+ ensureDirectory,
8
+ isExistingPath,
9
+ resolvePackageCacheDir,
10
+ resolvePackageCacheRootDir,
11
+ resolvePackageInstallDir,
12
+ resolvePackageManagerEnv
13
+ } from './npm-package-cache'
14
+ import { runBufferedCommand } from './process-utils'
15
+ import { createBootstrapProgress } from './progress'
16
+
17
+ const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm'
18
+
19
+ interface InstalledPackageInfo {
20
+ packageDir: string
21
+ version: string
22
+ }
23
+
24
+ export const readInstalledPackageVersion = async (packageDir: string) => {
25
+ const packageJsonPath = path.join(packageDir, 'package.json')
26
+ if (!(await isExistingPath(packageJsonPath))) {
27
+ return undefined
28
+ }
29
+
30
+ try {
31
+ const content = await readFile(packageJsonPath, 'utf8')
32
+ const packageJson = JSON.parse(content) as { version?: unknown }
33
+ return typeof packageJson.version === 'string' ? packageJson.version : undefined
34
+ } catch {
35
+ return undefined
36
+ }
37
+ }
38
+
39
+ export const findInstalledPublishedPackageVersion = async (packageName: string) => {
40
+ let versions: string[]
41
+ try {
42
+ versions = (await readdir(resolvePackageCacheRootDir(packageName), { withFileTypes: true }))
43
+ .filter(entry => entry.isDirectory())
44
+ .map(entry => entry.name)
45
+ } catch {
46
+ return undefined
47
+ }
48
+
49
+ const installedVersions: string[] = []
50
+ for (const version of versions) {
51
+ const packageDir = resolvePackageInstallDir(resolvePackageCacheDir(packageName, version), packageName)
52
+ const installedVersion = await readInstalledPackageVersion(packageDir)
53
+ if (installedVersion === version) {
54
+ installedVersions.push(version)
55
+ }
56
+ }
57
+
58
+ return installedVersions.sort(compareVersionLike).at(-1)
59
+ }
60
+
61
+ const formatInstallError = (message: string, stderr: string) => {
62
+ const detail = stderr.trim()
63
+ return detail ? `${message}\n${detail}` : message
64
+ }
65
+
66
+ export const installPublishedPackage = async (packageName: string, version: string): Promise<InstalledPackageInfo> => {
67
+ const cacheDir = resolvePackageCacheDir(packageName, version)
68
+ const packageDir = resolvePackageInstallDir(cacheDir, packageName)
69
+ const installedVersion = await readInstalledPackageVersion(packageDir)
70
+ if (installedVersion === version) {
71
+ return { packageDir, version }
72
+ }
73
+
74
+ const stagingDir = `${cacheDir}.tmp-${process.pid}-${Date.now()}`
75
+ await rm(stagingDir, { recursive: true, force: true })
76
+ await ensureDirectory(stagingDir)
77
+
78
+ const progress = createBootstrapProgress({
79
+ label: `installing ${packageName}@${version} into bootstrap cache`
80
+ })
81
+ try {
82
+ const result = await runBufferedCommand({
83
+ command: NPM_BIN,
84
+ args: [
85
+ 'install',
86
+ '--prefix',
87
+ stagingDir,
88
+ '--no-audit',
89
+ '--no-fund',
90
+ '--loglevel=error',
91
+ `${packageName}@${version}`
92
+ ],
93
+ env: resolvePackageManagerEnv()
94
+ })
95
+
96
+ if (result.code !== 0) {
97
+ throw new Error(formatInstallError(`Failed to install ${packageName}@${version}.`, result.stderr))
98
+ }
99
+
100
+ await ensureDirectory(path.dirname(cacheDir))
101
+ await rm(cacheDir, { recursive: true, force: true })
102
+ await rename(stagingDir, cacheDir)
103
+ progress.finish(`cached ${packageName}@${version}`)
104
+ } catch (error) {
105
+ progress.fail(`failed to cache ${packageName}@${version}`)
106
+ await rm(stagingDir, { recursive: true, force: true }).catch(() => {})
107
+ throw error
108
+ }
109
+
110
+ return {
111
+ packageDir: resolvePackageInstallDir(cacheDir, packageName),
112
+ version
113
+ }
114
+ }
115
+
116
+ export const resolvePackageBinEntrypoint = async (packageDir: string, commandName?: string) => {
117
+ const packageJsonContent = await readFile(path.join(packageDir, 'package.json'), 'utf8')
118
+ const packageJson = JSON.parse(packageJsonContent) as { bin?: unknown }
119
+ const { bin } = packageJson
120
+
121
+ if (typeof bin === 'string') {
122
+ return path.resolve(packageDir, bin)
123
+ }
124
+
125
+ if (bin == null || typeof bin !== 'object') {
126
+ throw new Error(`Package ${packageDir} does not expose a CLI bin.`)
127
+ }
128
+
129
+ const binEntries = Object.entries(bin).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
130
+ if (binEntries.length === 0) {
131
+ throw new Error(`Package ${packageDir} does not expose a CLI bin.`)
132
+ }
133
+
134
+ const matchedEntry = commandName != null
135
+ ? binEntries.find(([binName]) => binName === commandName)
136
+ : undefined
137
+
138
+ return path.resolve(packageDir, (matchedEntry ?? binEntries[0])[1])
139
+ }