@servicetitan/startup 36.1.1 → 36.1.2-canary.2

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.
Files changed (134) hide show
  1. package/bin/_run.js +77 -0
  2. package/bin/cpx.js +3 -0
  3. package/bin/eslint.js +3 -0
  4. package/bin/jest.js +3 -0
  5. package/bin/js-yaml.js +3 -0
  6. package/bin/json5.js +3 -0
  7. package/bin/kendo-ui-license.js +3 -0
  8. package/bin/lerna.js +3 -0
  9. package/bin/lessc.js +3 -0
  10. package/bin/patch-package.js +3 -0
  11. package/bin/prettier.js +3 -0
  12. package/bin/sass.js +3 -0
  13. package/bin/semver.js +3 -0
  14. package/bin/spack.js +3 -0
  15. package/bin/stylelint.js +3 -0
  16. package/bin/swc.js +3 -0
  17. package/bin/swcx.js +3 -0
  18. package/bin/tcm.js +3 -0
  19. package/bin/ts-jest.js +3 -0
  20. package/bin/ts-node-cwd.js +3 -0
  21. package/bin/ts-node-esm.js +3 -0
  22. package/bin/ts-node-script.js +3 -0
  23. package/bin/ts-node-transpile-only.js +3 -0
  24. package/bin/ts-node.js +3 -0
  25. package/bin/ts-script.js +3 -0
  26. package/bin/tsc.js +3 -0
  27. package/bin/tsserver.js +3 -0
  28. package/bin/vitest.js +3 -0
  29. package/bin/webpack-bundle-analyzer.js +3 -0
  30. package/bin/webpack-dev-server.js +3 -0
  31. package/bin/webpack.js +3 -0
  32. package/dist/cli/commands/clean.d.ts +1 -0
  33. package/dist/cli/commands/clean.d.ts.map +1 -1
  34. package/dist/cli/commands/clean.js +18 -8
  35. package/dist/cli/commands/clean.js.map +1 -1
  36. package/dist/cli/commands/coverage-finalize.d.ts +16 -0
  37. package/dist/cli/commands/coverage-finalize.d.ts.map +1 -0
  38. package/dist/cli/commands/coverage-finalize.js +41 -0
  39. package/dist/cli/commands/coverage-finalize.js.map +1 -0
  40. package/dist/cli/commands/install.js +1 -1
  41. package/dist/cli/commands/install.js.map +1 -1
  42. package/dist/cli/commands/kendo-ui-license.js +1 -2
  43. package/dist/cli/commands/kendo-ui-license.js.map +1 -1
  44. package/dist/cli/commands/prepare-package.js +2 -2
  45. package/dist/cli/commands/prepare-package.js.map +1 -1
  46. package/dist/cli/commands/registry/coverage-finalize.d.ts +5 -0
  47. package/dist/cli/commands/registry/coverage-finalize.d.ts.map +1 -0
  48. package/dist/cli/commands/registry/coverage-finalize.js +17 -0
  49. package/dist/cli/commands/registry/coverage-finalize.js.map +1 -0
  50. package/dist/cli/commands/review/review.d.ts.map +1 -1
  51. package/dist/cli/commands/review/review.js +8 -7
  52. package/dist/cli/commands/review/review.js.map +1 -1
  53. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts +0 -1
  54. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts.map +1 -1
  55. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js +12 -18
  56. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js.map +1 -1
  57. package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.d.ts.map +1 -1
  58. package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js +9 -4
  59. package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js.map +1 -1
  60. package/dist/cli/commands/review/types.d.ts +2 -8
  61. package/dist/cli/commands/review/types.d.ts.map +1 -1
  62. package/dist/cli/commands/review/types.js.map +1 -1
  63. package/dist/cli/utils/index.d.ts +0 -3
  64. package/dist/cli/utils/index.d.ts.map +1 -1
  65. package/dist/cli/utils/index.js +0 -3
  66. package/dist/cli/utils/index.js.map +1 -1
  67. package/dist/cypress/config/webpack-config.js +0 -1
  68. package/dist/cypress/config/webpack-config.js.map +1 -1
  69. package/dist/storybook-config/webpack-final.d.ts.map +1 -1
  70. package/dist/storybook-config/webpack-final.js +0 -6
  71. package/dist/storybook-config/webpack-final.js.map +1 -1
  72. package/dist/utils/default-excludes.d.ts +14 -0
  73. package/dist/utils/default-excludes.d.ts.map +1 -0
  74. package/dist/utils/default-excludes.js +23 -0
  75. package/dist/utils/default-excludes.js.map +1 -0
  76. package/dist/utils/find-packages.d.ts.map +1 -1
  77. package/dist/utils/find-packages.js +15 -0
  78. package/dist/utils/find-packages.js.map +1 -1
  79. package/dist/utils/get-coverage-aliases.d.ts +23 -0
  80. package/dist/utils/get-coverage-aliases.d.ts.map +1 -0
  81. package/dist/utils/get-coverage-aliases.js +41 -0
  82. package/dist/utils/get-coverage-aliases.js.map +1 -0
  83. package/dist/utils/get-coverage-source-patterns.d.ts +21 -0
  84. package/dist/utils/get-coverage-source-patterns.d.ts.map +1 -0
  85. package/dist/utils/get-coverage-source-patterns.js +39 -0
  86. package/dist/utils/get-coverage-source-patterns.js.map +1 -0
  87. package/dist/utils/get-jest-config.d.ts.map +1 -1
  88. package/dist/utils/get-jest-config.js +2 -1
  89. package/dist/utils/get-jest-config.js.map +1 -1
  90. package/dist/utils/prettify.js +1 -1
  91. package/dist/utils/prettify.js.map +1 -1
  92. package/dist/webpack/configs/cache-config.d.ts.map +1 -1
  93. package/dist/webpack/configs/cache-config.js +3 -1
  94. package/dist/webpack/configs/cache-config.js.map +1 -1
  95. package/dist/webpack/configs/rules/ts-coverage-rules.d.ts +22 -0
  96. package/dist/webpack/configs/rules/ts-coverage-rules.d.ts.map +1 -0
  97. package/dist/webpack/configs/rules/ts-coverage-rules.js +48 -0
  98. package/dist/webpack/configs/rules/ts-coverage-rules.js.map +1 -0
  99. package/eslint/config.mjs +2 -0
  100. package/package.json +54 -18
  101. package/src/__tests__/postinstall.test.ts +173 -0
  102. package/src/cli/commands/__tests__/clean.test.ts +30 -12
  103. package/src/cli/commands/__tests__/install.test.ts +3 -1
  104. package/src/cli/commands/__tests__/kendo-ui-license.test.ts +4 -4
  105. package/src/cli/commands/__tests__/prepare-package.test.ts +2 -3
  106. package/src/cli/commands/clean.ts +18 -9
  107. package/src/cli/commands/install.ts +1 -1
  108. package/src/cli/commands/kendo-ui-license.ts +1 -1
  109. package/src/cli/commands/prepare-package.ts +1 -1
  110. package/src/cli/commands/review/__mocks__/index.ts +1 -0
  111. package/src/cli/commands/review/__mocks__/mock-package-manager.ts +20 -0
  112. package/src/cli/commands/review/__tests__/review.test.ts +23 -31
  113. package/src/cli/commands/review/review.ts +11 -7
  114. package/src/cli/commands/review/rules/__mocks__/mock-project.ts +2 -1
  115. package/src/cli/commands/review/rules/__tests__/require-compatible-launch-darkly-sdk.test.ts +39 -49
  116. package/src/cli/commands/review/rules/__tests__/require-project-version-in-root-node-modules.test.ts +35 -22
  117. package/src/cli/commands/review/rules/require-compatible-launch-darkly-sdk.ts +13 -28
  118. package/src/cli/commands/review/rules/require-project-version-in-root-node-modules.ts +10 -3
  119. package/src/cli/commands/review/types.ts +3 -12
  120. package/src/cli/utils/index.ts +10 -3
  121. package/src/cypress/config/__tests__/webpack-config.test.ts +0 -9
  122. package/src/cypress/config/webpack-config.ts +0 -1
  123. package/src/postinstall.js +106 -14
  124. package/src/scripts/__tests__/generate-bin-wrappers.test.ts +226 -0
  125. package/src/scripts/generate-bin-wrappers.js +205 -0
  126. package/src/storybook-config/__tests__/webpack-final.test.ts +0 -25
  127. package/src/storybook-config/webpack-final.ts +0 -4
  128. package/src/utils/__tests__/get-jest-config.test.ts +3 -1
  129. package/src/utils/__tests__/get-packages.test.ts +47 -2
  130. package/src/utils/__tests__/prettify.test.ts +1 -1
  131. package/src/utils/find-packages.ts +16 -0
  132. package/src/utils/get-jest-config.ts +4 -1
  133. package/src/utils/prettify.ts +1 -1
  134. package/src/webpack/configs/cache-config.ts +3 -1
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { execSync } from 'child_process';
3
- import { expectCalls } from '../../__mocks__';
3
+ import { expectCalls, MockPackageManager } from '../../__mocks__';
4
4
  import { FixCategory, Package, PackageError, Project, ReviewConfiguration } from '../../types';
