@servicetitan/startup 32.0.0 → 32.1.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/commands/review/rules/index.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/index.js +10 -2
- package/dist/cli/commands/review/rules/index.js.map +1 -1
- package/dist/cli/commands/review/rules/no-deprecated-content-base.d.ts +13 -0
- package/dist/cli/commands/review/rules/no-deprecated-content-base.d.ts.map +1 -0
- package/dist/cli/commands/review/rules/no-deprecated-content-base.js +71 -0
- package/dist/cli/commands/review/rules/no-deprecated-content-base.js.map +1 -0
- package/dist/cli/commands/review/rules/no-direct-peer-dependencies.d.ts +15 -0
- package/dist/cli/commands/review/rules/no-direct-peer-dependencies.d.ts.map +1 -0
- package/dist/cli/commands/review/rules/no-direct-peer-dependencies.js +75 -0
- package/dist/cli/commands/review/rules/no-direct-peer-dependencies.js.map +1 -0
- package/dist/cli/commands/review/rules/no-typescript-entry-point.d.ts +2 -2
- package/dist/cli/commands/review/rules/no-typescript-entry-point.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/no-typescript-entry-point.js +58 -45
- package/dist/cli/commands/review/rules/no-typescript-entry-point.js.map +1 -1
- package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.d.ts +20 -0
- package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.d.ts.map +1 -0
- package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.js +145 -0
- package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.js.map +1 -0
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts +22 -0
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts.map +1 -0
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js +189 -0
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js.map +1 -0
- package/dist/cli/commands/review/rules/require-explicit-side-effects.d.ts +2 -1
- package/dist/cli/commands/review/rules/require-explicit-side-effects.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/require-explicit-side-effects.js +24 -11
- package/dist/cli/commands/review/rules/require-explicit-side-effects.js.map +1 -1
- package/dist/cli/commands/review/rules/require-one-collection-version.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/require-one-collection-version.js +1 -1
- package/dist/cli/commands/review/rules/require-one-collection-version.js.map +1 -1
- package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js +5 -7
- package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js.map +1 -1
- package/dist/cli/commands/review/rules/require-servicetitan-scope.d.ts +2 -1
- package/dist/cli/commands/review/rules/require-servicetitan-scope.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/require-servicetitan-scope.js +24 -11
- package/dist/cli/commands/review/rules/require-servicetitan-scope.js.map +1 -1
- package/dist/cli/commands/review/types.d.ts +4 -1
- package/dist/cli/commands/review/types.d.ts.map +1 -1
- package/dist/cli/commands/review/types.js.map +1 -1
- package/dist/cli/commands/review/utils/check-packages.d.ts +7 -0
- package/dist/cli/commands/review/utils/check-packages.d.ts.map +1 -0
- package/dist/cli/commands/review/utils/check-packages.js +30 -0
- package/dist/cli/commands/review/utils/check-packages.js.map +1 -0
- package/dist/cli/commands/review/utils/index.d.ts +1 -0
- package/dist/cli/commands/review/utils/index.d.ts.map +1 -1
- package/dist/cli/commands/review/utils/index.js +1 -0
- package/dist/cli/commands/review/utils/index.js.map +1 -1
- package/dist/cli/utils/cli-git.d.ts.map +1 -1
- package/dist/cli/utils/cli-git.js +11 -8
- package/dist/cli/utils/cli-git.js.map +1 -1
- package/dist/cli/utils/cli-os.d.ts.map +1 -1
- package/dist/cli/utils/cli-os.js +3 -0
- package/dist/cli/utils/cli-os.js.map +1 -1
- package/package.json +4 -4
- package/src/cli/commands/review/rules/__mocks__/mock-config.ts +8 -2
- package/src/cli/commands/review/rules/__tests__/no-deprecated-content-base.test.ts +123 -0
- package/src/cli/commands/review/rules/__tests__/no-direct-peer-dependencies.test.ts +130 -0
- package/src/cli/commands/review/rules/__tests__/prefer-open-ended-peer-dependencies.test.ts +121 -0
- package/src/cli/commands/review/rules/__tests__/require-compatible-launch-darkly-sdk.test.ts +256 -0
- package/src/cli/commands/review/rules/index.ts +10 -2
- package/src/cli/commands/review/rules/no-deprecated-content-base.ts +50 -0
- package/src/cli/commands/review/rules/no-direct-peer-dependencies.ts +58 -0
- package/src/cli/commands/review/rules/no-typescript-entry-point.ts +5 -8
- package/src/cli/commands/review/rules/prefer-open-ended-peer-dependencies.ts +89 -0
- package/src/cli/commands/review/rules/require-compatible-launch-darkly-sdk.ts +141 -0
- package/src/cli/commands/review/rules/require-explicit-side-effects.ts +14 -14
- package/src/cli/commands/review/rules/require-one-collection-version.ts +1 -3
- package/src/cli/commands/review/rules/require-project-version-in-root-node-modules.ts +5 -9
- package/src/cli/commands/review/rules/require-servicetitan-scope.ts +14 -14
- package/src/cli/commands/review/types.ts +16 -2
- package/src/cli/commands/review/utils/check-packages.ts +19 -0
- package/src/cli/commands/review/utils/index.ts +1 -0
- package/src/cli/utils/__tests__/cli-git.test.ts +20 -18
- package/src/cli/utils/cli-git.ts +8 -7
- package/src/cli/utils/cli-os.ts +4 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { FixCategory, Package, PackageError, Project, ReviewConfiguration } from '../../types';
|
|
4
|
+
import { setVersion } from '../../utils';
|
|
5
|
+
import { expectCalls } from '../../__mocks__';
|
|
6
|
+
import { mockConfig, mockPackages, mockProject } from '../__mocks__';
|
|
7
|
+
import { RequireCompatibleLaunchDarklySdk } from '../require-compatible-launch-darkly-sdk';
|
|
8
|
+
|
|
9
|
+
jest.mock('child_process', () => ({ execSync: jest.fn() }));
|
|
10
|
+
jest.mock('../../utils', () => ({
|
|
11
|
+
...jest.requireActual('../../utils'),
|
|
12
|
+
setVersion: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
const JS_CLIENT_SDK = 'launchdarkly-js-client-sdk';
|
|
16
|
+
const LD_SERVICE = '@servicetitan/launchdarkly-service';
|
|
17
|
+
|
|
18
|
+
describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
|
|
19
|
+
const id = 'require-compatible-launch-darkly-sdk';
|
|
20
|
+
const rule = new RequireCompatibleLaunchDarklySdk();
|
|
21
|
+
let config: ReviewConfiguration;
|
|
22
|
+
let dependencies: Project['dependencies'];
|
|
23
|
+
let packageLock: Project['packageLock'];
|
|
24
|
+
let packages: Package[];
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
config = {};
|
|
28
|
+
dependencies = {};
|
|
29
|
+
packages = [];
|
|
30
|
+
packageLock = { packages: {}, location: './package-lock.json' };
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const project = () => mockProject({ config, dependencies, packageLock, packages });
|
|
35
|
+
|
|
36
|
+
const subject = () => rule.run(project());
|
|
37
|
+
|
|
38
|
+
const fixSubject = () => {
|
|
39
|
+
const testProject = project();
|
|
40
|
+
const result = rule.run(testProject);
|
|
41
|
+
const error = Array.isArray(result) ? result[0] : result!;
|
|
42
|
+
rule.fix(error, testProject);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function itReturnsError(error: Partial<PackageError> | (() => Partial<PackageError>)) {
|
|
46
|
+
test('returns error', () => {
|
|
47
|
+
const expected = expect.objectContaining({
|
|
48
|
+
id,
|
|
49
|
+
message: `package "${packages[0]?.name}" depends on an incompatible version of ${JS_CLIENT_SDK}`,
|
|
50
|
+
location: packages[0]?.location,
|
|
51
|
+
fixable: FixCategory.isolated,
|
|
52
|
+
...(typeof error === 'function' ? error() : error),
|
|
53
|
+
});
|
|
54
|
+
const result = subject();
|
|
55
|
+
expect(result).toEqual(Array.isArray(result) ? [expected] : expected);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function itReturnsNothing() {
|
|
60
|
+
test('returns nothing', () => {
|
|
61
|
+
const result = subject();
|
|
62
|
+
expect(result === undefined || (Array.isArray(result) && result.length === 0)).toBe(
|
|
63
|
+
true
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
itReturnsNothing();
|
|
69
|
+
|
|
70
|
+
describe(`when package depends on different version of ${JS_CLIENT_SDK} from ${LD_SERVICE}`, () => {
|
|
71
|
+
const sdkVersions = { package: '^1.0.0', lockfile: '1.1.0', ldService: '1.0.1' };
|
|
72
|
+
const packageName = 'lib1';
|
|
73
|
+
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
dependencies = { [JS_CLIENT_SDK]: { [sdkVersions.package]: [packageName] } };
|
|
76
|
+
packages = mockPackages(dependencies);
|
|
77
|
+
packageLock.packages = {
|
|
78
|
+
[`node_modules/${JS_CLIENT_SDK}`]: {
|
|
79
|
+
version: sdkVersions.lockfile,
|
|
80
|
+
},
|
|
81
|
+
[`node_modules/${LD_SERVICE}`]: {
|
|
82
|
+
version: '1.0.0',
|
|
83
|
+
dependencies: { [JS_CLIENT_SDK]: sdkVersions.ldService },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
itReturnsError({
|
|
89
|
+
details: `${chalk.yellow(sdkVersions.lockfile)} does not match ${sdkVersions.ldService}`,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('fixes error', () => {
|
|
93
|
+
fixSubject();
|
|
94
|
+
|
|
95
|
+
expect(setVersion).toHaveBeenCalledWith({
|
|
96
|
+
project: project(),
|
|
97
|
+
packageName: packages[0].name,
|
|
98
|
+
dependency: JS_CLIENT_SDK,
|
|
99
|
+
version: sdkVersions.ldService,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('fix ignores invalid error', () => {
|
|
104
|
+
rule.fix({} as any, project());
|
|
105
|
+
|
|
106
|
+
expect(setVersion).not.toHaveBeenCalled();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('when config excludes package', () => {
|
|
110
|
+
beforeEach(() => (config = mockConfig({ id, exclude: packages[0].name })));
|
|
111
|
+
|
|
112
|
+
itReturnsNothing();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
function mergePackageLock(
|
|
116
|
+
key: string,
|
|
117
|
+
data: Partial<(typeof packageLock)['packages'][string]>
|
|
118
|
+
) {
|
|
119
|
+
packageLock.packages[key] = { ...packageLock.packages[key], ...data };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe(`when ${JS_CLIENT_SDK} is private package dependency`, () => {
|
|
123
|
+
const privateLockfileVersion = '1.2.0';
|
|
124
|
+
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
mergePackageLock(`node_modules/${packageName}/node_modules/${JS_CLIENT_SDK}`, {
|
|
127
|
+
version: privateLockfileVersion,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
itReturnsError({
|
|
132
|
+
details: `${chalk.yellow(privateLockfileVersion)} does not match ${sdkVersions.ldService}`,
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe(`when ${JS_CLIENT_SDK} is peer dependency of ${LD_SERVICE}`, () => {
|
|
137
|
+
const peerLDServiceVersion = '1.0.2';
|
|
138
|
+
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
mergePackageLock(`node_modules/${LD_SERVICE}`, {
|
|
141
|
+
peerDependencies: { [JS_CLIENT_SDK]: peerLDServiceVersion },
|
|
142
|
+
dependencies: undefined,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
itReturnsError({
|
|
147
|
+
details: `${chalk.yellow(sdkVersions.lockfile)} does not match ${peerLDServiceVersion}`,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe(`when ${LD_SERVICE} dependency is a compatible range`, () => {
|
|
152
|
+
const compatibleRange = `^${sdkVersions.ldService}`;
|
|
153
|
+
|
|
154
|
+
beforeEach(() => {
|
|
155
|
+
mergePackageLock(`node_modules/${LD_SERVICE}`, {
|
|
156
|
+
dependencies: { [JS_CLIENT_SDK]: compatibleRange },
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
itReturnsNothing();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe(`when dependency is an incompatible range`, () => {
|
|
164
|
+
const incompatibleRange = `<${sdkVersions.ldService}`;
|
|
165
|
+
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
mergePackageLock(`node_modules/${LD_SERVICE}`, {
|
|
168
|
+
dependencies: { [JS_CLIENT_SDK]: incompatibleRange },
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
itReturnsError({
|
|
173
|
+
details: `${chalk.yellow(sdkVersions.lockfile)} does not match ${incompatibleRange}`,
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('when multiple packages depend on incompatible version', () => {
|
|
178
|
+
const otherPackageName = 'lib2';
|
|
179
|
+
|
|
180
|
+
beforeEach(() => {
|
|
181
|
+
dependencies[JS_CLIENT_SDK][sdkVersions.package].push(otherPackageName);
|
|
182
|
+
packages = mockPackages(dependencies);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('returns multiple errors', () => {
|
|
186
|
+
expect(subject()).toEqual([
|
|
187
|
+
expect.objectContaining({
|
|
188
|
+
message: `package "${packages[0].name}" depends on an incompatible version of ${JS_CLIENT_SDK}`,
|
|
189
|
+
location: packages[0].location,
|
|
190
|
+
}),
|
|
191
|
+
expect.objectContaining({
|
|
192
|
+
message: `package "${packages[1].name}" depends on an incompatible version of ${JS_CLIENT_SDK}`,
|
|
193
|
+
location: packages[1].location,
|
|
194
|
+
}),
|
|
195
|
+
]);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe(`when ${JS_CLIENT_SDK} is not in lockfile`, () => {
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
delete packageLock.packages[`node_modules/${JS_CLIENT_SDK}`];
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
itReturnsNothing();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe(`when ${LD_SERVICE} is not in lockfile`, () => {
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
delete packageLock.packages[`node_modules/${LD_SERVICE}`];
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
itReturnsNothing();
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe(`when project depends on different version of ${JS_CLIENT_SDK} from ${LD_SERVICE}`, () => {
|
|
217
|
+
const sdkVersions = { lockfile: '1.1.0', ldService: '1.0.1' };
|
|
218
|
+
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
packageLock.packages = {
|
|
221
|
+
[`node_modules/${JS_CLIENT_SDK}`]: {
|
|
222
|
+
version: sdkVersions.lockfile,
|
|
223
|
+
},
|
|
224
|
+
[`node_modules/${LD_SERVICE}`]: {
|
|
225
|
+
version: '1.0.0',
|
|
226
|
+
dependencies: { [JS_CLIENT_SDK]: sdkVersions.ldService },
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
itReturnsError(() => ({
|
|
232
|
+
message: `project depends on an incompatible version of ${JS_CLIENT_SDK}`,
|
|
233
|
+
details: `${chalk.yellow(sdkVersions.lockfile)} does not match ${sdkVersions.ldService}`,
|
|
234
|
+
location: packageLock.location,
|
|
235
|
+
fixable: FixCategory.lockFile,
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
test('fixes error', () => {
|
|
239
|
+
fixSubject();
|
|
240
|
+
|
|
241
|
+
expectCalls(
|
|
242
|
+
...[
|
|
243
|
+
`npm pkg set dependencies["${JS_CLIENT_SDK}"]=${sdkVersions.ldService}`,
|
|
244
|
+
'npx startup install --fix --quiet',
|
|
245
|
+
`npm pkg delete dependencies["${JS_CLIENT_SDK}"]`,
|
|
246
|
+
].map(command => [execSync, command, { stdio: 'inherit' }])
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('fix ignores invalid error', () => {
|
|
251
|
+
rule.fix({} as any, project());
|
|
252
|
+
|
|
253
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { PackageRule } from '../types';
|
|
2
|
+
import { NoDeprecatedContentBase } from './no-deprecated-content-base';
|
|
3
|
+
import { NoDirectPeerDependencies } from './no-direct-peer-dependencies';
|
|
2
4
|
import { NoTypescriptEntryPoint } from './no-typescript-entry-point';
|
|
5
|
+
import { PreferOpenEndedPeerDependencies } from './prefer-open-ended-peer-dependencies';
|
|
3
6
|
import { RequireAllReactDependencies } from './require-all-react-dependencies';
|
|
7
|
+
import { RequireCompatibleLaunchDarklySdk } from './require-compatible-launch-darkly-sdk';
|
|
4
8
|
import { RequireExplicitSideEffects } from './require-explicit-side-effects';
|
|
5
9
|
import { RequireNpmrc } from './require-npmrc';
|
|
6
10
|
import { RequireOneAnvilUikitContribVersion } from './require-one-anvil-uikit-contrib-version';
|
|
@@ -11,13 +15,17 @@ import { RequireProjectVersionInRootNodeModules } from './require-project-versio
|
|
|
11
15
|
import { RequireServiceTitanScope } from './require-servicetitan-scope';
|
|
12
16
|
|
|
13
17
|
export const rules: PackageRule[] = [
|
|
14
|
-
new RequireOnePackageVersion(),
|
|
15
18
|
new RequireAllReactDependencies(),
|
|
16
|
-
new RequireOneUikitVersion(),
|
|
17
19
|
new RequireOneAnvilUikitContribVersion(),
|
|
20
|
+
new RequireOnePackageVersion(),
|
|
18
21
|
new RequireOneReactVersion(),
|
|
22
|
+
new RequireOneUikitVersion(),
|
|
19
23
|
new RequireProjectVersionInRootNodeModules(),
|
|
24
|
+
new RequireCompatibleLaunchDarklySdk(),
|
|
25
|
+
new NoDeprecatedContentBase(),
|
|
26
|
+
new NoDirectPeerDependencies(),
|
|
20
27
|
new NoTypescriptEntryPoint(),
|
|
28
|
+
new PreferOpenEndedPeerDependencies(),
|
|
21
29
|
new RequireServiceTitanScope(),
|
|
22
30
|
new RequireExplicitSideEffects(),
|
|
23
31
|
new RequireNpmrc(),
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { ErrorSeverity, FixCategory, Package, PackageError, PackageRule, Project } from '../types';
|
|
3
|
+
import { checkPackages } from '../utils';
|
|
4
|
+
|
|
5
|
+
interface ErrorData {
|
|
6
|
+
contentBase: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type RuleError = PackageError<ErrorData>;
|
|
10
|
+
|
|
11
|
+
export class NoDeprecatedContentBase implements PackageRule {
|
|
12
|
+
get id() {
|
|
13
|
+
return 'no-deprecated-content-base';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
run(project: Project): RuleError[] | undefined {
|
|
17
|
+
return checkPackages(this, project, this.checkPackage);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fix({ data, location }: RuleError) {
|
|
21
|
+
const { contentBase } = data ?? {};
|
|
22
|
+
if (!location || contentBase === undefined) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const setCommands = Array.isArray(contentBase)
|
|
27
|
+
? contentBase.map((directory, index) => {
|
|
28
|
+
return `npm pkg set cli.webpack.static[${index}].directory="${directory}" -w ${location}`;
|
|
29
|
+
})
|
|
30
|
+
: [`npm pkg set cli.webpack.static.directory="${contentBase}" -w ${location}`];
|
|
31
|
+
|
|
32
|
+
[...setCommands, `npm pkg delete cli.webpack.contentBase -w ${location}`].forEach(command =>
|
|
33
|
+
execSync(command, { stdio: 'inherit' })
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private readonly checkPackage = (pkg: Package): RuleError | undefined => {
|
|
38
|
+
const { webpack = {} } = pkg.cli ?? {};
|
|
39
|
+
if (webpack && typeof webpack === 'object' && Object.hasOwn(webpack, 'contentBase')) {
|
|
40
|
+
return {
|
|
41
|
+
id: this.id,
|
|
42
|
+
message: `package "${pkg.name}" uses deprecated cli.webpack.contentBase configuration`,
|
|
43
|
+
location: pkg.location,
|
|
44
|
+
fixable: FixCategory.isolated,
|
|
45
|
+
severity: ErrorSeverity.warning,
|
|
46
|
+
data: { contentBase: webpack.contentBase },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { FixCategory, Package, PackageError, PackageRule, Project } from '../types';
|
|
3
|
+
import { checkPackages, formatList } from '../utils';
|
|
4
|
+
|
|
5
|
+
interface ErrorData {
|
|
6
|
+
dependencies: Record<string, string>;
|
|
7
|
+
directPeerDependencies: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type RuleError = PackageError<ErrorData>;
|
|
11
|
+
|
|
12
|
+
export class NoDirectPeerDependencies implements PackageRule {
|
|
13
|
+
get id() {
|
|
14
|
+
return 'no-direct-peer-dependencies';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
run(project: Project): RuleError[] | undefined {
|
|
18
|
+
return checkPackages(this, project, this.checkPackage);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fix({ data, location }: RuleError) {
|
|
22
|
+
const { dependencies, directPeerDependencies } = data ?? {};
|
|
23
|
+
if (!(location && dependencies && directPeerDependencies)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
directPeerDependencies.forEach(name => {
|
|
28
|
+
[
|
|
29
|
+
`npm pkg set devDependencies["${name}"]="${dependencies[name]}" -w ${location}`,
|
|
30
|
+
`npm pkg delete dependencies["${name}"] -w ${location}`,
|
|
31
|
+
].forEach(command => execSync(command, { stdio: 'inherit' }));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private readonly checkPackage = (pkg: Package): RuleError | undefined => {
|
|
36
|
+
const { dependencies = {}, peerDependencies = {} } = pkg;
|
|
37
|
+
const directPeerDependencies = Object.keys(peerDependencies).filter(
|
|
38
|
+
peerDependency => !!dependencies[peerDependency]
|
|
39
|
+
);
|
|
40
|
+
if (directPeerDependencies.length) {
|
|
41
|
+
return {
|
|
42
|
+
id: this.id,
|
|
43
|
+
message: this.formatMessage(pkg.name, directPeerDependencies),
|
|
44
|
+
location: pkg.location,
|
|
45
|
+
fixable: FixCategory.isolated,
|
|
46
|
+
data: { dependencies, directPeerDependencies },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
private formatMessage(name: string, dependencies: string[]) {
|
|
52
|
+
if (dependencies.length === 1) {
|
|
53
|
+
return `package "${name}" lists "${dependencies[0]}" as both a direct and a peer dependency`;
|
|
54
|
+
}
|
|
55
|
+
const list = formatList(dependencies.map(name => `"${name}"`));
|
|
56
|
+
return `package "${name}" lists ${list} as both direct and peer dependencies`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -2,7 +2,7 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { getFolders, log } from '../../../../utils';
|
|
4
4
|
import { FixCategory, Package, PackageError, PackageRule, Project } from '../types';
|
|
5
|
-
import {
|
|
5
|
+
import { checkPackages } from '../utils';
|
|
6
6
|
|
|
7
7
|
interface ErrorData {
|
|
8
8
|
key: string;
|
|
@@ -14,11 +14,8 @@ export class NoTypescriptEntryPoint implements PackageRule {
|
|
|
14
14
|
return 'no-typescript-entry-point';
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
run(
|
|
18
|
-
|
|
19
|
-
return applyFilter(ruleConfig, packages, ({ name }) => name).flatMap(pkg =>
|
|
20
|
-
this.checkEntryPoints(pkg)
|
|
21
|
-
);
|
|
17
|
+
run(project: Project): PackageError<ErrorData>[] {
|
|
18
|
+
return checkPackages(this, project, this.checkEntryPoints);
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
fix({ data, location }: PackageError<ErrorData>) {
|
|
@@ -35,7 +32,7 @@ export class NoTypescriptEntryPoint implements PackageRule {
|
|
|
35
32
|
execSync(command);
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
private checkEntryPoints(pkg: Package) {
|
|
35
|
+
private readonly checkEntryPoints = (pkg: Package) => {
|
|
39
36
|
const errors: PackageError<ErrorData>[] = [];
|
|
40
37
|
const addError = (message: string, data: ErrorData) =>
|
|
41
38
|
errors.push({
|
|
@@ -70,7 +67,7 @@ export class NoTypescriptEntryPoint implements PackageRule {
|
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
return errors;
|
|
73
|
-
}
|
|
70
|
+
};
|
|
74
71
|
|
|
75
72
|
private isTypescriptEntryPoint(str: string) {
|
|
76
73
|
return str.endsWith('.ts') && !str.endsWith('.d.ts');
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { satisfies } from 'semver';
|
|
3
|
+
import { ErrorSeverity, FixCategory, Package, PackageError, PackageRule, Project } from '../types';
|
|
4
|
+
import { checkPackages } from '../utils';
|
|
5
|
+
|
|
6
|
+
interface ErrorData {
|
|
7
|
+
dependency: string;
|
|
8
|
+
version: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type RuleError = PackageError<ErrorData>;
|
|
12
|
+
|
|
13
|
+
export class PreferOpenEndedPeerDependencies implements PackageRule {
|
|
14
|
+
#project!: Project;
|
|
15
|
+
|
|
16
|
+
get id() {
|
|
17
|
+
return 'prefer-open-ended-peer-dependencies';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
run(project: Project): RuleError[] | undefined {
|
|
21
|
+
this.#project = project;
|
|
22
|
+
return checkPackages(this, project, this.checkPackage);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fix({ data, location }: RuleError) {
|
|
26
|
+
const { dependency, version } = data ?? {};
|
|
27
|
+
if (!(location && dependency && version)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const openEndedVersion = version.replace(/^[\^~]/, '>=');
|
|
32
|
+
const command = `npm pkg set peerDependencies["${dependency}"]="${openEndedVersion}" -w ${location}`;
|
|
33
|
+
execSync(command, { stdio: 'inherit' });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private readonly checkPackage = (pkg: Package): RuleError[] | undefined => {
|
|
37
|
+
return Object.entries(pkg.peerDependencies ?? {}).reduce<RuleError[]>(
|
|
38
|
+
(result, [dependency, version]) => {
|
|
39
|
+
if (this.isViolation(pkg, { dependency, version })) {
|
|
40
|
+
result.push({
|
|
41
|
+
id: this.id,
|
|
42
|
+
message: `package "${pkg.name}" has closed-ended peer dependency on "${dependency}@${version}"`,
|
|
43
|
+
location: pkg.location,
|
|
44
|
+
severity: ErrorSeverity.warning,
|
|
45
|
+
...(this.isFixable(version)
|
|
46
|
+
? { fixable: FixCategory.isolated, data: { dependency, version } }
|
|
47
|
+
: {}),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
},
|
|
52
|
+
[]
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
private isExactVersion(version: string) {
|
|
57
|
+
return /^\d/.test(version);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private isExcluded({ name }: Package, dependency: string) {
|
|
61
|
+
const ruleConfig = this.#project.config.rules?.[this.id];
|
|
62
|
+
if (Array.isArray(ruleConfig)) {
|
|
63
|
+
const { exclude } = ruleConfig[1] ?? {};
|
|
64
|
+
if (typeof exclude === 'object' && !Array.isArray(exclude)) {
|
|
65
|
+
return exclude[name]?.includes(dependency);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private isOpenEndedVersion(version: string) {
|
|
72
|
+
return satisfies('999.999.999', version);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private isFixable(version: string) {
|
|
76
|
+
return /^[\^~]\d+(\.\d+){0,2}$/.test(version);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private isViolation(
|
|
80
|
+
pkg: Package,
|
|
81
|
+
{ dependency, version }: { dependency: string; version: string }
|
|
82
|
+
) {
|
|
83
|
+
return (
|
|
84
|
+
!this.isExactVersion(version) &&
|
|
85
|
+
!this.isOpenEndedVersion(version) &&
|
|
86
|
+
!this.isExcluded(pkg, dependency)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import { satisfies } from 'semver';
|
|
4
|
+
import { FixCategory, Package, PackageError, PackageRule, Project } from '../types';
|
|
5
|
+
import { applyFilter, checkPackages, setVersion } from '../utils';
|
|
6
|
+
|
|
7
|
+
const JS_CLIENT_SDK = 'launchdarkly-js-client-sdk';
|
|
8
|
+
const LD_SERVICE = '@servicetitan/launchdarkly-service';
|
|
9
|
+
|
|
10
|
+
interface ErrorData {
|
|
11
|
+
name?: string;
|
|
12
|
+
actualVersion: string;
|
|
13
|
+
targetVersion: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type RuleError = PackageError<ErrorData>;
|
|
17
|
+
|
|
18
|
+
export class RequireCompatibleLaunchDarklySdk implements PackageRule {
|
|
19
|
+
#project!: Project;
|
|
20
|
+
|
|
21
|
+
get id() {
|
|
22
|
+
return 'require-compatible-launch-darkly-sdk';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
run(project: Project): RuleError | RuleError[] | undefined {
|
|
26
|
+
this.#project = project;
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Pass an empty filter object to checkPackages to disable its filtering.
|
|
30
|
+
* The project-level check is only useful when there are no package-level
|
|
31
|
+
* errors so we need an accurate count.
|
|
32
|
+
*/
|
|
33
|
+
const errors = checkPackages({}, project, this.checkPackage);
|
|
34
|
+
|
|
35
|
+
if (!errors.length) {
|
|
36
|
+
return this.checkProject();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return applyFilter(project.config.rules?.[this.id], errors, ({ data }) => data!.name!);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fix({ data }: RuleError, project: Project) {
|
|
43
|
+
const { name, targetVersion } = data ?? {};
|
|
44
|
+
if (!targetVersion) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (name) {
|
|
48
|
+
setVersion({
|
|
49
|
+
project,
|
|
50
|
+
packageName: name,
|
|
51
|
+
dependency: JS_CLIENT_SDK,
|
|
52
|
+
version: targetVersion,
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[
|
|
58
|
+
`npm pkg set dependencies["${JS_CLIENT_SDK}"]=${targetVersion}`,
|
|
59
|
+
'npx startup install --fix --quiet',
|
|
60
|
+
`npm pkg delete dependencies["${JS_CLIENT_SDK}"]`,
|
|
61
|
+
].forEach(command => execSync(command, { stdio: 'inherit' }));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private readonly checkPackage = ({
|
|
65
|
+
dependencies = {},
|
|
66
|
+
location,
|
|
67
|
+
name,
|
|
68
|
+
}: Package): RuleError | undefined => {
|
|
69
|
+
if (!dependencies[JS_CLIENT_SDK]) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const actualVersion = this.getActualJsClientSdkVersion(name);
|
|
74
|
+
const targetVersion = this.getTargetJsClientSdkVersion(name);
|
|
75
|
+
if (actualVersion && targetVersion && !satisfies(actualVersion, targetVersion)) {
|
|
76
|
+
return this.createError({
|
|
77
|
+
message: `package "${name}" depends on an incompatible version of ${JS_CLIENT_SDK}`,
|
|
78
|
+
location,
|
|
79
|
+
data: { name, actualVersion, targetVersion },
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
private checkProject() {
|
|
85
|
+
const actualVersion = this.getActualJsClientSdkVersion();
|
|
86
|
+
const targetVersion = this.getTargetJsClientSdkVersion();
|
|
87
|
+
if (actualVersion && targetVersion && !satisfies(actualVersion, targetVersion)) {
|
|
88
|
+
return this.createError({
|
|
89
|
+
message: `project depends on an incompatible version of ${JS_CLIENT_SDK}`,
|
|
90
|
+
location: this.#project.packageLock.location,
|
|
91
|
+
data: { actualVersion, targetVersion },
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private createError(params: { message: string; location: string; data: ErrorData }): RuleError {
|
|
97
|
+
const { message, data, location } = params;
|
|
98
|
+
return {
|
|
99
|
+
id: this.id,
|
|
100
|
+
message,
|
|
101
|
+
details: this.formatDetails(data),
|
|
102
|
+
location,
|
|
103
|
+
fixable: data.name ? FixCategory.isolated : FixCategory.lockFile,
|
|
104
|
+
data,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private formatDetails({ actualVersion, targetVersion }: ErrorData) {
|
|
109
|
+
return `${chalk.yellow(actualVersion)} does not match ${targetVersion}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private getActualJsClientSdkVersion(packageName?: string) {
|
|
113
|
+
return this.getResolvedDependency({ packageName, dependency: JS_CLIENT_SDK }).version;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private getResolvedDependency({
|
|
117
|
+
packageName,
|
|
118
|
+
dependency,
|
|
119
|
+
}: {
|
|
120
|
+
packageName?: string;
|
|
121
|
+
dependency: string;
|
|
122
|
+
}) {
|
|
123
|
+
const { packages } = this.#project.packageLock;
|
|
124
|
+
return (
|
|
125
|
+
(packageName
|
|
126
|
+
? packages[`node_modules/${packageName}/node_modules/${dependency}`]
|
|
127
|
+
: undefined) ??
|
|
128
|
+
packages[`node_modules/${dependency}`] ??
|
|
129
|
+
{}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private getTargetJsClientSdkVersion(packageName?: string) {
|
|
134
|
+
const { dependencies = {}, peerDependencies = {} } = this.getResolvedDependency({
|
|
135
|
+
packageName,
|
|
136
|
+
dependency: LD_SERVICE,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return dependencies[JS_CLIENT_SDK] ?? peerDependencies[JS_CLIENT_SDK];
|
|
140
|
+
}
|
|
141
|
+
}
|