@servicetitan/startup 37.0.1 → 38.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/cli/commands/review/types.d.ts.map +1 -1
  2. package/dist/cli/commands/review/types.js.map +1 -1
  3. package/dist/cypress/config/vite-config.d.ts.map +1 -1
  4. package/dist/cypress/config/vite-config.js +23 -3
  5. package/dist/cypress/config/vite-config.js.map +1 -1
  6. package/dist/cypress/config/webpack-config.d.ts.map +1 -1
  7. package/dist/cypress/config/webpack-config.js +5 -0
  8. package/dist/cypress/config/webpack-config.js.map +1 -1
  9. package/dist/storybook-config/vite-final.d.ts.map +1 -1
  10. package/dist/storybook-config/vite-final.js +32 -6
  11. package/dist/storybook-config/vite-final.js.map +1 -1
  12. package/dist/storybook-config/webpack-final.js +2 -1
  13. package/dist/storybook-config/webpack-final.js.map +1 -1
  14. package/dist/vite/config/base-config.d.ts.map +1 -1
  15. package/dist/vite/config/base-config.js +2 -0
  16. package/dist/vite/config/base-config.js.map +1 -1
  17. package/dist/vite/config/create-filtering-logger.d.ts.map +1 -1
  18. package/dist/vite/config/create-filtering-logger.js +12 -1
  19. package/dist/vite/config/create-filtering-logger.js.map +1 -1
  20. package/dist/vite/config/create-vite-build-context.d.ts.map +1 -1
  21. package/dist/vite/config/create-vite-build-context.js +4 -1
  22. package/dist/vite/config/create-vite-build-context.js.map +1 -1
  23. package/dist/vite/plugins/dev-server-routing-plugin.d.ts +1 -1
  24. package/dist/vite/plugins/dev-server-routing-plugin.d.ts.map +1 -1
  25. package/dist/vite/plugins/dev-server-routing-plugin.js +3 -2
  26. package/dist/vite/plugins/dev-server-routing-plugin.js.map +1 -1
  27. package/dist/vite/plugins/istanbul-plugin.d.ts.map +1 -1
  28. package/dist/vite/plugins/istanbul-plugin.js +33 -1
  29. package/dist/vite/plugins/istanbul-plugin.js.map +1 -1
  30. package/dist/vite/types.d.ts +4 -1
  31. package/dist/vite/types.d.ts.map +1 -1
  32. package/package.json +12 -12
  33. package/src/cli/commands/__tests__/bundle-vite.test.ts +1 -0
  34. package/src/cli/commands/review/types.ts +1 -3
  35. package/src/cypress/config/__tests__/vite-config.test.ts +21 -11
  36. package/src/cypress/config/__tests__/webpack-config.test.ts +17 -0
  37. package/src/cypress/config/vite-config.ts +18 -3
  38. package/src/cypress/config/webpack-config.ts +2 -0
  39. package/src/storybook-config/__tests__/vite-final.test.ts +65 -11
  40. package/src/storybook-config/__tests__/webpack-final.test.ts +16 -6
  41. package/src/storybook-config/vite-final.ts +23 -2
  42. package/src/storybook-config/webpack-final.ts +4 -2
  43. package/src/vite/config/__tests__/base-config.test.ts +4 -0
  44. package/src/vite/config/__tests__/create-filtering-logger.test.ts +9 -0
  45. package/src/vite/config/__tests__/create-vite-build-context.test.ts +25 -0
  46. package/src/vite/config/base-config.ts +5 -1
  47. package/src/vite/config/create-filtering-logger.ts +13 -2
  48. package/src/vite/config/create-vite-build-context.ts +1 -0
  49. package/src/vite/plugins/__tests__/dev-server-routing-plugin.test.ts +120 -98
  50. package/src/vite/plugins/__tests__/istanbul-plugin.test.ts +42 -1
  51. package/src/vite/plugins/dev-server-routing-plugin.ts +3 -3
  52. package/src/vite/plugins/istanbul-plugin.ts +28 -1
  53. package/src/vite/types.ts +4 -1