5
5
  import { setVersion } from '../../utils';
6
6
  import { mockConfig, mockPackages, mockProject } from '../__mocks__';
@@ -15,23 +15,45 @@ jest.mock('../../utils', () => ({
15
15
  const JS_CLIENT_SDK = 'launchdarkly-js-client-sdk';
16
16
  const LD_SERVICE = '@servicetitan/launchdarkly-service';
17
17
 
18
+ interface LockFile {
19
+ packages: Record<string, { version?: string; dependencies?: Record<string, string> }>;
20
+ location: string;
21
+ }
22
+
18
23
  describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
19
24
  const id = 'require-compatible-launch-darkly-sdk';
20
25
  const rule = new RequireCompatibleLaunchDarklySdk();
26
+ const mockPackageManager = new MockPackageManager();
21
27
  let config: ReviewConfiguration;
22
28
  let dependencies: Project['dependencies'];
23
- let packageLock: Project['packageLock'];
29
+ let lockFile: LockFile;
24
30
  let packages: Package[];
25
31
 
32
+ beforeAll(() => {
33
+ jest.spyOn(mockPackageManager, 'getResolvedVersion').mockImplementation(
34
+ ({ dependency }) => lockFile.packages[dependency]?.version
35
+ );
36
+ jest.spyOn(mockPackageManager, 'getDeclaredVersion').mockImplementation(
37
+ ({ declaredBy, dependency }) =>
38
+ lockFile.packages[declaredBy]?.dependencies?.[dependency]
39
+ );
40
+ });
41
+
26
42
  beforeEach(() => {
27
43
  config = {};
28
44
  dependencies = {};
29
45
  packages = [];
30
- packageLock = { packages: {}, location: './package-lock.json' };
46
+ lockFile = { packages: {}, location: mockPackageManager.lockFileName };
31
47
  jest.clearAllMocks();
32
48
  });
33
49
 
34
- const project = () => mockProject({ config, dependencies, packageLock, packages });
50
+ const project = () =>
51
+ mockProject({
52
+ config,
53
+ dependencies,
54
+ packageManager: mockPackageManager as any,
55
+ packages,
56
+ });
35
57
 
36
58
  const subject = () => rule.run(project());
37
59
 
@@ -74,11 +96,11 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
74
96
  beforeEach(() => {
75
97
  dependencies = { [JS_CLIENT_SDK]: { [sdkVersions.package]: [packageName] } };
76
98
  packages = mockPackages(dependencies);
77
- packageLock.packages = {
78
- [`node_modules/${JS_CLIENT_SDK}`]: {
99
+ lockFile.packages = {
100
+ [JS_CLIENT_SDK]: {
79
101
  version: sdkVersions.lockfile,
80
102
  },
81
- [`node_modules/${LD_SERVICE}`]: {
103
+ [LD_SERVICE]: {
82
104
  version: '1.0.0',
83
105
  dependencies: { [JS_CLIENT_SDK]: sdkVersions.ldService },
84
106
  },
@@ -112,47 +134,15 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
112
134
  itReturnsNothing();
113
135
  });
114
136
 
115
- function mergePackageLock(
116
- key: string,
117
- data: Partial<(typeof packageLock)['packages'][string]>
118
- ) {
119
- packageLock.packages[key] = { ...packageLock.packages[key], ...data };
137
+ function mergePackageLock(key: string, data: Partial<LockFile['packages'][string]>) {
138
+ lockFile.packages[key] = { ...lockFile.packages[key], ...data };
120
139
  }
121
140
 
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
141
  describe(`when ${LD_SERVICE} dependency is a compatible range`, () => {
152
142
  const compatibleRange = `^${sdkVersions.ldService}`;
153
143
 
154
144
  beforeEach(() => {
155
- mergePackageLock(`node_modules/${LD_SERVICE}`, {
145
+ mergePackageLock(LD_SERVICE, {
156
146
  dependencies: { [JS_CLIENT_SDK]: compatibleRange },
157
147
  });
158
148
  });
@@ -164,7 +154,7 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
164
154
  const incompatibleRange = `<${sdkVersions.ldService}`;
165
155
 
166
156
  beforeEach(() => {
167
- mergePackageLock(`node_modules/${LD_SERVICE}`, {
157
+ mergePackageLock(LD_SERVICE, {
168
158
  dependencies: { [JS_CLIENT_SDK]: incompatibleRange },
169
159
  });
170
160
  });
@@ -198,7 +188,7 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
198
188
 
199
189
  describe(`when ${JS_CLIENT_SDK} is not in lockfile`, () => {
200
190
  beforeEach(() => {
201
- delete packageLock.packages[`node_modules/${JS_CLIENT_SDK}`];
191
+ delete lockFile.packages[JS_CLIENT_SDK];
202
192
  });
203
193
 
204
194
  itReturnsNothing();
@@ -206,7 +196,7 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
206
196
 
207
197
  describe(`when ${LD_SERVICE} is not in lockfile`, () => {
208
198
  beforeEach(() => {
209
- delete packageLock.packages[`node_modules/${LD_SERVICE}`];
199
+ delete lockFile.packages[LD_SERVICE];
210
200
  });
211
201
 
212
202
  itReturnsNothing();
@@ -217,11 +207,11 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
217
207
  const sdkVersions = { lockfile: '1.1.0', ldService: '1.0.1' };
218
208
 
219
209
  beforeEach(() => {
220
- packageLock.packages = {
221
- [`node_modules/${JS_CLIENT_SDK}`]: {
210
+ lockFile.packages = {
211
+ [JS_CLIENT_SDK]: {
222
212
  version: sdkVersions.lockfile,
223
213
  },
224
- [`node_modules/${LD_SERVICE}`]: {
214
+ [LD_SERVICE]: {
225
215
  version: '1.0.0',
226
216
  dependencies: { [JS_CLIENT_SDK]: sdkVersions.ldService },
227
217
  },
@@ -231,7 +221,7 @@ describe(`[startup] Review ${RequireCompatibleLaunchDarklySdk.name}`, () => {
231
221
  itReturnsError(() => ({
232
222
  message: `project depends on an incompatible version of ${JS_CLIENT_SDK}`,
233
223
  details: `${chalk.yellow(sdkVersions.lockfile)} does not match ${sdkVersions.ldService}`,
234
- location: packageLock.location,
224
+ location: lockFile.location,
235
225
  fixable: FixCategory.lockFile,
236
226
  }));
237
227
 
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { execSync } from 'child_process';
3
- import { expectCalls } from '../../__mocks__';
3
+ import { expectCalls, MockPackageManager } from '../../__mocks__';
4
4
  import { FixCategory, Package, Project, ReviewConfiguration } from '../../types';
5
5
  import { mockConfig, mockPackages, mockProject } from '../__mocks__';
6
6
  import { RequireProjectVersionInRootNodeModules } from '../require-project-version-in-root-node-modules';
@@ -10,20 +10,35 @@ jest.mock('child_process', () => ({ execSync: jest.fn() }));
10
10
  describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () => {
11
11
  const id = 'require-project-version-in-root-node-modules';
12
12
  const rule = new RequireProjectVersionInRootNodeModules();
13
+ const mockPackageManager = new MockPackageManager();
13
14
  let config: ReviewConfiguration;
14
15
  let dependencies: Project['dependencies'];
15
- let packageLock: Project['packageLock'];
16
+ let resolvedRootVersions: Record<string, string>;
16
17
  let packages: Package[];
17
18
 
19
+ beforeAll(() => {
20
+ jest.spyOn(mockPackageManager, 'getResolvedVersion').mockImplementation(
21
+ ({ workspacePackage, dependency }) =>
22
+ workspacePackage === '' ? resolvedRootVersions[dependency] : undefined
23
+ );
24
+ });
25
+
18
26
  beforeEach(() => {
19
27
  config = {};
20
28
  dependencies = { foo: { '~1.0.0': ['lib'] } };
21
29
  packages = [{ name: 'root', location: '.' }, ...mockPackages(dependencies)];
22
- packageLock = { packages: {}, location: './package-lock.json' };
30
+ resolvedRootVersions = {};
31
+ mockPackageManager.command = 'npm';
23
32
  jest.clearAllMocks();
24
33
  });
25
34
 
26
- const project = () => mockProject({ config, dependencies, packageLock, packages });
35
+ const project = () =>
36
+ mockProject({
37
+ config,
38
+ dependencies,
39
+ packageManager: mockPackageManager as any,
40
+ packages,
41
+ });
27
42
 
28
43
  const subject = () => rule.run(project());
29
44
 
@@ -39,7 +54,7 @@ describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () =
39
54
  id,
40
55
  message: 'project has unexpected version in root node_modules',
41
56
  details,
42
- location: packageLock.location,
57
+ location: mockPackageManager.lockFileName,
43
58
  fixable: FixCategory.lockFile,
44
59
  })
45
60
  );
@@ -61,17 +76,13 @@ describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () =
61
76
  }
62
77
 
63
78
  describe('when root matches project version', () => {
64
- beforeEach(() => {
65
- packageLock.packages = { 'node_modules/foo': { version: '1.0.1' } };
66
- });
79
+ beforeEach(() => (resolvedRootVersions = { foo: '1.0.1' }));
67
80
 
68
81
  itReturnsNothing();
69
82
  });
70
83
 
71
84
  describe('when root does not match project version', () => {
72
- beforeEach(() => {
73
- packageLock.packages = { 'node_modules/foo': { version: '1.1.0' } };
74
- });
85
+ beforeEach(() => (resolvedRootVersions = { foo: '1.1.0' }));
75
86
 
76
87
  itReturnsError(`${chalk.yellow('foo@1.1.0')} does not match ${chalk.yellow('~1.0.0')}`);
77
88
 
@@ -81,6 +92,12 @@ describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () =
81
92
  itReturnsNothing();
82
93
  });
83
94
 
95
+ describe('when package manager is pnpm', () => {
96
+ beforeEach(() => (mockPackageManager.command = 'pnpm'));
97
+
98
+ itReturnsNothing();
99
+ });
100
+
84
101
  test('fixes error', () => {
85
102
  fixSubject();
86
103
 
@@ -113,7 +130,7 @@ describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () =
113
130
  });
114
131
 
115
132
  describe('with no root version', () => {
116
- beforeEach(() => (packageLock.packages = {}));
133
+ beforeEach(() => (resolvedRootVersions = {}));
117
134
 
118
135
  itReturnsNothing();
119
136
  });
@@ -122,17 +139,13 @@ describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () =
122
139
  beforeEach(() => (dependencies = { foo: { '~1.0.0': ['lib1'], '^2.0.0': ['lib2'] } }));
123
140
 
124
141
  describe('when root matches version', () => {
125
- beforeEach(() => {
126
- packageLock.packages = { 'node_modules/foo': { version: '2.1.0' } };
127
- });
142
+ beforeEach(() => (resolvedRootVersions = { foo: '2.1.0' }));
128
143
 
129
144
  itReturnsNothing();
130
145
  });
131
146
 
132
147
  describe('when root does not match any version', () => {
133
- beforeEach(() => {
134
- packageLock.packages = { 'node_modules/foo': { version: '1.1.0' } };
135
- });
148
+ beforeEach(() => (resolvedRootVersions = { foo: '1.1.0' }));
136
149
 
137
150
  itReturnsError(
138
151
  `${chalk.yellow('foo@1.1.0')} does not match ${chalk.yellow('~1.0.0, ^2.0.0')}`
@@ -156,10 +169,10 @@ describe(`[startup] Review ${RequireProjectVersionInRootNodeModules.name}`, () =
156
169
  bar: { '>=2 <3': ['lib2'] },
157
170
  baz: { '>=3 <4': ['lib3'] },
158
171
  };
159
- packageLock.packages = {
160
- 'node_modules/foo': { version: '1.1.0' }, // error
161
- 'node_modules/bar': { version: '2.0.1' }, // ok
162
- 'node_modules/baz': { version: '4.0.0' }, // error
172
+ resolvedRootVersions = {
173
+ foo: '1.1.0', // error
174
+ bar: '2.0.1', // ok
175
+ baz: '4.0.0', // error
163
176
  };
164
177
  });
165
178
 
@@ -70,8 +70,8 @@ export class RequireCompatibleLaunchDarklySdk implements PackageRule {
70
70
  return;
71
71
  }
72
72
 
73
- const actualVersion = this.getActualJsClientSdkVersion(name);
74
- const targetVersion = this.getTargetJsClientSdkVersion(name);
73
+ const actualVersion = this.getActualJsClientSdkVersion(location);
74
+ const targetVersion = this.getTargetJsClientSdkVersion(location);
75
75
  if (actualVersion && targetVersion && !satisfies(actualVersion, targetVersion)) {
76
76
  return this.createError({
77
77
  message: `package "${name}" depends on an incompatible version of ${JS_CLIENT_SDK}`,
@@ -87,7 +87,7 @@ export class RequireCompatibleLaunchDarklySdk implements PackageRule {
87
87
  if (actualVersion && targetVersion && !satisfies(actualVersion, targetVersion)) {
88
88
  return this.createError({
89
89
  message: `project depends on an incompatible version of ${JS_CLIENT_SDK}`,
90
- location: this.#project.packageLock.location,
90
+ location: this.#project.packageManager.lockFileName,
91
91
  data: { actualVersion, targetVersion },
92
92
  });
93
93
  }
@@ -109,33 +109,18 @@ export class RequireCompatibleLaunchDarklySdk implements PackageRule {
109
109
  return `${chalk.yellow(actualVersion)} does not match ${targetVersion}`;
110
110
  }
111
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
- );
112
+ private getActualJsClientSdkVersion(workspacePackage = '') {
113
+ return this.#project.packageManager.getResolvedVersion({
114
+ workspacePackage,
115
+ dependency: JS_CLIENT_SDK,
116
+ });
131
117
  }
132
118
 
133
- private getTargetJsClientSdkVersion(packageName?: string) {
134
- const { dependencies = {}, peerDependencies = {} } = this.getResolvedDependency({
135
- packageName,
136
- dependency: LD_SERVICE,
119
+ private getTargetJsClientSdkVersion(workspacePackage = '') {
120
+ return this.#project.packageManager.getDeclaredVersion({
121
+ workspacePackage,
122
+ declaredBy: LD_SERVICE,
123
+ dependency: JS_CLIENT_SDK,
137
124
  });
138
-
139
- return dependencies[JS_CLIENT_SDK] ?? peerDependencies[JS_CLIENT_SDK];
140
125
  }
141
126
  }
@@ -21,6 +21,11 @@ export class RequireProjectVersionInRootNodeModules implements PackageRule {
21
21
  }
22
22
 
23
23
  run(project: Project): PackageError<ErrorData> | undefined {
24
+ // pnpm isolates each package's dependencies, so root version mismatches are harmless
25
+ if (project.packageManager.command === 'pnpm') {
26
+ return;
27
+ }
28
+
24
29
  const mismatches = this.findMismatches(project);
25
30
  if (!mismatches.length) {
26
31
  return;
@@ -31,7 +36,7 @@ export class RequireProjectVersionInRootNodeModules implements PackageRule {
31
36
  message: 'project has unexpected version in root node_modules',
32
37
  details: this.formatDetails(mismatches),
33
38
  fixable: FixCategory.lockFile,
34
- location: project.packageLock.location,
39
+ location: project.packageManager.lockFileName,
35
40
  data: { mismatches },
36
41
  };
37
42
  }
@@ -80,9 +85,11 @@ export class RequireProjectVersionInRootNodeModules implements PackageRule {
80
85
  }
81
86
 
82
87
  private findMismatches(project: Project) {
83
- const { packageLock } = project;
84
88
  return this.getDependencies(project).reduce((result, [name, versions]) => {
85
- const rootVersion = packageLock.packages[`node_modules/${name}`]?.version;
89
+ const rootVersion = project.packageManager.getResolvedVersion({
90
+ workspacePackage: '',
91
+ dependency: name,
92
+ });
86
93
  if (rootVersion) {
87
94
  const projectVersions = Object.keys(versions);
88
95
  if (!projectVersions.some(version => satisfies(rootVersion, version))) {
@@ -1,3 +1,5 @@
1
+ import { PackageManager } from '@servicetitan/install';
2
+
1
3
  export abstract class PackageRule {
2
4
  abstract get id(): string;
3
5
  abstract run(project: Project): PackageError | PackageError[] | undefined;
@@ -7,18 +9,7 @@ export abstract class PackageRule {
7
9
  export interface Project {
8
10
  config: ReviewConfiguration;
9
11
  dependencies: Dependencies;
10
- // Only using this subset of package-lock.json
11
- packageLock: {
12
- packages: Record<
13
- string,
14
- {
15
- version: string;
16
- dependencies?: Record<string, string>;
17
- peerDependencies?: Record<string, string>;
18
- }
19
- >;
20
- location: string;
21
- };
12
+ packageManager: PackageManager;
22
13
  packages: Package[];
23
14
  }
24
15
 
@@ -5,10 +5,17 @@ export * from './cli-git';
5
5
  export * from './cli-npm';
6
6
  export * from './cli-os';
7
7
  export * from './compile';
8
- export * from './compile-less';
9
- export * from './compile-sass';
8
+ /*
9
+ * These modules eagerly load heavy dependencies (less, sass). Importing them
10
+ * from the barrel causes every test that touches cli/utils to pay that cost
11
+ * and spams "Less has finished and no sheets were loaded" in test output.
12
+ * Import directly from the module instead (e.g., import { copyFiles } from './copy-files').
13
+ *
14
+ * export * from './compile-less';
15
+ * export * from './compile-sass';
16
+ * export * from './copy-files';
17
+ */
10
18
  export * from './constants';
11
- export * from './copy-files';
12
19
  export * from './eslint';
13
20
  export * from './get-module-type';
14
21
  export * from './is-module-installed';
@@ -45,15 +45,6 @@ describe(`[startup/cypress-config] ${webpackConfig.name}`, () => {
45
45
  );
46
46
  });
47
47
 
48
- test('enables code coverage', () => {
49
- subject();
50
-
51
- expect(rulesConfig).toHaveBeenCalledWith(
52
- expect.objectContaining({ codeCoverage: true }),
53
- expect.anything()
54
- );
55
- });
56
-
57
48
  test('includes all rules from rulesConfig', () => {
58
49
  const result = subject();
59
50
 
@@ -27,7 +27,6 @@ export function webpackConfig(overrides?: Overrides): Configuration {
27
27
 
28
28
  function getContext(): Context {
29
29
  return {
30
- codeCoverage: true,
31
30
  destination: '',
32
31
  isProduction: false,
33
32
  name: '',
@@ -1,26 +1,118 @@
1
+ /**
2
+ * Applies patch-package patches after startup is installed in a consumer project.
3
+ *
4
+ * Under npm, patch-package finds packages directly in node_modules/. Under pnpm,
5
+ * packages live in the .pnpm store and aren't at their expected node_modules/<pkg>
6
+ * paths. This script bridges the gap by creating temporary symlinks so
7
+ * patch-package can locate and patch them, then cleans up the symlinks afterward.
8
+ *
9
+ * Only runs when startup is installed inside a project directory (not globally
10
+ * or in an npx cache). Failures are logged as warnings to avoid blocking installs.
11
+ */
1
12
  const { execFileSync } = require('child_process');
2
13
  const fs = require('fs');
3
14
  const path = require('path');
4
15
 
5
- const cwd = process.env.INIT_CWD;
6
- const patchDir = path.resolve('patches');
7
- const relativePatchDir = path.relative(cwd, patchDir);
8
- const isInstalledInCurrentDirectory = patchDir.startsWith(path.resolve(cwd));
9
- console.log({ cwd, patchDir, relativePatchDir, isInstalledInCurrentDirectory }); // eslint-disable-line no-console
16
+ /**
17
+ * Parse patch filenames and resolve each package's install location in the
18
+ * .pnpm store. Returns an array of { pkg, link, target } for packages that
19
+ * need a temporary symlink (i.e., not already at node_modules/<pkg>).
20
+ *
21
+ * Expected filename format (patch-package convention):
22
+ * <name>+<version>.patch — e.g., jsdom+26.1.0.patch
23
+ * <name>+<version>.dev.patch — e.g., jsdom+26.1.0.dev.patch
24
+ * @<scope>+<name>+<version>.patch — e.g., @babel+core+7.28.5.patch
25
+ */
26
+ function getPnpmPatchTargets(cwd, patchDir) {
27
+ const pnpmDir = path.join(cwd, 'node_modules', '.pnpm');
28
+ if (!fs.existsSync(pnpmDir)) {
29
+ return [];
30
+ }
31
+
32
+ const targets = [];
33
+ for (const file of fs.readdirSync(patchDir).filter(f => f.endsWith('.patch'))) {
34
+ const match = file.match(/^(?<name>.+)\+\d/);
35
+ if (!match?.groups) {
36
+ continue;
37
+ }
38
+
39
+ const { name } = match.groups;
40
+ const pkg = name.replace(/\+/, '/'); // @scope+name -> @scope/name
41
+ const link = path.join(cwd, 'node_modules', pkg);
42
+ if (fs.existsSync(link)) {
43
+ // already exists
44
+ continue;
45
+ }
46
+
47
+ const entry = fs.readdirSync(pnpmDir).find(e => e.startsWith(`${name}@`));
48
+ if (!entry) {
49
+ // package not installed (e.g., .dev patch in production install)
50
+ continue;
51
+ }
52
+
53
+ targets.push({
54
+ pkg,
55
+ link,
56
+ target: path.join(pnpmDir, entry, 'node_modules', pkg),
57
+ });
58
+ }
59
+ return targets;
60
+ }
61
+
62
+ /*
63
+ * For pnpm, patch-package can't find packages because they live in the .pnpm
64
+ * store instead of node_modules/<pkg>. We create temporary symlinks so
65
+ * patch-package can find them, then clean up after.
66
+ */
67
+ function addPnpmSymlinks(targets) {
68
+ for (const { link, target } of targets) {
69
+ fs.mkdirSync(path.dirname(link), { recursive: true });
70
+ fs.symlinkSync(target, link);
71
+ }
72
+ }
73
+
74
+ function removePnpmSymlinks(targets) {
75
+ for (const { link } of targets) {
76
+ fs.unlinkSync(link);
77
+ }
78
+ }
79
+
80
+ function main() {
81
+ const cwd = process.env.INIT_CWD;
82
+ const patchDir = path.resolve('patches');
83
+ const relativePatchDir = path.relative(cwd, patchDir);
84
+ const isInstalledInCurrentDirectory = patchDir.startsWith(path.resolve(cwd));
10
85
 
11
- try {
12
86
  // Only apply patches when installed in project (as opposed to globally or in npx cache)
13
- if (fs.existsSync('package.json') && isInstalledInCurrentDirectory) {
14
- execFileSync('npx', ['patch-package', '--error-on-fail', '--patch-dir', relativePatchDir], {
87
+ if (!fs.existsSync('package.json') || !isInstalledInCurrentDirectory) {
88
+ return;
89
+ }
90
+
91
+ const patchTargets = getPnpmPatchTargets(cwd, patchDir);
92
+ addPnpmSymlinks(patchTargets);
93
+ try {
94
+ execFileSync('patch-package', ['--error-on-fail', '--patch-dir', relativePatchDir], {
15
95
  cwd,
16
96
  shell: process.platform === 'win32',
17
97
  stdio: 'inherit',
18
98
  });
99
+ } finally {
100
+ removePnpmSymlinks(patchTargets);
19
101
  }
20
- } catch (e) {
21
- /*
22
- * To avoid disrupting workflows only log warning when postinstall fails.
23
- * To see detailed error, run npm with --foreground-scripts option.
24
- */
25
- console.warn(`postinstall failed`, e); // eslint-disable-line no-console
26
102
  }
103
+
104
+ // Only run when executed directly (e.g., `node postinstall.js`), not when imported (e.g., in tests).
105
+ /* istanbul ignore next */
106
+ if (require.main === module) {
107
+ try {
108
+ main();
109
+ } catch (e) {
110
+ /*
111
+ * To avoid disrupting workflows only log warning when postinstall fails.
112
+ * To see detailed error, run npm with --foreground-scripts option.
113
+ */
114
+ console.warn(`postinstall failed`, e); // eslint-disable-line no-console
115
+ }
116
+ }
117
+
118
+ module.exports = { main };