Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# npm v11+ settings (not pnpm — pnpm v11 only reads auth/registry from .npmrc).
min-release-age=7
trust-policy=no-downgrade
1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Migrated from .npmrc (pnpm v11 only reads auth/registry from .npmrc).
ignoreScripts: true
linkWorkspacePackages: false
trustPolicy: no-downgrade

# Wait 7 days (10080 minutes) before installing newly published packages.
minimumReleaseAge: 10080
Expand Down
35 changes: 35 additions & 0 deletions scripts/constants/testing.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,45 @@ export const ALLOW_TEST_FAILURES_BY_ECOSYSTEM = new Map([
[
'npm',
new Set([
// TODO: Remove when pnpm/pnpm#11238 is fixed (ERR_PNPM_MISSING_TIME).
// These fail because pnpm v11 intermittently fails to fetch the "time"
// field in registry metadata for transitive deps (globals, lodash, dotenv,
// undici-types, @typescript-eslint/typescript-estree).
'array-buffer-byte-length',
'array-includes',
'array.from',
'array.of',
'array.prototype.at',
'array.prototype.every',
'array.prototype.filter',
'array.prototype.find',
'array.prototype.findlast',
'array.prototype.findlastindex',
'array.prototype.flat',
'array.prototype.foreach',
'array.prototype.map',
'array.prototype.reduce',
'array.prototype.toreversed',
'array.prototype.tosorted',
'asynciterator.prototype',
'available-typed-arrays',
'is-unicode-supported',
// es-define-property installation fails intermittently in CI environments.
'es-define-property',
// es-get-iterator installation fails intermittently in CI environments.
'es-get-iterator',
// es-set-tostringtag installation fails intermittently in CI environments.
'es-set-tostringtag',
// for-each installation fails intermittently in CI environments.
'for-each',
// function-bind installation fails intermittently in CI environments.
'function-bind',
// function.prototype.name installation fails intermittently in CI environments.
'function.prototype.name',
// get-symbol-description installation fails intermittently in CI environments.
'get-symbol-description',
// has-tostringtag installation fails intermittently in CI environments.
'has-tostringtag',
// is-boolean-object installation fails intermittently in CI environments.
'is-boolean-object',
// object.assign installation fails intermittently in CI environments.
Expand Down
70 changes: 38 additions & 32 deletions scripts/npm/install-npm-packages.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ const ENV = { CI: getCI() }
const spinner = getDefaultSpinner()

import { parseArgs } from '@socketsecurity/lib/argv/parse'
import { readFileUtf8, readJson, writeJson } from '@socketsecurity/lib/fs'
import {
readFileUtf8,
readJson,
safeDelete,
writeJson,
} from '@socketsecurity/lib/fs'
import { LOG_SYMBOLS, getDefaultLogger } from '@socketsecurity/lib/logger'
import { readPackageJson } from '@socketsecurity/lib/packages'
import { pEach, pRetry } from '@socketsecurity/lib/promises'
Expand All @@ -104,6 +109,7 @@ import {
import { filterPackagesByChanges } from '../utils/git.mjs'
import {
copySocketOverride,
PNPM_REAL_BIN,
PNPM_HOISTED_INSTALL_FLAGS,
PNPM_INSTALL_ENV,
} from '../utils/package.mjs'
Expand Down Expand Up @@ -524,7 +530,7 @@ async function installPackage(packageInfo) {
// If Socket override has different dependencies, install them.
// Always use pnpm for installation to avoid npm override conflicts.
if (overridePkgJson.dependencies) {
await runCommandQuietStrict('pnpm', ['install'], {
await runCommandQuietStrict(PNPM_REAL_BIN, ['install'], {
cwd: installedPath,
env: {
...process.env,
Expand All @@ -539,7 +545,7 @@ async function installPackage(packageInfo) {
// The hoisted install at parent level makes test runners available to nested package.
// Always use pnpm for installation to avoid npm override conflicts.
await runCommandQuietStrict(
'pnpm',
PNPM_REAL_BIN,
['install', ...PNPM_HOISTED_INSTALL_FLAGS],
{
cwd: packageTempDir,
Expand Down Expand Up @@ -658,7 +664,7 @@ async function installPackage(packageInfo) {
const pkgJsonStats = await fs.stat(pkgJsonPath)
const fileContent = await readFileUtf8(pkgJsonPath)
throw new Error(
`Invalid package.json after 3 retries: ${lastError.message}. File size: ${pkgJsonStats.size}, Content preview: ${fileContent.slice(0, 200)}`,
`Invalid package.json after ${JSON_PARSE_MAX_RETRIES} retries: ${lastError.message}. File size: ${pkgJsonStats.size}, Content preview: ${fileContent.slice(0, 200)}`,
)
}
const { scripts } = editablePkgJson.content
Expand Down Expand Up @@ -751,30 +757,25 @@ async function installPackage(packageInfo) {
},
}

// pnpm v11 ignores overrides in package.json pnpm.overrides for subdependencies
// (regression from v10). Overrides in pnpm-workspace.yaml still work.
// Also set pnpm v11 settings in workspace yaml since CLI --config flags
// may not override workspace-level settings.
// pnpm v11: write workspace yaml for every test install.
// - registrySupportsTimeField: false — CLI flag is ignored (pnpm/pnpm#11238),
// but the workspace yaml setting works.
// - overrides — pnpm v11 ignores package.json pnpm.overrides for subdependencies
// (regression from v10), but workspace yaml overrides still work.
if (packageManager === 'pnpm') {
const overrideLines = Object.entries(pnpmOverrides)
.map(([pkg, spec]) => ` ${pkg}: '${spec}'`)
.join('\n')
const overridesSection =
Object.keys(pnpmOverrides).length > 0
? `\noverrides:\n${overrideLines}\n`
: ''
const sections = [
'packages:\n - .\n',
'registrySupportsTimeField: false',
]
if (Object.keys(pnpmOverrides).length > 0) {
const overrideLines = Object.entries(pnpmOverrides)
.map(([pkg, spec]) => ` ${pkg}: '${spec}'`)
.join('\n')
sections.push(`\noverrides:\n${overrideLines}`)
}
await fs.writeFile(
path.join(packageTempDir, 'pnpm-workspace.yaml'),
[
'packages:',
' - .',
'',
'# pnpm v11 settings for third-party test installs.',
'blockExoticSubdeps: false',
'strictDepBuilds: false',
'resolutionMode: highest',
overridesSection,
].join('\n'),
sections.join('\n') + '\n',
'utf8',
)
} else if (packageManager === 'npm') {
Expand All @@ -789,11 +790,12 @@ async function installPackage(packageInfo) {
// registry timeouts, and rate limiting from npm registry.
// Retry up to 3 times with exponential backoff (1s base delay, 2x multiplier).
// Unset NODE_ENV and CI to prevent package manager from skipping devDependencies.
// Always use pnpm for installation to avoid npm override conflicts.
// Retry with backoff — pnpm v11 intermittently fails with ERR_PNPM_MISSING_TIME
// when registry metadata is cached without the "time" field (pnpm/pnpm#11238).
await pRetry(
async () => {
await runCommandQuietStrict(
'pnpm',
PNPM_REAL_BIN,
['install', ...PNPM_HOISTED_INSTALL_FLAGS],
{
cwd: packageTempDir,
Expand All @@ -807,8 +809,12 @@ async function installPackage(packageInfo) {
},
{
backoffFactor: 2,
baseDelayMs: 1000,
retries: 3,
baseDelayMs: 3000,
onRetry: async () => {
await safeDelete(path.join(packageTempDir, 'node_modules'))
await safeDelete(path.join(packageTempDir, 'pnpm-lock.yaml'))
},
retries: 12,
},
)

Expand All @@ -831,7 +837,7 @@ async function installPackage(packageInfo) {
// Always use pnpm for installation to avoid npm override conflicts.
writeProgress('👷')
await runCommandQuietStrict(
'pnpm',
PNPM_REAL_BIN,
['install', ...PNPM_HOISTED_INSTALL_FLAGS],
{
cwd: packageTempDir,
Expand Down Expand Up @@ -955,7 +961,7 @@ async function installPackage(packageInfo) {
// If Socket override has different dependencies, install them.
// Always use pnpm for installation to avoid npm override conflicts.
if (overridePkgJson.dependencies) {
await runCommandQuietStrict('pnpm', ['install'], {
await runCommandQuietStrict(PNPM_REAL_BIN, ['install'], {
cwd: installedPath,
env: {
...process.env,
Expand All @@ -970,7 +976,7 @@ async function installPackage(packageInfo) {
// The hoisted install at parent level makes test runners available to nested package.
// Always use pnpm for installation to avoid npm override conflicts.
await runCommandQuietStrict(
'pnpm',
PNPM_REAL_BIN,
['install', ...PNPM_HOISTED_INSTALL_FLAGS],
{
cwd: packageTempDir,
Expand Down
Loading
Loading