@@ -1,10 +1,12 @@
1
1
  import { fs, vol } from 'memfs';
2
2
  import path from 'path';
3
+ import { normalizePath } from 'vite';
3
4
  import { type BuildContext, getBundleType } from '../../../core';
4
5
  import { log } from '../../../utils';
5
6
  import { devServerRoutingPlugin } from '../dev-server-routing-plugin';
6
7
 
7
8
  jest.mock('fs', () => fs);
9
+ jest.mock('vite', () => ({ normalizePath: jest.fn() }));
8
10
  jest.mock('../../../core', () => ({
9
11
  getBundleType: jest.fn(),
10
12
  }));
@@ -16,8 +18,6 @@ describe(devServerRoutingPlugin.name, () => {
16
18
  const SHARED_DEPS_DIR = 'shared';
17
19
  const destination = 'dist/bundle/full';
18
20
  const destDir = path.resolve(destination);
19
- const outDir = path.resolve('dist/bundle/full');
20
- const outDirRelative = path.relative(process.cwd(), outDir);
21
21
 
22
22
  let context: BuildContext;
23
23
 
@@ -25,6 +25,7 @@ describe(devServerRoutingPlugin.name, () => {
25
25
  jest.clearAllMocks();
26
26
  vol.fromJSON({});
27
27
  jest.mocked(getBundleType).mockReturnValue(SHARED_DEPS_DIR);
28
+ jest.mocked(normalizePath).mockImplementation(id => id.split('\\').join('/'));
28
29
  context = {
29
30
  build: { name: 'test-app' },
30
31
  package: { destination },
@@ -42,89 +43,91 @@ describe(devServerRoutingPlugin.name, () => {
42
43
  expect(subject().name).toBe('dev-server-routing');
43
44
  });
44
45
 
45
- const setupMiddleware = (resolvedOutDir: string) => {
46
- const plugin = subject();
47
- (plugin.configResolved as Function).call(plugin, {
48
- build: { outDir: resolvedOutDir },
49
- });
50
-
51
- let middleware!: Function;
52
- const server: any = {
53
- middlewares: {
54
- use: (handler: Function) => (middleware = handler),
55
- },
56
- };
57
- (plugin.configureServer as Function).call(plugin, server);
58
- return middleware;
59
- };
46
+ describe('configureServer', () => {
47
+ let req: any;
48
+ let next: jest.Mock;
49
+ let middleware: Function;
60
50
 
61
- describe('configResolved', () => {
62
- test('captures outDir for use by configureServer', () => {
63
- const middleware = setupMiddleware(outDir);
64
- const req: any = { url: `/${SHARED_DEPS_DIR}/dep.js` };
65
- const originalUrl = req.url;
51
+ const setupMiddleware = () => {
52
+ const plugin = devServerRoutingPlugin(context);
53
+ (plugin.configResolved as Function).call(plugin, { build: { outDir: destDir } });
66
54
 
67
- middleware(req, {}, jest.fn());
55
+ const server: any = {
56
+ middlewares: { use: (handler: Function) => (middleware = handler) },
57
+ };
58
+ (plugin.configureServer as Function).call(plugin, server);
59
+ };
68
60
 
69
- expect(req.url).toBe(`/${outDirRelative}/${originalUrl.slice(1)}`);
61
+ beforeEach(() => {
62
+ req = { method: 'GET', url: '/anything' };
63
+ next = jest.fn();
64
+ setupMiddleware();
70
65
  });
71
- });
72
-
73
- describe('configureServer', () => {
74
- const itPreservesUnmatchedUrls = () => {
75
- test('preserves unmatched URL', () => {
76
- const middleware = setupMiddleware(outDir);
77
- const req: any = { url: '/other/path' };
78
- const originalUrl = req.url;
79
-
80
- middleware(req, {}, jest.fn());
81
66
 
82
- expect(req.url).toBe(originalUrl);
83
- });
84
- };
67
+ const subject = () => middleware(req, {}, next);
85
68
 
86
69
  test('logs request', () => {
87
- const middleware = setupMiddleware(outDir);
88
-
89
- middleware({ method: 'GET', url: '/anything' }, {}, jest.fn());
70
+ subject();
90
71
 
91
72
  expect(log.info).toHaveBeenCalled();
92
73
  });
93
74
 
94
75
  test('calls next middleware', () => {
95
- const middleware = setupMiddleware(outDir);
96
- const next = jest.fn();
97
-
98
- middleware({ url: '/anything' }, {}, next);
76
+ subject();
99
77
 
100
78
  expect(next).toHaveBeenCalled();
101
79
  });
102
80
 
81
+ const itPreservesUnmatchedUrls = () => {
82
+ describe('with an unmatched URL', () => {
83
+ let originalUrl: string;
84
+
85
+ beforeEach(() => {
86
+ req.url = '/other/path';
87
+ originalUrl = req.url;
88
+ });
89
+
90
+ test('preserves the URL', () => {
91
+ subject();
92
+
93
+ expect(req.url).toBe(originalUrl);
94
+ });
95
+ });
96
+ };
97
+
103
98
  describe('when package is a web component', () => {
104
- beforeEach(() => (context.package.isWebComponent = true));
99
+ beforeEach(() => {
100
+ context.package.isWebComponent = true;
101
+ setupMiddleware();
102
+ });
105
103
 
106
- test('strips outDir prefix from URL', () => {
107
- const middleware = setupMiddleware(outDir);
108
- const req: any = { url: `/${outDirRelative}/index.js` };
104
+ describe('with a URL under outDir', () => {
105
+ beforeEach(() => (req.url = `/${destination}/index.js`));
109
106
 
110
- middleware(req, {}, jest.fn());
107
+ test('strips outDir prefix from URL', () => {
108
+ subject();
111
109
 
112
- expect(req.url).toBe('/index.js');
110
+ expect(req.url).toBe('/index.js');
111
+ });
113
112
  });
114
113
 
115
114
  itPreservesUnmatchedUrls();
116
115
  });
117
116
 
118
117
  describe('when package is not a web component', () => {
119
- beforeEach(() => (context.package.isWebComponent = false));
118
+ beforeEach(() => {
119
+ context.package.isWebComponent = false;
120
+ setupMiddleware();
121
+ });
120
122
 
121
- test('rewrites shared dependencies URL to outDir', () => {
122
- const middleware = setupMiddleware(outDir);
123
- const req: any = { url: `/${SHARED_DEPS_DIR}/dep.js` };
123
+ describe('with a shared dependency URL', () => {
124
+ beforeEach(() => (req.url = `/${SHARED_DEPS_DIR}/dep.js`));
124
125
 
125
- middleware(req, {}, jest.fn());
126
+ test('rewrites URL to outDir', () => {
127
+ subject();
126
128
 
127
- expect(req.url).toBe(`/${outDirRelative}/${SHARED_DEPS_DIR}/dep.js`);
129
+ expect(req.url).toBe(`/${destination}/${SHARED_DEPS_DIR}/dep.js`);
130
+ });
128
131
  });
129
132
 
130
133
  itPreservesUnmatchedUrls();
@@ -132,8 +135,8 @@ describe(devServerRoutingPlugin.name, () => {
132
135
  });
133
136
 
134
137
  describe('configurePreviewServer', () => {
135
- const setupPreviewServer = () => {
136
- const plugin = subject();
138
+ const registerPreviewServer = () => {
139
+ const plugin = devServerRoutingPlugin(context);
137
140
  const uses: { path?: string; handler: Function }[] = [];
138
141
  const server: any = {
139
142
  middlewares: {
@@ -153,39 +156,66 @@ describe(devServerRoutingPlugin.name, () => {
153
156
  };
154
157
  };
155
158
 
156
- test('logs request and calls next middleware', () => {
157
- const { logger } = setupPreviewServer();
158
- const req = { method: 'GET', url: '/index.html' };
159
- const next = jest.fn();
159
+ describe('logging middleware', () => {
160
+ let req: any;
161
+ let next: jest.Mock;
162
+ let logger: Function;
160
163
 
161
- logger(req, {}, next);
164
+ beforeEach(() => {
165
+ ({ logger } = registerPreviewServer());
166
+ req = { method: 'GET', url: '/index.html' };
167
+ next = jest.fn();
168
+ });
162
169
 
163
- expect(log.info).toHaveBeenCalled();
164
- expect(next).toHaveBeenCalled();
170
+ const subject = () => logger(req, {}, next);
171
+
172
+ test('logs request', () => {
173
+ subject();
174
+
175
+ expect(log.info).toHaveBeenCalled();
176
+ });
177
+
178
+ test('calls next middleware', () => {
179
+ subject();
180
+
181
+ expect(next).toHaveBeenCalled();
182
+ });
165
183
  });
166
184
 
167
185
  describe('when package is not a web component', () => {
168
- beforeEach(() => (context.package.isWebComponent = false));
186
+ let staticFiles: Function | undefined;
169
187
 
170
- test('only registers logging middleware', () => {
171
- const { logger, staticFiles } = setupPreviewServer();
188
+ beforeEach(() => {
189
+ context.package.isWebComponent = false;
190
+ ({ staticFiles } = registerPreviewServer());
191
+ });
172
192
 
173
- expect(logger).toBeDefined();
193
+ test('does not register a static file middleware', () => {
174
194
  expect(staticFiles).toBeUndefined();
175
195
  });
176
196
  });
177
197
 
178
198
  describe('when package is a web component', () => {
179
- beforeEach(() => (context.package.isWebComponent = true));
180
-
181
- const serveStaticFile = (fileName: string) => {
182
- const mockStream = { pipe: jest.fn() };
183
- jest.spyOn(fs, 'createReadStream').mockReturnValue(mockStream as any);
184
- const { staticFiles } = setupPreviewServer();
185
- const res = { setHeader: jest.fn() };
186
- staticFiles!({ url: `/${fileName}` }, res, jest.fn());
187
- return { res, mockStream };
188
- };
199
+ let req: any;
200
+ let res: any;
201
+ let next: jest.Mock;
202
+ let mockStream: { pipe: jest.Mock };
203
+ let createReadStreamSpy: jest.SpyInstance;
204
+ let staticFiles: Function | undefined;
205
+
206
+ beforeEach(() => {
207
+ context.package.isWebComponent = true;
208
+ mockStream = { pipe: jest.fn() };
209
+ createReadStreamSpy = jest
210
+ .spyOn(fs, 'createReadStream')
211
+ .mockReturnValue(mockStream as any);
212
+ ({ staticFiles } = registerPreviewServer());
213
+ req = { url: '/file.js' };
214
+ res = { setHeader: jest.fn() };
215
+ next = jest.fn();
216
+ });
217
+
218
+ const subject = () => staticFiles!(req, res, next);
189
219
 
190
220
  describe.each([
191
221
  ['file.js', 'application/javascript'],
@@ -193,11 +223,12 @@ describe(devServerRoutingPlugin.name, () => {
193
223
  ['file.json', 'application/json'],
194
224
  ])('when requested file is %s', (fileName, contentType) => {
195
225
  beforeEach(() => {
226
+ req.url = `/${fileName}`;
196
227
  vol.fromJSON({ [path.join(destDir, fileName)]: 'content' });
197
228
  });
198
229
 
199
230
  test(`serves file as ${contentType}`, () => {
200
- const { res, mockStream } = serveStaticFile(fileName);
231
+ subject();
201
232
 
202
233
  expect(res.setHeader).toHaveBeenCalledWith('Content-Type', contentType);
203
234
  expect(mockStream.pipe).toHaveBeenCalledWith(res);
@@ -205,14 +236,13 @@ describe(devServerRoutingPlugin.name, () => {
205
236
  });
206
237
 
207
238
  describe('when requested file has unrecognized extension', () => {
208
- const fileName = 'file.txt';
209
-
210
239
  beforeEach(() => {
211
- vol.fromJSON({ [path.join(destDir, fileName)]: 'content' });
240
+ req.url = '/file.txt';
241
+ vol.fromJSON({ [path.join(destDir, 'file.txt')]: 'content' });
212
242
  });
213
243
 
214
244
  test('serves file without content type', () => {
215
- const { res, mockStream } = serveStaticFile(fileName);
245
+ subject();
216
246
 
217
247
  expect(res.setHeader).not.toHaveBeenCalled();
218
248
  expect(mockStream.pipe).toHaveBeenCalledWith(res);
@@ -220,13 +250,10 @@ describe(devServerRoutingPlugin.name, () => {
220
250
  });
221
251
 
222
252
  describe('when requested file does not exist', () => {
223
- beforeEach(() => vol.reset());
253
+ beforeEach(() => (req.url = '/missing.js'));
224
254
 
225
255
  test('calls next middleware', () => {
226
- const { staticFiles } = setupPreviewServer();
227
- const next = jest.fn();
228
-
229
- staticFiles!({ url: '/missing.js' }, {}, next);
256
+ subject();
230
257
 
231
258
  expect(next).toHaveBeenCalled();
232
259
  });
@@ -234,20 +261,15 @@ describe(devServerRoutingPlugin.name, () => {
234
261
 
235
262
  describe('when requested URL traverses outside the mounted directory', () => {
236
263
  beforeEach(() => {
237
- vol.fromJSON({
238
- [path.resolve(destDir, '../outside.js')]: 'content',
239
- });
264
+ req.url = '/../outside.js';
265
+ vol.fromJSON({ [path.resolve(destDir, '../outside.js')]: 'content' });
240
266
  });
241
267
 
242
268
  test('does not serve the file', () => {
243
- const createReadStream = jest.spyOn(fs, 'createReadStream');
244
- const { staticFiles } = setupPreviewServer();
245
- const next = jest.fn();
246
-
247
- staticFiles!({ url: '/../outside.js' }, {}, next);
269
+ subject();
248
270
 
249
271
  expect(next).toHaveBeenCalled();
250
- expect(createReadStream).not.toHaveBeenCalled();
272
+ expect(createReadStreamSpy).not.toHaveBeenCalled();
251
273
  });
252
274
  });
253
275
  });
@@ -11,7 +11,12 @@ describe(istanbulPlugin.name, () => {
11
11
  beforeEach(() => {
12
12
  jest.clearAllMocks();
13
13
  jest.mocked(importEsm).mockResolvedValue({ default: mockIstanbul });
14
- context = { options: {} } as ViteBuildContext;
14
+ context = {
15
+ options: {},
16
+ build: {},
17
+ package: { destination: 'dist' },
18
+ bundlerConfig: {},
19
+ } as ViteBuildContext;
15
20
  });
16
21
 
17
22
  const subject = () => istanbulPlugin(context);
@@ -58,5 +63,41 @@ describe(istanbulPlugin.name, () => {
58
63
  expect.objectContaining({ exclude: ['**/node_modules/**', '**/.yalc/**'] })
59
64
  );
60
65
  });
66
+
67
+ describe('when the package exposes shared dependencies', () => {
68
+ beforeEach(() => {
69
+ context.package.isExposeSharedDependencies = true;
70
+ });
71
+
72
+ test('also excludes the shared dependencies', async () => {
73
+ await subject();
74
+
75
+ expect(mockIstanbul).toHaveBeenCalledWith(
76
+ expect.objectContaining({
77
+ exclude: [
78
+ '**/node_modules/**',
79
+ '**/.yalc/**',
80
+ `${context.package.destination}/bundle/shared/**`,
81
+ ],
82
+ })
83
+ );
84
+ });
85
+
86
+ describe('when package has custom outDir', () => {
87
+ const outDir = 'foo';
88
+
89
+ beforeEach(() => (context.bundlerConfig.outDir = outDir));
90
+
91
+ test('excludes custom shared dependencies location', async () => {
92
+ await subject();
93
+
94
+ expect(mockIstanbul).toHaveBeenCalledWith(
95
+ expect.objectContaining({
96
+ exclude: ['**/node_modules/**', '**/.yalc/**', `${outDir}/shared/**`],
97
+ })
98
+ );
99
+ });
100
+ });
101
+ });
61
102
  });
62
103
  });
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import type { Plugin } from 'vite';
3
+ import { normalizePath, type Plugin } from 'vite';
4
4
  import { getBundleType, type BuildContext } from '../../core';
5
5
  import { log } from '../../utils';
6
6
 
@@ -45,7 +45,7 @@ export function devServerRoutingPlugin(context: BuildContext): Plugin {
45
45
  },
46
46
 
47
47
  configureServer(server) {
48
- const outDirRelative = `/${path.relative(process.cwd(), outDir)}/`;
48
+ const outDirRelative = `/${normalizePath(path.relative(process.cwd(), outDir))}/`;
49
49
 
50
50
  server.middlewares.use((req, _res, next) => {
51
51
  logRequest(req);
@@ -72,7 +72,7 @@ export function devServerRoutingPlugin(context: BuildContext): Plugin {
72
72
  }
73
73
 
74
74
  const destDir = path.resolve(destination);
75
- const mountPath = '/' + path.relative(process.cwd(), destDir);
75
+ const mountPath = '/' + normalizePath(path.relative(process.cwd(), destDir));
76
76
 
77
77
  const contentTypes: Record<string, string> = {
78
78
  '.js': 'application/javascript',
@@ -1,4 +1,6 @@
1
+ import path from 'path';
1
2
  import type { Plugin } from 'vite';
3
+ import { resolveOutputPath } from '../../core';
2
4
  import { ViteBuildContext } from '../types';
3
5
  import { importEsm } from './utils';
4
6
 
@@ -11,9 +13,34 @@ export function istanbulPlugin(context: ViteBuildContext): Promise<Plugin> | und
11
13
  return importEsm<typeof import('vite-plugin-istanbul')>('vite-plugin-istanbul').then(
12
14
  ({ default: istanbul }) =>
13
15
  istanbul({
14
- exclude: ['**/node_modules/**', '**/.yalc/**'],
16
+ exclude: [
17
+ '**/node_modules/**',
18
+ '**/.yalc/**',
19
+ ...sharedDependenciesExclude(context),
20
+ ],
15
21
  requireEnv: false,
16
22
  forceBuildInstrument: true,
17
23
  })
18
24
  );
19
25
  }
26
+
27
+ /*
28
+ * devServerRoutingPlugin serves the prebuilt shared-dependencies bundle (react, mobx, the
29
+ * design system, etc.) straight from disk at dev time. Instrumenting that vendor code breaks
30
+ * Vite's import analysis on the huge instrumented output and provides no useful coverage.
31
+ *
32
+ * vite-plugin-istanbul matches exclude patterns against paths relative to process.cwd()
33
+ * (via test-exclude's default `relativePath` behavior), so the pattern must be cwd-relative
34
+ * rather than absolute.
35
+ */
36
+ function sharedDependenciesExclude(context: ViteBuildContext): string[] {
37
+ if (!context.package.isExposeSharedDependencies) {
38
+ return [];
39
+ }
40
+ const sharedContext = {
41
+ ...context,
42
+ build: { ...context.build, emitExposedDependencies: true },
43
+ };
44
+ const outputPath = resolveOutputPath(sharedContext, context.bundlerConfig.outDir);
45
+ return [path.join(path.relative(process.cwd(), outputPath), '**').replace(/\\/g, '/')];
46
+ }
package/src/vite/types.ts CHANGED
@@ -2,5 +2,8 @@ import { BuildContext } from '../core';
2
2
  import { ViteBundlerConfig } from '../utils';
3
3
 
4
4
  export type ViteBuildContext = BuildContext<
5
- Required<Omit<ViteBundlerConfig, 'port'>> & Pick<ViteBundlerConfig, 'port'>
5
+ (Required<Omit<ViteBundlerConfig, 'port'>> & Pick<ViteBundlerConfig, 'port'>) & {
6
+ /** The resolved `build.outDir` override, if the package configures one. */
7
+ outDir?: string;
8
+ }
6
9
  >;