Skip to content

Commit 9e5dc27

Browse files
committed
Restrict overrides to those supported by the environment
1 parent f222bfa commit 9e5dc27

File tree

4 files changed

+106
-73
lines changed

4 files changed

+106
-73
lines changed

src/commands/analytics.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import fs from 'node:fs'
1+
import fs from 'node:fs/promises'
22

33
import blessed from 'blessed'
44
// @ts-ignore
@@ -186,30 +186,26 @@ async function fetchOrgAnalyticsData(
186186
if (result.success === false) {
187187
return handleUnsuccessfulApiResponse('getOrgAnalytics', result, spinner)
188188
}
189-
190189
spinner.stop()
191190

192191
if (!result.data.length) {
193192
return console.log(
194193
'No analytics data is available for this organization yet.'
195194
)
196195
}
197-
198196
const data = formatData(result.data, 'org')
199-
200197
if (outputJson && !filePath) {
201198
return console.log(result.data)
202199
}
203-
204200
if (filePath) {
205-
fs.writeFile(filePath, JSON.stringify(result.data), err => {
206-
err
207-
? console.error(err)
208-
: console.log(`Data successfully written to ${filePath}`)
209-
})
201+
try {
202+
await fs.writeFile(filePath, JSON.stringify(result.data), 'utf8')
203+
console.log(`Data successfully written to ${filePath}`)
204+
} catch (e: any) {
205+
console.error(e)
206+
}
210207
return
211208
}
212-
213209
return displayAnalyticsScreen(data)
214210
}
215211

@@ -361,22 +357,19 @@ async function fetchRepoAnalyticsData(
361357
'No analytics data is available for this organization yet.'
362358
)
363359
}
364-
365360
const data = formatData(result.data, 'repo')
366-
367361
if (outputJson && !filePath) {
368362
return console.log(result.data)
369363
}
370-
371364
if (filePath) {
372-
fs.writeFile(filePath, JSON.stringify(result.data), err => {
373-
err
374-
? console.error(err)
375-
: console.log(`Data successfully written to ${filePath}`)
376-
})
365+
try {
366+
await fs.writeFile(filePath, JSON.stringify(result.data), 'utf8')
367+
console.log(`Data successfully written to ${filePath}`)
368+
} catch (e: any) {
369+
console.error(e)
370+
}
377371
return
378372
}
379-
380373
return displayAnalyticsScreen(data)
381374
}
382375

src/commands/optimize.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { escapeRegExp } from '../utils/regexps'
1414
import { toSortedObject } from '../utils/sorts'
1515

