@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,226 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { vol } from 'memfs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
jest.mock('fs', () => require('memfs').fs);
|
|
6
|
+
|
|
7
|
+
const { main } = require('../generate-bin-wrappers');
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Use the real startup directory so path.resolve(__dirname, '..') in the
|
|
11
|
+
* module resolves naturally. The memfs volume mirrors the real directory
|
|
12
|
+
* layout - no path.resolve mock needed.
|
|
13
|
+
*/
|
|
14
|
+
const startupDir = path.resolve(__dirname, '..', '..', '..');
|
|
15
|
+
const binDir = path.join(startupDir, 'bin');
|
|
16
|
+
const nodeModulesDir = path.join(startupDir, 'node_modules');
|
|
17
|
+
const startupPackageJson = path.join(startupDir, 'package.json');
|
|
18
|
+
|
|
19
|
+
describe('[startup] generate-bin-wrappers', () => {
|
|
20
|
+
const defaultBin = { startup: './bin/index.js' };
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vol.reset();
|
|
29
|
+
jest.restoreAllMocks();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe(main.name, () => {
|
|
33
|
+
const pkgScriptName = 'foo';
|
|
34
|
+
const pkgScriptPath = './bin/cli.js';
|
|
35
|
+
const pkg = { name: 'bar', version: '1.0.0', bin: { [pkgScriptName]: pkgScriptPath } };
|
|
36
|
+
const pkgJsonPath = path.join(nodeModulesDir, pkg.name, 'package.json');
|
|
37
|
+
const wrapper = path.join(binDir, `${pkgScriptName}.js`);
|
|
38
|
+
|
|
39
|
+
function volFromJSON(overrides: Record<string, string | Record<string, any>> = {}) {
|
|
40
|
+
vol.fromJSON({
|
|
41
|
+
[startupPackageJson]: JSON.stringify({
|
|
42
|
+
dependencies: { [pkg.name]: pkg.version },
|
|
43
|
+
bin: defaultBin,
|
|
44
|
+
}),
|
|
45
|
+
[pkgJsonPath]: JSON.stringify(pkg),
|
|
46
|
+
...Object.fromEntries(
|
|
47
|
+
Object.entries(overrides).map(([k, v]) => [
|
|
48
|
+
k,
|
|
49
|
+
typeof v === 'string' ? v : JSON.stringify(v),
|
|
50
|
+
])
|
|
51
|
+
),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
volFromJSON();
|
|
57
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const subject = () => main();
|
|
61
|
+
|
|
62
|
+
test('generates executable script', () => {
|
|
63
|
+
subject();
|
|
64
|
+
|
|
65
|
+
expect(fs.statSync(wrapper).mode & 0o755).toBe(0o755);
|
|
66
|
+
expect(fs.readFileSync(wrapper, 'utf8')).toContain(
|
|
67
|
+
`require('./_run')('${pkg.name}', '${pkgScriptPath}')`
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('adds script to package.json bin field', () => {
|
|
72
|
+
subject();
|
|
73
|
+
|
|
74
|
+
expect(JSON.parse(fs.readFileSync(startupPackageJson, 'utf8')).bin).toEqual(
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
[pkgScriptName]: `./bin/${pkgScriptName}.js`,
|
|
77
|
+
...defaultBin,
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('with string-form bin field', () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
volFromJSON({ [pkgJsonPath]: { name: pkg.name, bin: './cli.js' } });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('uses package name as script name', () => {
|
|
88
|
+
subject();
|
|
89
|
+
|
|
90
|
+
expect(fs.existsSync(path.join(binDir, `${pkg.name}.js`))).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('with scoped package', () => {
|
|
94
|
+
const scoped = { name: `@scope/${pkg.name}`, bin: './cli.js' };
|
|
95
|
+
|
|
96
|
+
beforeEach(() => volFromJSON({ [pkgJsonPath]: scoped }));
|
|
97
|
+
|
|
98
|
+
test('omits scope from script name', () => {
|
|
99
|
+
subject();
|
|
100
|
+
|
|
101
|
+
expect(fs.existsSync(path.join(binDir, `${pkg.name}.js`))).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
function itDoesNotGenerateWrapper() {
|
|
107
|
+
test('does not generate wrapper', () => {
|
|
108
|
+
subject();
|
|
109
|
+
|
|
110
|
+
expect(fs.existsSync(wrapper)).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
describe.each(['@servicetitan/install'])('when dependency is %s', name => {
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
volFromJSON({
|
|
117
|
+
[startupPackageJson]: { dependencies: { [name]: '1.0.0' } },
|
|
118
|
+
[path.join(nodeModulesDir, name, 'package.json')]: { ...pkg, name },
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
itDoesNotGenerateWrapper();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('with no dependencies', () => {
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
volFromJSON({ [startupPackageJson]: {} });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('does nothing', () => {
|
|
131
|
+
expect(() => subject()).not.toThrow();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('when dependency has no bin field', () => {
|
|
136
|
+
beforeEach(() => {
|
|
137
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify({ name: pkg.name }));
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
itDoesNotGenerateWrapper();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('when dependency package.json does not exist', () => {
|
|
144
|
+
beforeEach(() => {
|
|
145
|
+
fs.rmSync(path.join(nodeModulesDir, pkg.name), { recursive: true });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
itDoesNotGenerateWrapper();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('when wrapper already exists with same content', () => {
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
subject();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('does not rewrite wrapped', () => {
|
|
157
|
+
const writeFileSpy = jest.spyOn(fs, 'writeFileSync');
|
|
158
|
+
subject();
|
|
159
|
+
|
|
160
|
+
expect(
|
|
161
|
+
writeFileSpy.mock.calls.filter(([path]) => String(path) === wrapper)
|
|
162
|
+
).toHaveLength(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('does not rewrite package.json', () => {
|
|
166
|
+
const writeFileSpy = jest.spyOn(fs, 'writeFileSync');
|
|
167
|
+
subject();
|
|
168
|
+
|
|
169
|
+
expect(
|
|
170
|
+
writeFileSpy.mock.calls.filter(([path]) => String(path) === startupPackageJson)
|
|
171
|
+
).toHaveLength(0);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('when obsolete wrapper exists', () => {
|
|
176
|
+
const obsoleteWrapper = path.join(binDir, 'old-tool.js');
|
|
177
|
+
|
|
178
|
+
beforeEach(() => {
|
|
179
|
+
volFromJSON({
|
|
180
|
+
[obsoleteWrapper]: '// Auto-generated by scripts/generate-bin-wrappers.js\n',
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('removes it', () => {
|
|
185
|
+
subject();
|
|
186
|
+
|
|
187
|
+
expect(fs.existsSync(obsoleteWrapper)).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('when remove fails', () => {
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
jest.spyOn(fs, 'rmSync').mockImplementation(() => {
|
|
193
|
+
throw new Error('Oops!');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('ignores it', () => {
|
|
198
|
+
expect(() => subject()).not.toThrow();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('with real dependency resolved via require.resolve', () => {
|
|
204
|
+
const realPkg = { name: 'patch-package', version: '1.0.0', bin: './index.js' };
|
|
205
|
+
const realPkgJson = require.resolve(path.join(realPkg.name, 'package.json'), {
|
|
206
|
+
paths: [startupDir],
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
beforeEach(() => {
|
|
210
|
+
volFromJSON({
|
|
211
|
+
[startupPackageJson]: { dependencies: { [realPkg.name]: realPkg.version } },
|
|
212
|
+
[realPkgJson]: { name: realPkg.name, bin: realPkg.bin },
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('generates wrapper from resolved package.json', () => {
|
|
217
|
+
subject();
|
|
218
|
+
|
|
219
|
+
const wrapper = path.join(binDir, `${realPkg.name}.js`);
|
|
220
|
+
expect(fs.readFileSync(wrapper, 'utf8')).toContain(
|
|
221
|
+
`require('./_run')('${realPkg.name}', '${realPkg.bin}')`
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates bin wrapper scripts for all CLI tools exposed by startup's dependencies.
|
|
6
|
+
*
|
|
7
|
+
* pnpm uses strict node_modules isolation - only direct dependencies' bins are linked
|
|
8
|
+
* into a consumer's node_modules/.bin/. Since startup acts as a toolbox package, it
|
|
9
|
+
* needs to re-export its dependencies' bins so consumers get them automatically.
|
|
10
|
+
*
|
|
11
|
+
* Under npm these wrappers are harmless but redundant - npm links the original
|
|
12
|
+
* package's bins directly. Under pnpm, startup's wrappers are the only ones
|
|
13
|
+
* visible to consumers.
|
|
14
|
+
*
|
|
15
|
+
* This script:
|
|
16
|
+
* 1. Reads startup's dependencies from package.json
|
|
17
|
+
* 2. Resolves each dependency's `bin` entries
|
|
18
|
+
* 3. Generates thin wrapper scripts in bin/ (using shared bin/_run.js helper)
|
|
19
|
+
* 4. Removes obsolete wrappers from previous runs
|
|
20
|
+
* 5. Updates the `bin` field in startup's package.json
|
|
21
|
+
*
|
|
22
|
+
* Run: npm run prepare -w packages/startup
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
|
|
28
|
+
const GENERATED_MARKER = 'Auto-generated by scripts/generate-bin-wrappers.js';
|
|
29
|
+
|
|
30
|
+
// Packages whose bins should not be exposed to consumers
|
|
31
|
+
const SKIP_PACKAGES = new Set([
|
|
32
|
+
'@servicetitan/install', // consumers use @servicetitan/install directly
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Bins from transitive dependencies that startup needs to expose to consumers
|
|
36
|
+
const EXTRA_BINS = [
|
|
37
|
+
{
|
|
38
|
+
name: 'kendo-ui-license',
|
|
39
|
+
from: '@progress/kendo-licensing',
|
|
40
|
+
binPath: 'bin/kendo-ui-license.js',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* Collect bin entries from each dependency's package.json. Skips packages in
|
|
46
|
+
* SKIP_PACKAGES, normalizes string-form bin fields to { name: path } objects,
|
|
47
|
+
* and appends EXTRA_BINS for transitive dependencies that need explicit exposure.
|
|
48
|
+
*/
|
|
49
|
+
function discoverBins(startupDirectory, dependencies) {
|
|
50
|
+
const bins = [];
|
|
51
|
+
|
|
52
|
+
for (const packageName of dependencies) {
|
|
53
|
+
if (SKIP_PACKAGES.has(packageName)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
let packageJson;
|
|
57
|
+
try {
|
|
58
|
+
const packageJsonPath = require.resolve(packageName + '/package.json', {
|
|
59
|
+
paths: [startupDirectory],
|
|
60
|
+
});
|
|
61
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
62
|
+
} catch {
|
|
63
|
+
// Some packages block ./package.json in exports - read directly from node_modules
|
|
64
|
+
try {
|
|
65
|
+
const packageJsonPath = resolveNodeModulesPackageJson(
|
|
66
|
+
packageName,
|
|
67
|
+
startupDirectory
|
|
68
|
+
);
|
|
69
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
70
|
+
} catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!packageJson.bin) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
* When bin is a string the command name defaults to the package name.
|
|
81
|
+
* Strip the @scope/ prefix for scoped packages, matching npm's behavior.
|
|
82
|
+
*/
|
|
83
|
+
const entries =
|
|
84
|
+
typeof packageJson.bin === 'string'
|
|
85
|
+
? { [packageJson.name.replace(/^@.*\//, '')]: packageJson.bin }
|
|
86
|
+
: packageJson.bin;
|
|
87
|
+
|
|
88
|
+
for (const [name, binPath] of Object.entries(entries)) {
|
|
89
|
+
bins.push({ name, from: packageName, binPath });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
bins.push(...EXTRA_BINS);
|
|
94
|
+
bins.sort((a, b) => a.name.localeCompare(b.name));
|
|
95
|
+
return bins;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* Delete previously generated wrappers that no longer correspond to a
|
|
100
|
+
* dependency. Identifies generated files by the presence of GENERATED_MARKER
|
|
101
|
+
* and removes any that aren't in the current bin list.
|
|
102
|
+
*/
|
|
103
|
+
function removeObsoleteWrappers(binDirectory, bins) {
|
|
104
|
+
const generatedNames = new Set(bins.map(bin => `${bin.name}.js`));
|
|
105
|
+
const removed = [];
|
|
106
|
+
for (const file of fs.readdirSync(binDirectory)) {
|
|
107
|
+
const filePath = path.join(binDirectory, file);
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
110
|
+
if (content.includes(GENERATED_MARKER) && !generatedNames.has(file)) {
|
|
111
|
+
fs.rmSync(filePath);
|
|
112
|
+
removed.push(file);
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return removed;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/*
|
|
122
|
+
* Write a thin wrapper script for each bin entry. Each wrapper delegates to
|
|
123
|
+
* bin/_run.js which locates and executes the real binary. Skips files whose
|
|
124
|
+
* content is already up to date. Returns the bin entries for package.json
|
|
125
|
+
* and the list of wrappers that were added or updated.
|
|
126
|
+
*/
|
|
127
|
+
function generateWrappers(binDirectory, bins) {
|
|
128
|
+
const binEntries = {
|
|
129
|
+
startup: './bin/index.js',
|
|
130
|
+
};
|
|
131
|
+
const added = [];
|
|
132
|
+
|
|
133
|
+
for (const { name, from, binPath } of bins) {
|
|
134
|
+
const wrapperPath = path.join(binDirectory, `${name}.js`);
|
|
135
|
+
const wrapper = `#!/usr/bin/env node
|
|
136
|
+
// ${GENERATED_MARKER} - wraps: ${from} -> ${binPath}
|
|
137
|
+
require('./_run')('${from}', '${binPath}');
|
|
138
|
+
`;
|
|
139
|
+
const existing = fs.existsSync(wrapperPath) && fs.readFileSync(wrapperPath, 'utf8');
|
|
140
|
+
const normalized = existing && existing.replace(/\r\n/g, '\n');
|
|
141
|
+
if (normalized !== wrapper) {
|
|
142
|
+
fs.writeFileSync(wrapperPath, wrapper);
|
|
143
|
+
fs.chmodSync(wrapperPath, 0o755);
|
|
144
|
+
added.push(name);
|
|
145
|
+
}
|
|
146
|
+
binEntries[name] = `./bin/${name}.js`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { binEntries, added };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Walk up node_modules directories to find a dependency's package.json,
|
|
154
|
+
* handling both hoisted and nested layouts.
|
|
155
|
+
*/
|
|
156
|
+
function resolveNodeModulesPackageJson(packageName, fromDirectory) {
|
|
157
|
+
let directory = fromDirectory;
|
|
158
|
+
while (directory !== path.dirname(directory)) {
|
|
159
|
+
const candidate = path.join(directory, 'node_modules', packageName, 'package.json');
|
|
160
|
+
if (fs.existsSync(candidate)) {
|
|
161
|
+
return candidate;
|
|
162
|
+
}
|
|
163
|
+
directory = path.dirname(directory);
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`Cannot find package.json for ${packageName}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function main() {
|
|
169
|
+
const startupDirectory = path.resolve(__dirname, '..', '..');
|
|
170
|
+
const packageJsonPath = path.join(startupDirectory, 'package.json');
|
|
171
|
+
const binDirectory = path.join(startupDirectory, 'bin');
|
|
172
|
+
|
|
173
|
+
console.log('Generating bin wrappers...');
|
|
174
|
+
|
|
175
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
176
|
+
const dependencies = Object.keys(packageJson.dependencies ?? {});
|
|
177
|
+
|
|
178
|
+
const bins = discoverBins(startupDirectory, dependencies);
|
|
179
|
+
const removed = removeObsoleteWrappers(binDirectory, bins);
|
|
180
|
+
const { binEntries, added } = generateWrappers(binDirectory, bins);
|
|
181
|
+
|
|
182
|
+
if (JSON.stringify(packageJson.bin) !== JSON.stringify(binEntries)) {
|
|
183
|
+
packageJson.bin = binEntries;
|
|
184
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4) + '\n');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const name of added) {
|
|
188
|
+
console.log(` added: ${name}`);
|
|
189
|
+
}
|
|
190
|
+
for (const file of removed) {
|
|
191
|
+
console.log(` removed: ${file}`);
|
|
192
|
+
}
|
|
193
|
+
const upToDate = bins.length - added.length;
|
|
194
|
+
console.log(
|
|
195
|
+
` ${added.length} added/updated, ${removed.length} removed, ${upToDate} up to date`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Only run when executed directly (e.g., `node generate-bin-wrappers.js`), not when imported (e.g., in tests).
|
|
200
|
+
/* istanbul ignore next */
|
|
201
|
+
if (require.main === module) {
|
|
202
|
+
main();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { main };
|
|
@@ -49,31 +49,6 @@ describe(`[startup/storybook-config] ${webpackFinal.name}`, () => {
|
|
|
49
49
|
);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
describe('when config has optimization settings', () => {
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
config.optimization = { sideEffects: true, splitChunks: { chunks: 'all' } };
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('when mode is development', () => {
|
|
58
|
-
beforeEach(() => (config.mode = 'development'));
|
|
59
|
-
|
|
60
|
-
test('disables sideEffects to preserve CSS imports from barrel files', () => {
|
|
61
|
-
expect(subject().optimization).toEqual({
|
|
62
|
-
...config.optimization,
|
|
63
|
-
sideEffects: false,
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('when mode is production', () => {
|
|
69
|
-
beforeEach(() => (config.mode = 'production'));
|
|
70
|
-
|
|
71
|
-
test('does not change sideEffects', () => {
|
|
72
|
-
expect(subject().optimization).toEqual(config.optimization);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
52
|
test('adds MiniCssExtractPlugin to config', () => {
|
|
78
53
|
expect(subject().plugins).toEqual([mockMiniCssExtractPlugin]);
|
|
79
54
|
});
|
|
@@ -14,10 +14,6 @@ export function webpackFinal(config: Configuration, overrides: Overrides = {}):
|
|
|
14
14
|
|
|
15
15
|
return {
|
|
16
16
|
...config,
|
|
17
|
-
optimization: {
|
|
18
|
-
...config.optimization,
|
|
19
|
-
...(config.mode !== 'production' ? { sideEffects: false } : {}),
|
|
20
|
-
},
|
|
21
17
|
module: {
|
|
22
18
|
...config.module,
|
|
23
19
|
rules: [...transformDefaultRules(config.module?.rules), ...rules],
|
|
@@ -27,7 +27,9 @@ describe('[startup] Utils', () => {
|
|
|
27
27
|
testEnvironment: 'jsdom',
|
|
28
28
|
testPathIgnorePatterns: ['\\.yalc', ...destinationFolders],
|
|
29
29
|
testRunner: 'jest-circus/runner',
|
|
30
|
-
transformIgnorePatterns: [
|
|
30
|
+
transformIgnorePatterns: [
|
|
31
|
+
'node_modules/(?!.*node_modules/)(?!(@servicetitan|@react-hook|nanoid|axios)/)',
|
|
32
|
+
],
|
|
31
33
|
verbose: true,
|
|
32
34
|
};
|
|
33
35
|
|
|
@@ -190,10 +190,55 @@ describe('[startup] Utils', () => {
|
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
describe('
|
|
194
|
-
|
|
193
|
+
describe('when package.json omits packages', () => {
|
|
194
|
+
const packages = {
|
|
195
|
+
foo: {
|
|
196
|
+
'package.json': JSON.stringify({
|
|
197
|
+
name: 'foo',
|
|
198
|
+
version: '1.0.0',
|
|
199
|
+
}),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
function volFromJSON(json?: Record<string, any>) {
|
|
204
|
+
vol.fromNestedJSON({
|
|
205
|
+
'package.json': JSON.stringify({}),
|
|
206
|
+
packages,
|
|
207
|
+
...json,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
beforeEach(() => volFromJSON());
|
|
195
212
|
|
|
196
213
|
itReturnsEmptyArray();
|
|
214
|
+
|
|
215
|
+
function itReturnsPackages() {
|
|
216
|
+
test('returns packages', () => {
|
|
217
|
+
expect(subject()).toEqual([
|
|
218
|
+
expect.objectContaining({ name: 'foo', version: '1.0.0' }),
|
|
219
|
+
]);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
describe('when pnpm-workspace.yaml contains packages', () => {
|
|
224
|
+
beforeEach(() => {
|
|
225
|
+
volFromJSON({
|
|
226
|
+
'pnpm-workspace.yaml': 'packages:\n - "packages/*"\n',
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
itReturnsPackages();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('with lerna.json contains packages', () => {
|
|
234
|
+
beforeEach(() => {
|
|
235
|
+
volFromJSON({
|
|
236
|
+
'lerna.json': JSON.stringify({ packages: ['packages/*'] }),
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
itReturnsPackages();
|
|
241
|
+
});
|
|
197
242
|
});
|
|
198
243
|
|
|
199
244
|
describe('with no package.json', () => {
|
|
@@ -8,7 +8,7 @@ jest.mock('node:child_process', () => ({
|
|
|
8
8
|
|
|
9
9
|
describe('[startup] Prettify Utils', () => {
|
|
10
10
|
const filename = '/path/to/file.ts';
|
|
11
|
-
const expectedCommand = `
|
|
11
|
+
const expectedCommand = `prettier -w "${filename}"`;
|
|
12
12
|
|
|
13
13
|
beforeEach(() => {
|
|
14
14
|
jest.resetAllMocks();
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
1
2
|
import { globSync } from 'glob';
|
|
3
|
+
import yaml from 'js-yaml';
|
|
2
4
|
import multimatch from 'multimatch';
|
|
3
5
|
import path from 'path';
|
|
4
6
|
import { findUp } from './find-up';
|
|
@@ -44,6 +46,12 @@ function findWorkspaces() {
|
|
|
44
46
|
log.debug('find-packages', `reading: ${packageJson}`);
|
|
45
47
|
let workspaces = readJsonSafe<{ workspaces: string[] }>(packageJson)?.workspaces;
|
|
46
48
|
|
|
49
|
+
if (!workspaces) {
|
|
50
|
+
const pnpmWorkspace = path.resolve(path.join(directory, 'pnpm-workspace.yaml'));
|
|
51
|
+
log.debug('find-packages', `reading: ${pnpmWorkspace}`);
|
|
52
|
+
workspaces = readYamlSafe<{ packages: string[] }>(pnpmWorkspace)?.packages;
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
if (!workspaces) {
|
|
48
56
|
const lernaJson = path.resolve(path.join(directory, 'lerna.json'));
|
|
49
57
|
log.debug('find-packages', `reading: ${lernaJson}`);
|
|
@@ -64,3 +72,11 @@ function findWorkspaces() {
|
|
|
64
72
|
}
|
|
65
73
|
});
|
|
66
74
|
}
|
|
75
|
+
|
|
76
|
+
function readYamlSafe<T>(filePath: string): T | undefined {
|
|
77
|
+
try {
|
|
78
|
+
return yaml.load(fs.readFileSync(filePath, 'utf-8')) as T;
|
|
79
|
+
} catch {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -22,7 +22,10 @@ function getDefaultJestConfiguration() {
|
|
|
22
22
|
testEnvironment: 'jsdom',
|
|
23
23
|
testPathIgnorePatterns: ['\\.yalc', ...getDestinationFolders()],
|
|
24
24
|
testRunner: 'jest-circus/runner',
|
|
25
|
-
|
|
25
|
+
// Under pnpm, dependencies are nested (e.g. node_modules/.pnpm/…/node_modules/pkg); the extra lookahead skips the inner node_modules.
|
|
26
|
+
transformIgnorePatterns: [
|
|
27
|
+
'node_modules/(?!.*node_modules/)(?!(@servicetitan|@react-hook|nanoid|axios)/)',
|
|
28
|
+
],
|
|
26
29
|
verbose: true,
|
|
27
30
|
} as Omit<Config.Argv, 'collectCoverageFrom' | 'moduleNameMapper' | 'setupFiles'> & {
|
|
28
31
|
collectCoverageFrom: string[];
|
package/src/utils/prettify.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getPackageManager } from '@servicetitan/install';
|
|
1
2
|
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { Configuration } from 'webpack';
|
|
@@ -30,8 +31,9 @@ export function cacheConfig(context: Context, _overrides: Overrides): Result {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
function findPackageLock() {
|
|
34
|
+
const { lockFileName } = getPackageManager();
|
|
33
35
|
return findUp(directory => {
|
|
34
|
-
const lockFile = path.resolve(path.join(directory),
|
|
36
|
+
const lockFile = path.resolve(path.join(directory), lockFileName);
|
|
35
37
|
return fs.existsSync(lockFile) ? lockFile : undefined;
|
|
36
38
|
});
|
|
37
39
|
}
|