@servicetitan/install 39.2.0 → 40.0.0
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.
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/install.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/package-manager/npm-package-manager.d.ts +2 -0
- package/dist/package-manager/npm-package-manager.d.ts.map +1 -1
- package/dist/package-manager/npm-package-manager.js +63 -4
- package/dist/package-manager/npm-package-manager.js.map +1 -1
- package/dist/package-manager/package-manager.d.ts +1 -0
- package/dist/package-manager/package-manager.d.ts.map +1 -1
- package/dist/package-manager/package-manager.js.map +1 -1
- package/dist/package-manager/pnpm-package-manager.d.ts +2 -0
- package/dist/package-manager/pnpm-package-manager.d.ts.map +1 -1
- package/dist/package-manager/pnpm-package-manager.js +40 -1
- package/dist/package-manager/pnpm-package-manager.js.map +1 -1
- package/dist/package-manager/utils/filter-trusted-scripts.d.ts +13 -0
- package/dist/package-manager/utils/filter-trusted-scripts.d.ts.map +1 -0
- package/dist/package-manager/utils/filter-trusted-scripts.js +42 -0
- package/dist/package-manager/utils/filter-trusted-scripts.js.map +1 -0
- package/dist/package-manager/utils/index.d.ts +2 -0
- package/dist/package-manager/utils/index.d.ts.map +1 -1
- package/dist/package-manager/utils/index.js +2 -0
- package/dist/package-manager/utils/index.js.map +1 -1
- package/dist/package-manager/utils/resolve-allow-scripts.d.ts +0 -1
- package/dist/package-manager/utils/resolve-allow-scripts.d.ts.map +1 -1
- package/dist/package-manager/utils/resolve-allow-scripts.js +3 -18
- package/dist/package-manager/utils/resolve-allow-scripts.js.map +1 -1
- package/dist/package-manager/utils/resolve-min-release-age.d.ts +6 -0
- package/dist/package-manager/utils/resolve-min-release-age.d.ts.map +1 -0
- package/dist/package-manager/utils/resolve-min-release-age.js +25 -0
- package/dist/package-manager/utils/resolve-min-release-age.js.map +1 -0
- package/dist/utils/audited-scripts.d.ts +8 -0
- package/dist/utils/audited-scripts.d.ts.map +1 -0
- package/dist/utils/audited-scripts.js +102 -0
- package/dist/utils/audited-scripts.js.map +1 -0
- package/dist/utils/get-packages-with-install-scripts.d.ts.map +1 -1
- package/dist/utils/get-packages-with-install-scripts.js +10 -14
- package/dist/utils/get-packages-with-install-scripts.js.map +1 -1
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/version-compare.d.ts +2 -0
- package/dist/utils/version-compare.d.ts.map +1 -0
- package/dist/utils/version-compare.js +44 -0
- package/dist/utils/version-compare.js.map +1 -0
- package/dist/utils/warn-untrusted-install-scripts.d.ts +2 -1
- package/dist/utils/warn-untrusted-install-scripts.d.ts.map +1 -1
- package/dist/utils/warn-untrusted-install-scripts.js +18 -25
- package/dist/utils/warn-untrusted-install-scripts.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/install.ts +1 -0
- package/src/index.ts +1 -1
- package/src/package-manager/npm-package-manager.ts +89 -6
- package/src/package-manager/package-manager.ts +1 -0
- package/src/package-manager/pnpm-package-manager.ts +47 -1
- package/src/package-manager/utils/filter-trusted-scripts.ts +44 -0
- package/src/package-manager/utils/index.ts +2 -0
- package/src/package-manager/utils/resolve-allow-scripts.ts +0 -8
- package/src/package-manager/utils/resolve-min-release-age.ts +16 -0
- package/src/utils/audited-scripts.ts +92 -0
- package/src/utils/get-packages-with-install-scripts.ts +6 -10
- package/src/utils/index.ts +2 -0
- package/src/utils/version-compare.ts +34 -0
- package/src/utils/warn-untrusted-install-scripts.ts +26 -24
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AUDITED_SCRIPTS,
|
|
5
|
+
getPackagesWithInstallScripts,
|
|
6
|
+
isCI,
|
|
7
|
+
runCommand,
|
|
8
|
+
versionCompare,
|
|
9
|
+
warnUntrustedInstallScripts,
|
|
10
|
+
} from '../utils';
|
|
4
11
|
import type { DeclaredVersionQuery, InstallArgs, ResolvedVersionQuery } from './package-manager';
|
|
5
12
|
import { PackageManager } from './package-manager';
|
|
13
|
+
import type { TrustedScripts } from './utils';
|
|
6
14
|
import {
|
|
7
|
-
|
|
15
|
+
filterTrustedScripts,
|
|
8
16
|
logAllowedScripts,
|
|
17
|
+
parseAllowScripts,
|
|
9
18
|
resolveAllowScripts,
|
|
19
|
+
resolveMinReleaseAge,
|
|
10
20
|
usesStartup,
|
|
11
21
|
withProjectAllowScripts,
|
|
12
22
|
} from './utils';
|
|
13
23
|
|
|
24
|
+
const MIN_NPM_VERSION_FOR_RELEASE_AGE = '11.10.0';
|
|
25
|
+
|
|
14
26
|
interface NpmLockFileEntry {
|
|
15
27
|
version?: string;
|
|
16
28
|
resolved?: string;
|
|
@@ -45,8 +57,16 @@ export class NpmPackageManager extends PackageManager<NpmLockFile> {
|
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
installPackages(args: InstallArgs = {}, options?: Parameters<typeof runCommand>[2]) {
|
|
48
|
-
const
|
|
60
|
+
const builtInIgnoredScripts = AUDITED_SCRIPTS.filter(script => script.action === 'ignore');
|
|
61
|
+
const builtInAllowedScripts = usesStartup()
|
|
62
|
+
? AUDITED_SCRIPTS.filter(script => script.action === 'allow')
|
|
63
|
+
: [];
|
|
64
|
+
const builtInAllowlist = builtInAllowedScripts.map(script => script.name);
|
|
49
65
|
const allowScripts = resolveAllowScripts(args.allowScripts ?? [], builtInAllowlist);
|
|
66
|
+
const userAllowlist = Array.isArray(args.allowScripts)
|
|
67
|
+
? parseAllowScripts(args.allowScripts)
|
|
68
|
+
: [];
|
|
69
|
+
const minReleaseAgeFlags = this.resolveMinReleaseAgeFlags(args);
|
|
50
70
|
|
|
51
71
|
if (!args.quiet) {
|
|
52
72
|
logAllowedScripts(allowScripts, builtInAllowlist);
|
|
@@ -60,6 +80,7 @@ export class NpmPackageManager extends PackageManager<NpmLockFile> {
|
|
|
60
80
|
'--fund=false',
|
|
61
81
|
'--legacy-peer-deps',
|
|
62
82
|
...(allowScripts !== true ? ['--ignore-scripts'] : []),
|
|
83
|
+
...minReleaseAgeFlags,
|
|
63
84
|
...(args.fix ? ['--package-lock-only', '--prefer-dedupe'] : []),
|
|
64
85
|
],
|
|
65
86
|
options
|
|
@@ -69,13 +90,27 @@ export class NpmPackageManager extends PackageManager<NpmLockFile> {
|
|
|
69
90
|
if (args.fix) {
|
|
70
91
|
return;
|
|
71
92
|
}
|
|
93
|
+
let trusted: TrustedScripts | undefined;
|
|
94
|
+
if (Array.isArray(allowScripts)) {
|
|
95
|
+
try {
|
|
96
|
+
trusted = filterTrustedScripts(
|
|
97
|
+
getPackagesWithInstallScripts(),
|
|
98
|
+
builtInAllowedScripts,
|
|
99
|
+
userAllowlist
|
|
100
|
+
);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(String(error));
|
|
103
|
+
console.error('Failed to determine trusted install scripts');
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
72
107
|
/*
|
|
73
108
|
* Rebuild applies when allowScripts is a non-empty explicit list:
|
|
74
109
|
* `true` means npm install already ran every script, `false`
|
|
75
110
|
* means the user opted out.
|
|
76
111
|
*/
|
|
77
|
-
if (
|
|
78
|
-
withProjectAllowScripts(
|
|
112
|
+
if (trusted && trusted.specs.length > 0) {
|
|
113
|
+
withProjectAllowScripts(trusted.names, () => this.runRebuild(trusted.specs));
|
|
79
114
|
}
|
|
80
115
|
/*
|
|
81
116
|
* Warn against the effective allowlist so teams can see which
|
|
@@ -84,7 +119,8 @@ export class NpmPackageManager extends PackageManager<NpmLockFile> {
|
|
|
84
119
|
*/
|
|
85
120
|
if (allowScripts !== false) {
|
|
86
121
|
warnUntrustedInstallScripts(
|
|
87
|
-
|
|
122
|
+
trusted?.specs ?? builtInAllowlist,
|
|
123
|
+
builtInIgnoredScripts
|
|
88
124
|
);
|
|
89
125
|
}
|
|
90
126
|
/*
|
|
@@ -149,6 +185,53 @@ export class NpmPackageManager extends PackageManager<NpmLockFile> {
|
|
|
149
185
|
}
|
|
150
186
|
}
|
|
151
187
|
|
|
188
|
+
private resolveMinReleaseAgeFlags(args: InstallArgs): string[] {
|
|
189
|
+
if (args.fix) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const resolved = resolveMinReleaseAge(args.minReleaseAge);
|
|
194
|
+
if (!resolved) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const version = runCommand(this.command, ['--version']).trim();
|
|
199
|
+
if (versionCompare(version, MIN_NPM_VERSION_FOR_RELEASE_AGE) < 0) {
|
|
200
|
+
console.warn(
|
|
201
|
+
`WARNING: npm ${version} does not support minimum release age protection. Installing without it.`
|
|
202
|
+
);
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (this.hasConfiguredMinReleaseAge()) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return [
|
|
211
|
+
`--min-release-age=${resolved.days}`,
|
|
212
|
+
...resolved.exclude.map(pattern => `--min-release-age-exclude=${pattern}`),
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private hasConfiguredMinReleaseAge(): boolean {
|
|
217
|
+
try {
|
|
218
|
+
const config = JSON.parse(runCommand(this.command, ['config', 'list', '--json']));
|
|
219
|
+
if (Number(config['min-release-age']) > 0) {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
/*
|
|
223
|
+
* On npm >=11.10.0 <11.14.0, min-release-age is flattened into an
|
|
224
|
+
* absolute `before` cutoff date and removed from the config, so
|
|
225
|
+
* fall back to checking that cutoff instead.
|
|
226
|
+
*/
|
|
227
|
+
return (
|
|
228
|
+
typeof config.before === 'string' && new Date(config.before).getTime() < Date.now()
|
|
229
|
+
);
|
|
230
|
+
} catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
152
235
|
private runProjectPostinstall(options?: Parameters<typeof runCommand>[2]) {
|
|
153
236
|
try {
|
|
154
237
|
runCommand(this.command, ['run', 'postinstall', '--if-present'], options);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
import fs from 'fs';
|
|
2
|
-
import { isCI, runCommand } from '../utils';
|
|
3
|
+
import { isCI, runCommand, versionCompare } from '../utils';
|
|
3
4
|
import type { DeclaredVersionQuery, InstallArgs, ResolvedVersionQuery } from './package-manager';
|
|
4
5
|
import { PackageManager } from './package-manager';
|
|
6
|
+
import { resolveMinReleaseAge } from './utils';
|
|
7
|
+
|
|
8
|
+
const MIN_PNPM_VERSION_FOR_RELEASE_AGE = '10.16.0';
|
|
5
9
|
|
|
6
10
|
interface PnpmImporterEntry {
|
|
7
11
|
specifier: string;
|
|
@@ -54,6 +58,8 @@ export class PnpmPackageManager extends PackageManager<PnpmLockFile> {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
installPackages(args: InstallArgs = {}, options?: Parameters<typeof runCommand>[2]) {
|
|
61
|
+
this.warnIfMinReleaseAgeNotConfigured(args);
|
|
62
|
+
|
|
57
63
|
try {
|
|
58
64
|
runCommand(
|
|
59
65
|
this.command,
|
|
@@ -94,6 +100,46 @@ export class PnpmPackageManager extends PackageManager<PnpmLockFile> {
|
|
|
94
100
|
importer?.optionalDependencies?.[dependency];
|
|
95
101
|
return entry ? stripPeerDependencySuffix(entry.version) : undefined;
|
|
96
102
|
}
|
|
103
|
+
|
|
104
|
+
private warnIfMinReleaseAgeNotConfigured(args: InstallArgs) {
|
|
105
|
+
if (args.fix) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!resolveMinReleaseAge(args.minReleaseAge)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const version = runCommand(this.command, ['--version']).trim();
|
|
114
|
+
if (versionCompare(version, MIN_PNPM_VERSION_FOR_RELEASE_AGE) < 0) {
|
|
115
|
+
console.warn(
|
|
116
|
+
`WARNING: pnpm ${version} does not support minimum release age protection. Installing without it.`
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (this.hasConfiguredMinReleaseAge()) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.warn(
|
|
126
|
+
'WARNING: this project does not enforce a minimum release age for pnpm installs. Configure minimumReleaseAge in pnpm-workspace.yaml.'
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
* `pnpm config list --json` reports this key as `minimum-release-age` on
|
|
132
|
+
* pnpm <11 and `minimumReleaseAge` on pnpm >=11, so both are checked.
|
|
133
|
+
*/
|
|
134
|
+
private hasConfiguredMinReleaseAge(): boolean {
|
|
135
|
+
try {
|
|
136
|
+
const config = JSON.parse(runCommand(this.command, ['config', 'list', '--json']));
|
|
137
|
+
const configuredAge = config.minimumReleaseAge ?? config['minimum-release-age'];
|
|
138
|
+
return Number(configuredAge) > 0;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
97
143
|
}
|
|
98
144
|
|
|
99
145
|
function stripPeerDependencySuffix(version: string): string {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AuditedScript } from '../../utils';
|
|
2
|
+
import { versionCompare } from '../../utils';
|
|
3
|
+
|
|
4
|
+
export interface InstallScript {
|
|
5
|
+
name: string;
|
|
6
|
+
version: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TrustedScripts {
|
|
10
|
+
/** Trusted package name or <name>@<version> */
|
|
11
|
+
specs: string[];
|
|
12
|
+
/** Bare names for all trusted packages */
|
|
13
|
+
names: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function filterTrustedScripts(
|
|
17
|
+
installScripts: InstallScript[],
|
|
18
|
+
builtInAllowlist: AuditedScript[],
|
|
19
|
+
userAllowlist: string[]
|
|
20
|
+
): TrustedScripts {
|
|
21
|
+
const specs = new Set<string>();
|
|
22
|
+
const names = new Set<string>();
|
|
23
|
+
|
|
24
|
+
for (const { name, version } of installScripts) {
|
|
25
|
+
const allowlistEntry = builtInAllowlist.find(entry => entry.name === name);
|
|
26
|
+
if (!allowlistEntry) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (!allowlistEntry.maximumVersion) {
|
|
30
|
+
specs.add(name);
|
|
31
|
+
names.add(name);
|
|
32
|
+
} else if (versionCompare(version, allowlistEntry.maximumVersion) <= 0) {
|
|
33
|
+
specs.add(`${name}@${version}`);
|
|
34
|
+
names.add(name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const name of userAllowlist) {
|
|
39
|
+
specs.add(name);
|
|
40
|
+
names.add(name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { specs: [...specs], names: [...names] };
|
|
44
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
export * from './filter-trusted-scripts';
|
|
1
2
|
export * from './log-allowed-scripts';
|
|
2
3
|
export * from './parse-allow-scripts';
|
|
3
4
|
export * from './resolve-allow-scripts';
|
|
5
|
+
export * from './resolve-min-release-age';
|
|
4
6
|
export * from './uses-startup';
|
|
5
7
|
export * from './with-project-allow-scripts';
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { AllowScripts } from '../package-manager';
|
|
2
2
|
import { parseAllowScripts } from './parse-allow-scripts';
|
|
3
3
|
|
|
4
|
-
export const DEFAULT_ALLOWED_SCRIPTS = [
|
|
5
|
-
'@servicetitan/startup',
|
|
6
|
-
'cypress',
|
|
7
|
-
'msw',
|
|
8
|
-
'nx',
|
|
9
|
-
'tree-sitter',
|
|
10
|
-
];
|
|
11
|
-
|
|
12
4
|
export function resolveAllowScripts(value: AllowScripts, builtInAllowlist: string[]): AllowScripts {
|
|
13
5
|
if (value === true || value === false) {
|
|
14
6
|
return value;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const DEFAULT_MIN_RELEASE_AGE_DAYS = 3;
|
|
2
|
+
const DEFAULT_MIN_RELEASE_AGE_EXCLUDE = ['@servicetitan/*'];
|
|
3
|
+
|
|
4
|
+
export interface ResolvedMinReleaseAge {
|
|
5
|
+
days: number;
|
|
6
|
+
exclude: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveMinReleaseAge(
|
|
10
|
+
minReleaseAge: false | undefined
|
|
11
|
+
): ResolvedMinReleaseAge | false {
|
|
12
|
+
if (minReleaseAge === false) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return { days: DEFAULT_MIN_RELEASE_AGE_DAYS, exclude: DEFAULT_MIN_RELEASE_AGE_EXCLUDE };
|
|
16
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface AuditedScript {
|
|
2
|
+
name: string;
|
|
3
|
+
action: 'allow' | 'ignore';
|
|
4
|
+
maximumVersion?: string;
|
|
5
|
+
note?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const AUDITED_SCRIPTS: AuditedScript[] = [
|
|
9
|
+
// Trusted to run their install scripts, up to the audited version.
|
|
10
|
+
...[
|
|
11
|
+
{
|
|
12
|
+
name: '@servicetitan/startup',
|
|
13
|
+
note: 'Applies patch-package patches to installed dependencies',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'cypress',
|
|
17
|
+
maximumVersion: '16.0.0',
|
|
18
|
+
note: "Downloads Cypress's platform-specific test runner binary",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'msw',
|
|
22
|
+
maximumVersion: '2.15.0',
|
|
23
|
+
note: "Regenerates the mock service worker script into the project's configured workerDirectory, no-op unless that's set",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'nx',
|
|
27
|
+
maximumVersion: '23.2.1',
|
|
28
|
+
note: 'Verifies native platform support and updates the Nx Cloud client if the workspace uses Nx Cloud',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'tree-sitter',
|
|
32
|
+
maximumVersion: '0.25.1',
|
|
33
|
+
note: 'Installs a prebuilt native binary via node-gyp-build, compiling from source as a fallback',
|
|
34
|
+
},
|
|
35
|
+
].map((entry: Omit<AuditedScript, 'action'>) => ({ ...entry, action: 'allow' as const })),
|
|
36
|
+
|
|
37
|
+
// Have install scripts, but don't need them to function correctly.
|
|
38
|
+
...[
|
|
39
|
+
{
|
|
40
|
+
name: '@parcel/watcher',
|
|
41
|
+
note: 'Installs platform-specific binary via optionalDependencies, install script is a legacy fallback',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: '@progress/kendo-licensing',
|
|
45
|
+
note: 'Validates Kendo UI license key, not needed for functionality',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: '@scarf/scarf',
|
|
49
|
+
note: 'Sends anonymous analytics/telemetry to Scarf, usage-tracking mechanism with no functional purpose',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: '@swc/core',
|
|
53
|
+
note: 'Installs platform-specific binary via optionalDependencies, postinstall is a legacy fallback',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: '@tree-sitter-grammars/tree-sitter-yaml',
|
|
57
|
+
note: 'Ships prebuilds for all supported targets via node-gyp-build, install script is a source-compile fallback',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: '@vaadin/vaadin-usage-statistics',
|
|
61
|
+
note: "Toggles Vaadin's development-time telemetry, no effect on functionality",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'contentful',
|
|
65
|
+
note: 'In 9.3.x, printed a message announcing that v10 was available, no functional purpose; no install script at all since 9.4.0',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'core-js-pure',
|
|
69
|
+
note: 'Prints a donation/sponsorship banner to the console, no functional purpose',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'core-js',
|
|
73
|
+
note: 'Prints a donation/sponsorship banner to the console, no functional purpose',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'es5-ext',
|
|
77
|
+
note: 'Displays politically motivated message targeted at Russian developers, no effect on functionality',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'esbuild',
|
|
81
|
+
note: 'Installs platform-specific binary via optionalDependencies, postinstall is a legacy fallback',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'tree-sitter-json',
|
|
85
|
+
note: 'Ships prebuilds for all supported targets via node-gyp-build, install script is a source-compile fallback',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'unrs-resolver',
|
|
89
|
+
note: 'Installs platform-specific binary via optionalDependencies, postinstall is a legacy fallback',
|
|
90
|
+
},
|
|
91
|
+
].map((entry: Omit<AuditedScript, 'action'>) => ({ ...entry, action: 'ignore' as const })),
|
|
92
|
+
];
|
|
@@ -16,14 +16,10 @@ const SELECTORS = ['preinstall', 'install', 'postinstall']
|
|
|
16
16
|
.join(',');
|
|
17
17
|
|
|
18
18
|
export function getPackagesWithInstallScripts(): PackageWithScript[] {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}));
|
|
26
|
-
} catch {
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
19
|
+
const result = runCommand('npm', ['query', SELECTORS, '--json']);
|
|
20
|
+
return JSON.parse(result).map(({ location, name, version }: PackageWithScript) => ({
|
|
21
|
+
location,
|
|
22
|
+
name,
|
|
23
|
+
version,
|
|
24
|
+
}));
|
|
29
25
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './audited-scripts';
|
|
1
2
|
export * from './ci';
|
|
2
3
|
export * from './debug';
|
|
3
4
|
export * from './find-up';
|
|
@@ -8,4 +9,5 @@ export * from './git';
|
|
|
8
9
|
export * from './npm';
|
|
9
10
|
export * from './os';
|
|
10
11
|
export * from './read-json';
|
|
12
|
+
export * from './version-compare';
|
|
11
13
|
export * from './warn-untrusted-install-scripts';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function versionCompare(version: string, otherVersion: string): -1 | 0 | 1 {
|
|
2
|
+
const [versionCore, versionPrerelease] = splitPrerelease(version);
|
|
3
|
+
const [otherCore, otherPrerelease] = splitPrerelease(otherVersion);
|
|
4
|
+
|
|
5
|
+
const parts = parseVersion(versionCore);
|
|
6
|
+
const otherParts = parseVersion(otherCore);
|
|
7
|
+
|
|
8
|
+
const length = Math.max(parts.length, otherParts.length);
|
|
9
|
+
for (let i = 0; i < length; i++) {
|
|
10
|
+
const part = parts[i] ?? 0;
|
|
11
|
+
const otherPart = otherParts[i] ?? 0;
|
|
12
|
+
if (part !== otherPart) {
|
|
13
|
+
return part > otherPart ? 1 : -1;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Same core version: a pre-release build (e.g. "-canary.5") has lower
|
|
19
|
+
* precedence than the plain release it precedes.
|
|
20
|
+
*/
|
|
21
|
+
if (!!versionPrerelease === !!otherPrerelease) {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
return versionPrerelease ? -1 : 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function splitPrerelease(version: string): [core: string, prerelease: string | undefined] {
|
|
28
|
+
const [core, ...prerelease] = version.trim().split('-');
|
|
29
|
+
return [core, prerelease.length > 0 ? prerelease.join('-') : undefined];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseVersion(version: string): number[] {
|
|
33
|
+
return version.split('.').map(part => parseInt(part, 10) || 0);
|
|
34
|
+
}
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
/* eslint-disable no-console */
|
|
2
|
+
import type { AuditedScript } from './audited-scripts';
|
|
2
3
|
import { getPackagesWithInstallScripts } from './get-packages-with-install-scripts';
|
|
4
|
+
import { versionCompare } from './version-compare';
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
6
|
+
export function warnUntrustedInstallScripts(allowedScripts: string[], ignored: AuditedScript[]) {
|
|
7
|
+
let packages: ReturnType<typeof getPackagesWithInstallScripts>;
|
|
8
|
+
try {
|
|
9
|
+
packages = getPackagesWithInstallScripts();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.warn(`WARNING: Unable to check for untrusted install scripts: ${error}`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const untrusted = packages.filter(({ location, name, version }) => {
|
|
15
|
+
if (
|
|
16
|
+
!location ||
|
|
17
|
+
allowedScripts.includes(name) ||
|
|
18
|
+
allowedScripts.includes(`${name}@${version}`)
|
|
19
|
+
) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const ignoredEntry = ignored.find(entry => entry.name === name);
|
|
23
|
+
if (!ignoredEntry) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return (
|
|
27
|
+
!!ignoredEntry.maximumVersion &&
|
|
28
|
+
versionCompare(version, ignoredEntry.maximumVersion) > 0
|
|
29
|
+
);
|
|
28
30
|
});
|
|
29
31
|
|
|
30
32
|
if (untrusted.length === 0) {
|