1616
import type { Content as PackageJsonContent } from '@npmcli/package-json'
17+
import type { ManifestEntry } from '@socketsecurity/registry'
1718
import type { CliSubcommand } from '../utils/meow-with-subcommands'
1819
import type {
1920
Agent,
@@ -26,9 +27,7 @@ const COMMAND_TITLE = 'Socket Optimize'
2627
const OVERRIDES_FIELD_NAME = 'overrides'
2728
const RESOLUTIONS_FIELD_NAME = 'resolutions'
2829

29-
const availableOverrides = getManifestData('npm')!.filter(({ 1: d }) =>
30-
d.engines?.node?.startsWith('>=18')
31-
)
30+
const manifestNpmOverrides = getManifestData('npm')!
3231

3332
type NpmOverrides = { [key: string]: string | StringKeyValueObject }
3433
type PnpmOrYarnOverrides = { [key: string]: string }
@@ -130,10 +129,10 @@ type AddOverridesConfig = {
130129
isWorkspace: boolean
131130
lockSrc: string
132131
lockIncludes: LockIncludes
132+
manifestEntries: ManifestEntry[]
133133
pkgJsonPath: string
134134
pkgJsonStr: string
135135
pkgJson: PackageJsonContent
136-
overrides?: Overrides | undefined
137136
}
138137

139138
type AddOverridesState = {
@@ -148,6 +147,7 @@ async function addOverrides(
148147
isWorkspace,
149148
lockSrc,
150149
lockIncludes,
150+
manifestEntries,
151151
pkgJsonPath
152152
}: AddOverridesConfig,
153153
aoState: AddOverridesState
@@ -194,7 +194,7 @@ async function addOverrides(
194194
)
195195
}
196196
const aliasMap = new Map<string, string>()
197-
for (const { 1: data } of availableOverrides) {
197+
for (const { 1: data } of manifestEntries) {
198198
const { name: regPkgName, package: origPkgName, version } = data
199199
for (const { 1: depObj } of depEntries) {
200200
let pkgSpec = depObj[origPkgName]
@@ -212,7 +212,7 @@ async function addOverrides(
212212
aliasMap.set(origPkgName, pkgSpec)
213213
}
214214
}
215-
for (const { type, overrides } of overridesDataObjects) {
215+
for (const { overrides, type } of overridesDataObjects) {
216216
if (
217217
!hasOwn(overrides, origPkgName) &&
218218
lockIncludes(lockSrc, origPkgName)
@@ -233,7 +233,7 @@ async function addOverrides(
233233
}
234234
if (packageNames.size) {
235235
editablePkgJson.update(<PackageJsonContent>Object.fromEntries(depEntries))
236-
for (const { type, overrides } of overridesDataObjects) {
236+
for (const { overrides, type } of overridesDataObjects) {
237237
updateManifestByAgent[type](editablePkgJson, toSortedObject(overrides))
238238
}
239239
await editablePkgJson.save()
@@ -259,6 +259,7 @@ export const optimize: CliSubcommand = {
259259
isWorkspace,
260260
lockSrc,
261261
lockPath,
262+
minimumNodeVersion,
262263
pkgJsonPath,
263264
pkgJsonStr,
264265
pkgJson,
@@ -273,7 +274,7 @@ export const optimize: CliSubcommand = {
273274
})
274275
if (!supported) {
275276
console.log(
276-
`✘ ${COMMAND_TITLE}: Package engines.node range is not supported`
277+
`✘ ${COMMAND_TITLE}: No supported Node or browser range detected`
277278
)
278279
return
279280
}
@@ -301,6 +302,10 @@ export const optimize: CliSubcommand = {
301302
agent === 'bun'
302303
? lockIncludesByAgent.yarn
303304
: lockIncludesByAgent[agent]
305+
const nodeRange = `>=${minimumNodeVersion}`
306+
const manifestEntries = manifestNpmOverrides.filter(({ 1: data }) =>
307+
semver.satisfies(semver.coerce(data.engines.node)!, nodeRange)
308+
)
304309
await addOverrides(
305310
<AddOverridesConfig>{
306311
__proto__: null,
@@ -309,6 +314,7 @@ export const optimize: CliSubcommand = {
309314
isWorkspace,
310315
lockIncludes,
311316
lockSrc,
317+
manifestEntries,
312318
pkgJsonPath,
313319
pkgJsonStr,
314320
pkgJson

src/utils/objects.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
export function getOwn(obj: any, propKey: PropertyKey): any {
2-
if (obj === null || obj === undefined) return undefined
3-
return Object.hasOwn(obj, propKey) ? obj[propKey] : undefined
4-
}
5-
61
export function hasOwn(obj: any, propKey: PropertyKey): boolean {
72
if (obj === null || obj === undefined) return false
83
return Object.hasOwn(obj, propKey)

src/utils/package-manager-detector.ts

Lines changed: 79 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import which from 'which'
88

99
import { existsSync, findUp, readFileBinary, readFileUtf8 } from './fs'
1010
import { parseJSONObject } from './json'
11-
import { getOwn, isObjectObject } from './objects'
11+
import { isObjectObject } from './objects'
1212
import { isNonEmptyString } from './strings'
1313

1414
import type { Content as PackageJsonContent } from '@npmcli/package-json'
@@ -40,9 +40,50 @@ export const LOCKS: Record<string, string> = {
4040
'node_modules/.package-lock.json': 'npm'
4141
}
4242

43-
const MAINTAINED_NODE_VERSIONS = browserslist('maintained node versions')
44-
// Trim value, e.g. 'node 22.5.0' to '22.5.0'
45-
.map(v => v.slice(5))
43+
const numericCollator = new Intl.Collator(undefined, {
44+
numeric: true,
45+
sensitivity: 'base'
46+
})
47+
const { compare: alphaNumericComparator } = numericCollator
48+
49+
const maintainedNodeVersions = (() => {
50+
// Under the hood browserlist uses the node-releases package which is out of date:
51+
// https://github.com/chicoxyzzy/node-releases/issues/37
52+
// So we maintain a manual version list for now.
53+
// https://nodejs.org/en/about/previous-releases#looking-for-latest-release-of-a-version-branch
54+
const manualPrev = '18.20.4'
55+
const manualCurr = '20.18.0'
56+
const manualNext = '22.10.0'
57+
58+
const query = browserslist('maintained node versions')
59+
// Trim value, e.g. 'node 22.5.0' to '22.5.0'.
60+
.map(s => s.slice(5 /*'node '.length*/))
61+
// Sort ascending.
62+
.toSorted(alphaNumericComparator)
63+
const queryPrev = query.at(0) ?? manualPrev
64+
const queryCurr = query.at(1) ?? manualCurr
65+
const queryNext = query.at(2) ?? manualNext
66+
67+
const previous = semver.maxSatisfying(
68+
[queryPrev, manualPrev],
69+
`^${semver.major(queryPrev)}`
70+
)!
71+
const current = semver.maxSatisfying(
72+
[queryCurr, manualCurr],
73+
`^${semver.major(queryCurr)}`
74+
)!
75+
const next = semver.maxSatisfying(
76+
[queryNext, manualNext],
77+
`^${semver.major(queryNext)}`
78+
)!
79+
return Object.freeze(
80+
Object.assign([previous, current, next], {
81+
previous,
82+
current,
83+
next
84+
})
85+
)
86+
})()
4687

4788
export type DetectOptions = {
4889
cwd?: string
@@ -57,6 +98,7 @@ export type DetectResult = Readonly<{
5798
isWorkspace: boolean
5899
lockPath: string | undefined
59100
lockSrc: string | undefined
101+
minimumNodeVersion: string
60102
pkgJson: PackageJsonContent | undefined
61103
pkgJsonPath: string | undefined
62104
pkgJsonStr: string | undefined
@@ -69,25 +111,25 @@ export type DetectResult = Readonly<{
69111

70112
type ReadLockFile = (
71113
lockPath: string,
72-
agentExecPath?: string
114+
agentExecPath: string
73115
) => Promise<string | undefined>
74116

75117
const readLockFileByAgent: Record<AgentPlusBun, ReadLockFile> = (() => {
76118
const wrapReader =
77119
(
78120
reader: (
79121
lockPath: string,
80-
agentExecPath?: string
122+
agentExecPath: string
81123
) => Promise<string | undefined>
82124
): ReadLockFile =>
83-
async (lockPath: string, agentExecPath?: string) => {
125+
async (lockPath: string, agentExecPath: string) => {
84126
try {
85127
return await reader(lockPath, agentExecPath)
86128
} catch {}
87129
return undefined
88130
}
89131
return {
90-
bun: wrapReader(async (lockPath: string, agentExecPath?: string) => {
132+
bun: wrapReader(async (lockPath: string, agentExecPath: string) => {
91133
let lockBuffer: Buffer | undefined
92134
try {
93135
lockBuffer = <Buffer>await readFileBinary(lockPath)
@@ -99,7 +141,7 @@ const readLockFileByAgent: Record<AgentPlusBun, ReadLockFile> = (() => {
99141
} catch {}
100142
// To print a Yarn lockfile to your console without writing it to disk use `bun bun.lockb`.
101143
// https://bun.sh/guides/install/yarnlock
102-
return (await spawn(agentExecPath ?? 'bun', [lockPath])).stdout
144+
return (await spawn(agentExecPath, [lockPath])).stdout
103145
}),
104146
npm: wrapReader(async (lockPath: string) => await readFileUtf8(lockPath)),
105147
pnpm: wrapReader(async (lockPath: string) => await readFileUtf8(lockPath)),
@@ -126,8 +168,8 @@ export async function detect({
126168
? (parseJSONObject(pkgJsonStr) ?? undefined)
127169
: undefined
128170
const pkgManager = <string | undefined>(
129-
(isNonEmptyString(getOwn(pkgJson, 'packageManager'))
130-
? pkgJson?.['packageManager']
171+
(isNonEmptyString(pkgJson?.['packageManager'])
172+
? pkgJson['packageManager']
131173
: undefined)
132174
)
133175

@@ -156,60 +198,56 @@ export async function detect({
156198
agent = 'npm'
157199
onUnknown?.(pkgManager)
158200
}
159-
const agentExecPath = (await which(agent, { nothrow: true })) ?? agent
160201

161-
let lockSrc: string | undefined
202+
const agentExecPath = (await which(agent, { nothrow: true })) ?? agent
162203
const targets = {
163204
browser: false,
164205
node: true
165206
}
166-
207+
let lockSrc: string | undefined
167208
let isPrivate = false
168209
let isWorkspace = false
210+
let minimumNodeVersion = maintainedNodeVersions.previous
169211
if (pkgJson) {
170212
const pkgPath = path.dirname(pkgJsonPath!)
171213
isPrivate = !!pkgJson['private']
172214
isWorkspace =
173215
!!pkgJson['workspaces'] ||
174216
existsSync(path.join(pkgPath, `${PNPM_WORKSPACE}.yaml`)) ||
175217
existsSync(path.join(pkgPath, `${PNPM_WORKSPACE}.yml`))
176-
let browser: boolean | undefined
177-
let node: boolean | undefined
178-
const browserField = getOwn(pkgJson, 'browser')
218+
const browserField = pkgJson['browser']
179219
if (isNonEmptyString(browserField) || isObjectObject(browserField)) {
180-
browser = true
220+
targets.browser = true
181221
}
182-
const nodeRange = getOwn(pkgJson['engines'], 'node')
222+
const nodeRange = (pkgJson as any)['engines']?.['node']
183223
if (isNonEmptyString(nodeRange)) {
184-
node = MAINTAINED_NODE_VERSIONS.some(v => {
185-
const coerced = semver.coerce(nodeRange)
186-
return coerced && semver.satisfies(coerced, `^${semver.major(v)}`)
187-
})
224+
const coerced = semver.coerce(nodeRange)
225+
if (coerced) {
226+
minimumNodeVersion = coerced.version
227+
}
188228
}
189-
const browserslistQuery = getOwn(pkgJson, 'browserslist')
229+
const browserslistQuery = <string[] | undefined>pkgJson['browserslist']
190230
if (Array.isArray(browserslistQuery)) {
191231
const browserslistTargets = browserslist(browserslistQuery)
232+
.map(s => s.toLowerCase())
233+
.toSorted(alphaNumericComparator)
192234
const browserslistNodeTargets = browserslistTargets
193235
.filter(v => v.startsWith('node '))
194-
.map(v => v.slice(5))
195-
if (browser === undefined && browserslistTargets.length) {
196-
browser = browserslistTargets.length !== browserslistNodeTargets.length
236+
.map(v => v.slice(5 /*'node '.length*/))
237+
if (!targets.browser && browserslistTargets.length) {
238+
targets.browser =
239+
browserslistTargets.length !== browserslistNodeTargets.length
197240
}
198-
if (node === undefined && browserslistNodeTargets.length) {
199-
node = MAINTAINED_NODE_VERSIONS.some(v =>
200-
browserslistNodeTargets.some(t => {
201-
const coerced = semver.coerce(t)
202-
return coerced && semver.satisfies(coerced, `^${semver.major(v)}`)
203-
})
204-
)
241+
if (browserslistNodeTargets.length) {
242+
const coerced = semver.coerce(browserslistNodeTargets[0])
243+
if (coerced && semver.lt(coerced, minimumNodeVersion)) {
244+
minimumNodeVersion = coerced.version
245+
}
205246
}
206247
}
207-
if (browser !== undefined) {
208-
targets.browser = browser
209-
}
210-
if (node !== undefined) {
211-
targets.node = node
212-
}
248+
targets.node = maintainedNodeVersions.some(v =>
249+
semver.satisfies(v, `>=${minimumNodeVersion}`)
250+
)
213251
lockSrc =
214252
typeof lockPath === 'string'
215253
? await readLockFileByAgent[agent](lockPath, agentExecPath)
@@ -225,6 +263,7 @@ export async function detect({
225263
isWorkspace,
226264
lockPath,
227265
lockSrc,
266+
minimumNodeVersion,
228267
pkgJson,
229268
pkgJsonPath,
230269
pkgJsonStr,

0 commit comments

Comments
 (0)