@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.
- package/bin/_run.js +77 -0
- package/bin/cpx.js +3 -0
- package/bin/eslint.js +3 -0
- package/bin/jest.js +3 -0
- package/bin/js-yaml.js +3 -0
- package/bin/json5.js +3 -0
- package/bin/kendo-ui-license.js +3 -0
- package/bin/lerna.js +3 -0
- package/bin/lessc.js +3 -0
- package/bin/patch-package.js +3 -0
- package/bin/prettier.js +3 -0
- package/bin/sass.js +3 -0
- package/bin/semver.js +3 -0
- package/bin/spack.js +3 -0
- package/bin/stylelint.js +3 -0
- package/bin/swc.js +3 -0
- package/bin/swcx.js +3 -0
- package/bin/tcm.js +3 -0
- package/bin/ts-jest.js +3 -0
- package/bin/ts-node-cwd.js +3 -0
- package/bin/ts-node-esm.js +3 -0
- package/bin/ts-node-script.js +3 -0
- package/bin/ts-node-transpile-only.js +3 -0
- package/bin/ts-node.js +3 -0
- package/bin/ts-script.js +3 -0
- package/bin/tsc.js +3 -0
- package/bin/tsserver.js +3 -0
- package/bin/vitest.js +3 -0
- package/bin/webpack-bundle-analyzer.js +3 -0
- package/bin/webpack-dev-server.js +3 -0
- package/bin/webpack.js +3 -0
- package/dist/cli/commands/clean.d.ts +1 -0
- package/dist/cli/commands/clean.d.ts.map +1 -1
- package/dist/cli/commands/clean.js +18 -8
- package/dist/cli/commands/clean.js.map +1 -1
- package/dist/cli/commands/coverage-finalize.d.ts +16 -0
- package/dist/cli/commands/coverage-finalize.d.ts.map +1 -0
- package/dist/cli/commands/coverage-finalize.js +41 -0
- package/dist/cli/commands/coverage-finalize.js.map +1 -0
- package/dist/cli/commands/install.js +1 -1
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/kendo-ui-license.js +1 -2
- package/dist/cli/commands/kendo-ui-license.js.map +1 -1
- package/dist/cli/commands/prepare-package.js +2 -2
- package/dist/cli/commands/prepare-package.js.map +1 -1
- package/dist/cli/commands/registry/coverage-finalize.d.ts +5 -0
- package/dist/cli/commands/registry/coverage-finalize.d.ts.map +1 -0
- package/dist/cli/commands/registry/coverage-finalize.js +17 -0
- package/dist/cli/commands/registry/coverage-finalize.js.map +1 -0
- package/dist/cli/commands/review/review.d.ts.map +1 -1
- package/dist/cli/commands/review/review.js +8 -7
- package/dist/cli/commands/review/review.js.map +1 -1
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts +0 -1
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts.map +1 -1
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js +12 -18
- package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.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 +9 -4
- package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js.map +1 -1
- package/dist/cli/commands/review/types.d.ts +2 -8
- package/dist/cli/commands/review/types.d.ts.map +1 -1
- package/dist/cli/commands/review/types.js.map +1 -1
- package/dist/cli/utils/index.d.ts +0 -3
- package/dist/cli/utils/index.d.ts.map +1 -1
- package/dist/cli/utils/index.js +0 -3
- package/dist/cli/utils/index.js.map +1 -1
- package/dist/cypress/config/webpack-config.js +0 -1
- package/dist/cypress/config/webpack-config.js.map +1 -1
- package/dist/storybook-config/webpack-final.d.ts.map +1 -1
- package/dist/storybook-config/webpack-final.js +0 -6
- package/dist/storybook-config/webpack-final.js.map +1 -1
- package/dist/utils/default-excludes.d.ts +14 -0
- package/dist/utils/default-excludes.d.ts.map +1 -0
- package/dist/utils/default-excludes.js +23 -0
- package/dist/utils/default-excludes.js.map +1 -0
- package/dist/utils/find-packages.d.ts.map +1 -1
- package/dist/utils/find-packages.js +15 -0
- package/dist/utils/find-packages.js.map +1 -1
- package/dist/utils/get-coverage-aliases.d.ts +23 -0
- package/dist/utils/get-coverage-aliases.d.ts.map +1 -0
- package/dist/utils/get-coverage-aliases.js +41 -0
- package/dist/utils/get-coverage-aliases.js.map +1 -0
- package/dist/utils/get-coverage-source-patterns.d.ts +21 -0
- package/dist/utils/get-coverage-source-patterns.d.ts.map +1 -0
- package/dist/utils/get-coverage-source-patterns.js +39 -0
- package/dist/utils/get-coverage-source-patterns.js.map +1 -0
- package/dist/utils/get-jest-config.d.ts.map +1 -1
- package/dist/utils/get-jest-config.js +2 -1
- package/dist/utils/get-jest-config.js.map +1 -1
- package/dist/utils/prettify.js +1 -1
- package/dist/utils/prettify.js.map +1 -1
- package/dist/webpack/configs/cache-config.d.ts.map +1 -1
- package/dist/webpack/configs/cache-config.js +3 -1
- package/dist/webpack/configs/cache-config.js.map +1 -1
- package/dist/webpack/configs/rules/ts-coverage-rules.d.ts +22 -0
- package/dist/webpack/configs/rules/ts-coverage-rules.d.ts.map +1 -0
- package/dist/webpack/configs/rules/ts-coverage-rules.js +48 -0
- package/dist/webpack/configs/rules/ts-coverage-rules.js.map +1 -0
- package/eslint/config.mjs +2 -0
- package/package.json +54 -18
- package/src/__tests__/postinstall.test.ts +173 -0
- package/src/cli/commands/__tests__/clean.test.ts +30 -12
- package/src/cli/commands/__tests__/install.test.ts +3 -1
- package/src/cli/commands/__tests__/kendo-ui-license.test.ts +4 -4
- package/src/cli/commands/__tests__/prepare-package.test.ts +2 -3
- package/src/cli/commands/clean.ts +18 -9
- package/src/cli/commands/install.ts +1 -1
- package/src/cli/commands/kendo-ui-license.ts +1 -1
- package/src/cli/commands/prepare-package.ts +1 -1
- package/src/cli/commands/review/__mocks__/index.ts +1 -0
- package/src/cli/commands/review/__mocks__/mock-package-manager.ts +20 -0
- package/src/cli/commands/review/__tests__/review.test.ts +23 -31
- package/src/cli/commands/review/review.ts +11 -7
- package/src/cli/commands/review/rules/__mocks__/mock-project.ts +2 -1
- package/src/cli/commands/review/rules/__tests__/require-compatible-launch-darkly-sdk.test.ts +39 -49
- package/src/cli/commands/review/rules/__tests__/require-project-version-in-root-node-modules.test.ts +35 -22
- package/src/cli/commands/review/rules/require-compatible-launch-darkly-sdk.ts +13 -28
- package/src/cli/commands/review/rules/require-project-version-in-root-node-modules.ts +10 -3
- package/src/cli/commands/review/types.ts +3 -12
- package/src/cli/utils/index.ts +10 -3
- package/src/cypress/config/__tests__/webpack-config.test.ts +0 -9
- package/src/cypress/config/webpack-config.ts +0 -1
- package/src/postinstall.js +106 -14
- package/src/scripts/__tests__/generate-bin-wrappers.test.ts +226 -0
- package/src/scripts/generate-bin-wrappers.js +205 -0
- package/src/storybook-config/__tests__/webpack-final.test.ts +0 -25
- package/src/storybook-config/webpack-final.ts +0 -4
- package/src/utils/__tests__/get-jest-config.test.ts +3 -1
- package/src/utils/__tests__/get-packages.test.ts +47 -2
- package/src/utils/__tests__/prettify.test.ts +1 -1
- package/src/utils/find-packages.ts +16 -0
- package/src/utils/get-jest-config.ts +4 -1
- package/src/utils/prettify.ts +1 -1
- package/src/webpack/configs/cache-config.ts +3 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { vol } from 'memfs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
jest.mock('fs', () => require('memfs').fs);
|
|
7
|
+
jest.mock('child_process');
|
|
8
|
+
|
|
9
|
+
const { main } = require('../postinstall');
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Node 22's path.resolve uses a native binding that bypasses jest.spyOn on
|
|
13
|
+
* process.cwd. Re-wire path.resolve so it picks up the mocked cwd.
|
|
14
|
+
*/
|
|
15
|
+
const realResolve = path.resolve.bind(path);
|
|
16
|
+
|
|
17
|
+
describe('[startup] postinstall', () => {
|
|
18
|
+
const projectDir = '/project';
|
|
19
|
+
const nodeModulesDir = path.join(projectDir, 'node_modules');
|
|
20
|
+
const startupDir = path.join(nodeModulesDir, '@servicetitan', 'startup');
|
|
21
|
+
const patchDir = path.join(startupDir, 'patches');
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
jest.spyOn(process, 'cwd').mockReturnValue(startupDir);
|
|
26
|
+
jest.spyOn(path, 'resolve').mockImplementation((...args: string[]) =>
|
|
27
|
+
realResolve(process.cwd(), ...args)
|
|
28
|
+
);
|
|
29
|
+
process.env.INIT_CWD = projectDir;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
vol.reset();
|
|
34
|
+
delete process.env.INIT_CWD;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe(main.name, () => {
|
|
38
|
+
const pkg = { name: 'jsdom', version: '26.1.0' };
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vol.fromJSON({
|
|
42
|
+
[path.join(nodeModulesDir, pkg.name, 'index.js')]: '',
|
|
43
|
+
[path.join(startupDir, 'package.json')]: '{}',
|
|
44
|
+
[path.join(patchDir, `${pkg.name}+${pkg.version}.patch`)]: '',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const subject = () => main();
|
|
49
|
+
|
|
50
|
+
function itRunsPatchPackage() {
|
|
51
|
+
test('runs patch-package', () => {
|
|
52
|
+
subject();
|
|
53
|
+
|
|
54
|
+
expect(execFileSync).toHaveBeenCalledWith(
|
|
55
|
+
'patch-package',
|
|
56
|
+
['--error-on-fail', '--patch-dir', path.relative(projectDir, patchDir)],
|
|
57
|
+
expect.objectContaining({ cwd: projectDir })
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function itDoesNothing() {
|
|
63
|
+
test('does not run patch-package', () => {
|
|
64
|
+
subject();
|
|
65
|
+
|
|
66
|
+
expect(execFileSync).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
itRunsPatchPackage();
|
|
71
|
+
|
|
72
|
+
describe('when package.json does not exist', () => {
|
|
73
|
+
beforeEach(() => fs.rmSync(path.join(startupDir, 'package.json')));
|
|
74
|
+
|
|
75
|
+
itDoesNothing();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('when patches are outside project directory', () => {
|
|
79
|
+
beforeEach(() => (process.env.INIT_CWD = '/other'));
|
|
80
|
+
|
|
81
|
+
itDoesNothing();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('with pnpm project', () => {
|
|
85
|
+
const npmPath = path.join(nodeModulesDir, pkg.name);
|
|
86
|
+
const pnpmStore = path.join(nodeModulesDir, '.pnpm');
|
|
87
|
+
const pnpmPath = path.join(
|
|
88
|
+
pnpmStore,
|
|
89
|
+
`${pkg.name}@${pkg.version}`,
|
|
90
|
+
'node_modules',
|
|
91
|
+
pkg.name
|
|
92
|
+
);
|
|
93
|
+
let createdSymlink: boolean;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
createdSymlink = false;
|
|
97
|
+
fs.rmSync(npmPath, { recursive: true });
|
|
98
|
+
fs.mkdirSync(pnpmPath, { recursive: true });
|
|
99
|
+
fs.writeFileSync(path.join(pnpmPath, 'index.js'), '');
|
|
100
|
+
jest.mocked(execFileSync).mockImplementation((): any => {
|
|
101
|
+
createdSymlink = fs.existsSync(npmPath);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
itRunsPatchPackage();
|
|
106
|
+
|
|
107
|
+
test('creates transient symlink for patch-package', () => {
|
|
108
|
+
subject();
|
|
109
|
+
|
|
110
|
+
expect(createdSymlink).toBe(true);
|
|
111
|
+
expect(fs.existsSync(npmPath)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('when package already exists in node_modules', () => {
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
fs.mkdirSync(npmPath, { recursive: true });
|
|
117
|
+
fs.writeFileSync(path.join(npmPath, 'index.js'), '');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('does not replace it with a symlink', () => {
|
|
121
|
+
subject();
|
|
122
|
+
|
|
123
|
+
expect(fs.lstatSync(npmPath).isSymbolicLink()).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
function itDoesNotCreateSymlink() {
|
|
128
|
+
test('does not create symlink', () => {
|
|
129
|
+
subject();
|
|
130
|
+
|
|
131
|
+
expect(createdSymlink).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
describe('when package is not in .pnpm store', () => {
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
fs.rmSync(path.join(pnpmStore, `${pkg.name}@${pkg.version}`), {
|
|
138
|
+
recursive: true,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
itDoesNotCreateSymlink();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('when patch filename is not valid', () => {
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
fs.renameSync(
|
|
148
|
+
path.join(patchDir, `${pkg.name}+${pkg.version}.patch`),
|
|
149
|
+
path.join(patchDir, `${pkg.name}.patch`)
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
itDoesNotCreateSymlink();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('when patch-package throws', () => {
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
jest.mocked(execFileSync).mockImplementation(() => {
|
|
159
|
+
createdSymlink = fs.existsSync(npmPath);
|
|
160
|
+
throw new Error('patch failed');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('removes symlink', () => {
|
|
165
|
+
expect(() => subject()).toThrow();
|
|
166
|
+
|
|
167
|
+
expect(createdSymlink).toBe(true);
|
|
168
|
+
expect(fs.existsSync(npmPath)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { getPackageManager } from '@servicetitan/install';
|
|
1
2
|
import { fs, vol } from 'memfs';
|
|
2
3
|
import { exec, ExecException, execSync } from 'node:child_process';
|
|
3
4
|
import { log } from '../../../utils';
|
|
4
5
|
import { Clean } from '../clean';
|
|
5
6
|
import { entry } from '../registry/clean';
|
|
6
7
|
|
|
8
|
+
jest.mock('@servicetitan/install', () => ({ getPackageManager: jest.fn() }));
|
|
7
9
|
jest.mock('node:fs', () => fs);
|
|
8
10
|
jest.mock('node:child_process', () => ({
|
|
9
11
|
execSync: jest.fn(),
|
|
@@ -15,6 +17,7 @@ type ExecCallback = (error: ExecException | null, stdout: string, stderr: string
|
|
|
15
17
|
describe(`[startup] ${Clean.name}`, () => {
|
|
16
18
|
const cachePath = '/test-npm-cache';
|
|
17
19
|
const npxCacheDirectoryName = '_npx';
|
|
20
|
+
const packageManager: Partial<ReturnType<typeof getPackageManager>> = { clearCache: jest.fn() };
|
|
18
21
|
|
|
19
22
|
beforeEach(() => {
|
|
20
23
|
jest.clearAllMocks();
|
|
@@ -23,6 +26,7 @@ describe(`[startup] ${Clean.name}`, () => {
|
|
|
23
26
|
jest.mocked(execSync).mockImplementation((cmd: string) =>
|
|
24
27
|
cmd === 'npm config get cache' ? cachePath : ''
|
|
25
28
|
);
|
|
29
|
+
jest.mocked(getPackageManager).mockReturnValue(packageManager as any);
|
|
26
30
|
// @ts-expect-error this is a valid exec overload but TS shows an error
|
|
27
31
|
jest.mocked(exec).mockImplementation((_cmd: string, callback?: ExecCallback) => {
|
|
28
32
|
if (callback) {
|
|
@@ -77,14 +81,17 @@ describe(`[startup] ${Clean.name}`, () => {
|
|
|
77
81
|
});
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
test.each(['
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
test.each(['nx reset', 'jest --clearCache'])('runs "%s"', async command => {
|
|
85
|
+
await subject();
|
|
86
|
+
|
|
87
|
+
expect(exec).toHaveBeenCalledWith(command, expect.any(Function));
|
|
88
|
+
});
|
|
84
89
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
test('clears package manager cache', async () => {
|
|
91
|
+
await subject();
|
|
92
|
+
|
|
93
|
+
expect(packageManager.clearCache).toHaveBeenCalled();
|
|
94
|
+
});
|
|
88
95
|
|
|
89
96
|
itRunsGitClean();
|
|
90
97
|
|
|
@@ -94,22 +101,33 @@ describe(`[startup] ${Clean.name}`, () => {
|
|
|
94
101
|
expect(vol.existsSync(`${cachePath}/${npxCacheDirectoryName}`)).toBe(false);
|
|
95
102
|
});
|
|
96
103
|
|
|
97
|
-
describe('when "
|
|
104
|
+
describe('when "nx reset" fails', () => {
|
|
98
105
|
beforeEach(() => {
|
|
99
|
-
failExecOn('
|
|
106
|
+
failExecOn('nx reset');
|
|
100
107
|
});
|
|
101
108
|
|
|
102
|
-
itLogsError('Error running
|
|
109
|
+
itLogsError('Error running nx reset');
|
|
103
110
|
itRunsGitClean();
|
|
104
111
|
|
|
105
112
|
test('runs remaining commands', async () => {
|
|
106
113
|
await subject();
|
|
107
114
|
|
|
108
|
-
expect(exec).toHaveBeenCalledWith('
|
|
109
|
-
expect(
|
|
115
|
+
expect(exec).toHaveBeenCalledWith('jest --clearCache', expect.any(Function));
|
|
116
|
+
expect(packageManager.clearCache).toHaveBeenCalled();
|
|
110
117
|
});
|
|
111
118
|
});
|
|
112
119
|
|
|
120
|
+
describe('when clearing package manager cache fails', () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
jest.spyOn(packageManager, 'clearCache').mockImplementation(() => {
|
|
123
|
+
throw new Error('Oops!');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
itLogsError('Error clearing package manager cache');
|
|
128
|
+
itRunsGitClean();
|
|
129
|
+
});
|
|
130
|
+
|
|
113
131
|
describe('when clearing npx cache fails', () => {
|
|
114
132
|
beforeEach(() => {
|
|
115
133
|
jest.mocked(execSync).mockImplementation(cmd => {
|
|
@@ -29,7 +29,9 @@ describe(`${Install.name}`, () => {
|
|
|
29
29
|
test('runs install', async () => {
|
|
30
30
|
await subject();
|
|
31
31
|
|
|
32
|
-
expect(execSync).toHaveBeenCalledWith(`npx @servicetitan/install`, {
|
|
32
|
+
expect(execSync).toHaveBeenCalledWith(`npx --yes @servicetitan/install`, {
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
});
|
|
33
35
|
});
|
|
34
36
|
|
|
35
37
|
const keys = ['fix', 'quiet', 'token'] satisfies (keyof typeof args)[];
|
|
@@ -33,8 +33,8 @@ describe(`${kendoUILicense.name}`, () => {
|
|
|
33
33
|
|
|
34
34
|
expect(log.info).toHaveBeenCalledWith('Activating Kendo UI with embedded license...');
|
|
35
35
|
expect(execa).toHaveBeenCalledWith(
|
|
36
|
-
'
|
|
37
|
-
['
|
|
36
|
+
'kendo-ui-license',
|
|
37
|
+
['activate', '--no-install'],
|
|
38
38
|
expect.objectContaining({
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
40
40
|
env: { KENDO_UI_LICENSE: expect.anything() },
|
|
@@ -54,8 +54,8 @@ describe(`${kendoUILicense.name}`, () => {
|
|
|
54
54
|
'Activating Kendo UI with license from environment...'
|
|
55
55
|
);
|
|
56
56
|
expect(execa).toHaveBeenCalledWith(
|
|
57
|
-
'
|
|
58
|
-
['
|
|
57
|
+
'kendo-ui-license',
|
|
58
|
+
['activate', '--no-install'],
|
|
59
59
|
expect.objectContaining({ env: undefined })
|
|
60
60
|
);
|
|
61
61
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { copyFiles } from '../../utils';
|
|
1
|
+
import { copyFiles } from '../../utils/copy-files';
|
|
2
2
|
import { PreparePackage } from '../prepare-package';
|
|
3
3
|
|
|
4
|
-
jest.mock('../../utils', () => ({
|
|
5
|
-
...jest.requireActual('../../utils'),
|
|
4
|
+
jest.mock('../../utils/copy-files', () => ({
|
|
6
5
|
copyFiles: jest.fn(),
|
|
7
6
|
}));
|
|
8
7
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getPackageManager } from '@servicetitan/install';
|
|
1
2
|
import { exec as execAsync, execSync } from 'node:child_process';
|
|
2
3
|
import fs from 'node:fs';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -10,11 +11,11 @@ import { Command } from './types';
|
|
|
10
11
|
const exec = promisify(execAsync);
|
|
11
12
|
|
|
12
13
|
enum CleanProcesses {
|
|
13
|
-
|
|
14
|
+
CleanGitWorkingTree,
|
|
14
15
|
ClearJestCache,
|
|
15
|
-
ClearNpmCache,
|
|
16
16
|
ClearNpxCache,
|
|
17
|
-
|
|
17
|
+
ClearPackageManagerCache,
|
|
18
|
+
ResetNx,
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export class Clean extends Command<typeof entry> {
|
|
@@ -22,12 +23,10 @@ export class Clean extends Command<typeof entry> {
|
|
|
22
23
|
async execute() {
|
|
23
24
|
const processTree = new ProcessTree<typeof CleanProcesses>();
|
|
24
25
|
|
|
25
|
-
processTree.add(CleanProcesses.ResetNx, () => this.runCommand('
|
|
26
|
-
processTree.add(CleanProcesses.ClearJestCache, () =>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
processTree.add(CleanProcesses.ClearNpmCache, () =>
|
|
30
|
-
this.runCommand('npm cache clear --force')
|
|
26
|
+
processTree.add(CleanProcesses.ResetNx, () => this.runCommand('nx reset'));
|
|
27
|
+
processTree.add(CleanProcesses.ClearJestCache, () => this.runCommand('jest --clearCache'));
|
|
28
|
+
processTree.add(CleanProcesses.ClearPackageManagerCache, () =>
|
|
29
|
+
Promise.resolve(this.clearPackageManagerCache())
|
|
31
30
|
);
|
|
32
31
|
processTree.add(CleanProcesses.ClearNpxCache, () => this.clearNpxCache());
|
|
33
32
|
|
|
@@ -63,4 +62,14 @@ export class Clean extends Command<typeof entry> {
|
|
|
63
62
|
log.error(`Error clearing npx cache: ${error}`);
|
|
64
63
|
}
|
|
65
64
|
}
|
|
65
|
+
|
|
66
|
+
private clearPackageManagerCache() {
|
|
67
|
+
try {
|
|
68
|
+
const packageManager = getPackageManager();
|
|
69
|
+
log.info(`Clearing ${packageManager.command} cache`);
|
|
70
|
+
packageManager.clearCache();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
log.error(`Error clearing package manager cache: ${error}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
66
75
|
}
|
|
@@ -17,7 +17,7 @@ export class Install extends Command<typeof entry> {
|
|
|
17
17
|
this.args.token === false ? '--no-token' : '',
|
|
18
18
|
].filter(option => !!option);
|
|
19
19
|
|
|
20
|
-
const command = `npx @servicetitan/install ${options.join(' ')}`.trim();
|
|
20
|
+
const command = `npx --yes @servicetitan/install ${options.join(' ')}`.trim();
|
|
21
21
|
|
|
22
22
|
log.debug('install', command);
|
|
23
23
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DeclaredVersionQuery, ResolvedVersionQuery } from '@servicetitan/install';
|
|
2
|
+
|
|
3
|
+
export class MockPackageManager {
|
|
4
|
+
command = 'test';
|
|
5
|
+
lockFileName = 'test-lock.json';
|
|
6
|
+
|
|
7
|
+
getDeclaredVersion(_query: DeclaredVersionQuery): string | undefined {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getResolvedVersion(_query: ResolvedVersionQuery): string | undefined {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
hasValidLockFile(): boolean {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
installPackages() {}
|
|
20
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import { getPackageManager, PackageManager } from '@servicetitan/install';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
3
|
import { fs, vol } from 'memfs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import terminalLink from 'terminal-link';
|
|
6
6
|
import type { ProjectPackage } from '../../../../utils';
|
|
7
7
|
import { findPackages, getReviewConfiguration, log } from '../../../../utils';
|
|
8
8
|
import { entry } from '../../registry/review';
|
|
9
|
-
import { expectCalls } from '../__mocks__';
|
|
9
|
+
import { expectCalls, MockPackageManager } from '../__mocks__';
|
|
10
10
|
import { Review } from '../review';
|
|
11
11
|
import { rules } from '../rules';
|
|
12
12
|
import { ErrorSeverity, FixCategory, Level, PackageError, ReviewConfiguration } from '../types';
|
|
13
13
|
import { indent } from '../utils';
|
|
14
14
|
|
|
15
|
-
jest.mock('
|
|
15
|
+
jest.mock('@servicetitan/install', () => ({ getPackageManager: jest.fn() }));
|
|
16
16
|
jest.mock('fs', () => fs);
|
|
17
17
|
jest.mock('terminal-link', () => jest.fn());
|
|
18
18
|
jest.mock('../../../../utils', () => ({
|
|
@@ -73,7 +73,6 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
73
73
|
|
|
74
74
|
describe('when run from workspace root', () => {
|
|
75
75
|
const rootPackageJSON = { name: 'root', workspaces: ['packages/*'] };
|
|
76
|
-
const packageLockJSON = { packages: {} };
|
|
77
76
|
const packages: ProjectPackage[] = [
|
|
78
77
|
{
|
|
79
78
|
name: 'a',
|
|
@@ -96,11 +95,12 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
96
95
|
foo: { '1.0.0': ['b'], '1.0.1': ['a'] },
|
|
97
96
|
};
|
|
98
97
|
let config: ReviewConfiguration;
|
|
98
|
+
let packageManager: PackageManager;
|
|
99
99
|
|
|
100
100
|
function project() {
|
|
101
101
|
return {
|
|
102
102
|
config: {},
|
|
103
|
-
|
|
103
|
+
packageManager,
|
|
104
104
|
packages: [{ ...rootPackageJSON, location: '.' }, ...packages],
|
|
105
105
|
dependencies: expectedDependencies,
|
|
106
106
|
};
|
|
@@ -108,12 +108,15 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
108
108
|
|
|
109
109
|
beforeEach(() => {
|
|
110
110
|
config = {};
|
|
111
|
+
packageManager = new MockPackageManager() as any;
|
|
111
112
|
vol.fromJSON({
|
|
112
113
|
'package.json': JSON.stringify(rootPackageJSON),
|
|
113
|
-
|
|
114
|
+
[packageManager.lockFileName]: JSON.stringify({}),
|
|
114
115
|
});
|
|
115
116
|
jest.resetAllMocks();
|
|
117
|
+
jest.spyOn(packageManager, 'installPackages');
|
|
116
118
|
jest.mocked(findPackages).mockImplementation(() => packages);
|
|
119
|
+
jest.mocked(getPackageManager).mockReturnValue(packageManager);
|
|
117
120
|
jest.mocked(getReviewConfiguration).mockImplementation(() => config);
|
|
118
121
|
});
|
|
119
122
|
|
|
@@ -133,21 +136,10 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
133
136
|
|
|
134
137
|
itReportsNoErrors();
|
|
135
138
|
|
|
136
|
-
describe('
|
|
137
|
-
beforeEach(() =>
|
|
139
|
+
describe('when lock file is missing or invalid', () => {
|
|
140
|
+
beforeEach(() => jest.spyOn(packageManager, 'hasValidLockFile').mockReturnValue(false));
|
|
138
141
|
|
|
139
|
-
itThrowsError(/
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('when package-lock.json omits "packages"', () => {
|
|
143
|
-
beforeEach(() => {
|
|
144
|
-
vol.fromJSON({
|
|
145
|
-
'package.json': JSON.stringify(rootPackageJSON),
|
|
146
|
-
'package-lock.json': JSON.stringify({}),
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
itThrowsError(/must be run with valid package-lock\.json/);
|
|
142
|
+
itThrowsError(/missing or invalid lock file/);
|
|
151
143
|
});
|
|
152
144
|
|
|
153
145
|
describe('with --rule option', () => {
|
|
@@ -277,8 +269,8 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
277
269
|
test('regenerates lockfile', async () => {
|
|
278
270
|
await subject();
|
|
279
271
|
|
|
280
|
-
expect(
|
|
281
|
-
|
|
272
|
+
expect(packageManager.installPackages).toHaveBeenCalledWith(
|
|
273
|
+
{ fix: true },
|
|
282
274
|
{ stdio: 'inherit' }
|
|
283
275
|
);
|
|
284
276
|
});
|
|
@@ -349,10 +341,10 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
349
341
|
expectOutput(location(error), message(error, { indent: { label: 1 } }));
|
|
350
342
|
});
|
|
351
343
|
|
|
352
|
-
describe('when location is
|
|
353
|
-
beforeEach(() => (error.location = project().
|
|
344
|
+
describe('when location is lock file', () => {
|
|
345
|
+
beforeEach(() => (error.location = project().packageManager.lockFileName));
|
|
354
346
|
|
|
355
|
-
test('outputs
|
|
347
|
+
test('outputs lock file as location', async () => {
|
|
356
348
|
await subject();
|
|
357
349
|
|
|
358
350
|
expectOutput(chalk.underline(path.relative('.', error.location!)));
|
|
@@ -450,19 +442,19 @@ describe(`[startup] ${Review.name}`, () => {
|
|
|
450
442
|
]);
|
|
451
443
|
};
|
|
452
444
|
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
445
|
+
const installPackagesWithFixOption = [
|
|
446
|
+
packageManager.installPackages,
|
|
447
|
+
{ fix: true },
|
|
456
448
|
{ stdio: 'inherit' },
|
|
457
449
|
];
|
|
458
450
|
|
|
459
451
|
expectCalls(
|
|
460
452
|
...fixesForCategory(FixCategory.isolated),
|
|
461
|
-
|
|
453
|
+
installPackagesWithFixOption,
|
|
462
454
|
...fixesForCategory(FixCategory.normal),
|
|
463
|
-
|
|
455
|
+
installPackagesWithFixOption,
|
|
464
456
|
...fixesForCategory(FixCategory.lockFile),
|
|
465
|
-
|
|
457
|
+
installPackagesWithFixOption
|
|
466
458
|
);
|
|
467
459
|
});
|
|
468
460
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { getPackageManager } from '@servicetitan/install';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
|
-
import { execSync } from 'child_process';
|
|
3
3
|
import terminalLink from 'terminal-link';
|
|
4
4
|
import {
|
|
5
5
|
findPackages,
|
|
@@ -57,13 +57,12 @@ export class Review extends Command<typeof entry> {
|
|
|
57
57
|
throw new Error('this command must be run from the workspace root directory');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
62
|
-
throw new Error(
|
|
60
|
+
const packageManager = getPackageManager();
|
|
61
|
+
if (!packageManager.hasValidLockFile()) {
|
|
62
|
+
throw new Error(`missing or invalid lock file: ${packageManager.lockFileName}`);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
root.location = '.'; // identifies the root package.json for rules that care
|
|
66
|
-
packageLock.location = './package-lock.json';
|
|
67
66
|
|
|
68
67
|
const config = getReviewConfiguration();
|
|
69
68
|
const packages: Package[] = [root, ...findPackages()];
|
|
@@ -71,7 +70,12 @@ export class Review extends Command<typeof entry> {
|
|
|
71
70
|
this.debug('review:packages', packages);
|
|
72
71
|
this.debug('review:dependencies', dependencies);
|
|
73
72
|
|
|
74
|
-
return {
|
|
73
|
+
return {
|
|
74
|
+
config,
|
|
75
|
+
dependencies,
|
|
76
|
+
packageManager,
|
|
77
|
+
packages,
|
|
78
|
+
};
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
private debug(namespace: string, data: any) {
|
|
@@ -110,7 +114,7 @@ export class Review extends Command<typeof entry> {
|
|
|
110
114
|
}
|
|
111
115
|
});
|
|
112
116
|
|
|
113
|
-
|
|
117
|
+
project.packageManager.installPackages({ fix: true }, { stdio: 'inherit' });
|
|
114
118
|
project = this.createProject();
|
|
115
119
|
errors = this.findErrors(project);
|
|
116
120
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { MockPackageManager } from '../../__mocks__';
|
|
1
2
|
import { Project } from '../../types';
|
|
2
3
|
|
|
3
4
|
export function mockProject(project: Partial<Project>): Project {
|
|
4
5
|
return {
|
|
5
6
|
config: {},
|
|
6
7
|
dependencies: {},
|
|
7
|
-
|
|
8
|
+
packageManager: new MockPackageManager() as any,
|
|
8
9
|
packages: [],
|
|
9
10
|
...project,
|
|
10
11
|
};
|