@servicetitan/startup 31.0.0 → 31.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/build.js +1 -7
  3. package/dist/cli/commands/build.js.map +1 -1
  4. package/dist/cli/commands/prepare-package.d.ts +1 -2
  5. package/dist/cli/commands/prepare-package.d.ts.map +1 -1
  6. package/dist/cli/commands/prepare-package.js +4 -6
  7. package/dist/cli/commands/prepare-package.js.map +1 -1
  8. package/dist/cli/commands/run-task.test.js +59 -0
  9. package/dist/cli/commands/run-task.test.js.map +1 -0
  10. package/dist/cli/commands/start.d.ts.map +1 -1
  11. package/dist/cli/commands/start.js +2 -11
  12. package/dist/cli/commands/start.js.map +1 -1
  13. package/dist/cli/tasks/swc-cli.d.js +3 -0
  14. package/dist/cli/tasks/swc-cli.d.js.map +1 -0
  15. package/dist/cli/tasks/swc-compile-package.d.ts.map +1 -1
  16. package/dist/cli/tasks/swc-compile-package.js +22 -19
  17. package/dist/cli/tasks/swc-compile-package.js.map +1 -1
  18. package/dist/cli/types/cpx2.d.js +3 -0
  19. package/dist/cli/types/cpx2.d.js.map +1 -0
  20. package/dist/cli/utils/bundle.d.ts +2 -2
  21. package/dist/cli/utils/bundle.d.ts.map +1 -1
  22. package/dist/cli/utils/bundle.js +18 -4
  23. package/dist/cli/utils/bundle.js.map +1 -1
  24. package/dist/cli/utils/copy-files.d.ts +1 -1
  25. package/dist/cli/utils/copy-files.d.ts.map +1 -1
  26. package/dist/cli/utils/copy-files.js +18 -11
  27. package/dist/cli/utils/copy-files.js.map +1 -1
  28. package/dist/cli/utils/get-module-type.d.ts.map +1 -1
  29. package/dist/cli/utils/get-module-type.js +2 -16
  30. package/dist/cli/utils/get-module-type.js.map +1 -1
  31. package/dist/cli/utils/index.d.ts +1 -1
  32. package/dist/cli/utils/index.d.ts.map +1 -1
  33. package/dist/cli/utils/index.js +1 -1
  34. package/dist/cli/utils/index.js.map +1 -1
  35. package/dist/cli/utils/ts-config.d.ts +11 -0
  36. package/dist/cli/utils/ts-config.d.ts.map +1 -0
  37. package/dist/cli/utils/ts-config.js +80 -0
  38. package/dist/cli/utils/ts-config.js.map +1 -0
  39. package/dist/utils/get-configuration.d.ts +1 -0
  40. package/dist/utils/get-configuration.d.ts.map +1 -1
  41. package/dist/utils/get-configuration.js +14 -0
  42. package/dist/utils/get-configuration.js.map +1 -1
  43. package/dist/webpack/configs/index.d.ts +0 -1
  44. package/dist/webpack/configs/index.d.ts.map +1 -1
  45. package/dist/webpack/configs/index.js +0 -1
  46. package/dist/webpack/configs/index.js.map +1 -1
  47. package/dist/webpack/configs/optimization-config.js +6 -6
  48. package/dist/webpack/configs/optimization-config.js.map +1 -1
  49. package/dist/webpack/configs/output-config.d.ts.map +1 -1
  50. package/dist/webpack/configs/output-config.js +3 -2
  51. package/dist/webpack/configs/output-config.js.map +1 -1
  52. package/dist/webpack/configs/plugins/html-plugin.d.ts +1 -1
  53. package/dist/webpack/configs/plugins/html-plugin.d.ts.map +1 -1
  54. package/dist/webpack/configs/plugins/html-plugin.js +2 -2
  55. package/dist/webpack/configs/plugins/html-plugin.js.map +1 -1
  56. package/dist/webpack/configs/plugins/virtual-modules-plugin.js +14 -4
  57. package/dist/webpack/configs/plugins/virtual-modules-plugin.js.map +1 -1
  58. package/dist/webpack/configs/utils/generate-metadata.d.ts.map +1 -1
  59. package/dist/webpack/configs/utils/generate-metadata.js +4 -0
  60. package/dist/webpack/configs/utils/generate-metadata.js.map +1 -1
  61. package/dist/webpack/create-webpack-config.d.ts.map +1 -1
  62. package/dist/webpack/create-webpack-config.js +0 -1
  63. package/dist/webpack/create-webpack-config.js.map +1 -1
  64. package/dist/webpack/types.d.ts +1 -0
  65. package/dist/webpack/types.d.ts.map +1 -1
  66. package/dist/webpack/utils/index.d.ts +0 -1
  67. package/dist/webpack/utils/index.d.ts.map +1 -1
  68. package/dist/webpack/utils/index.js +0 -1
  69. package/dist/webpack/utils/index.js.map +1 -1
  70. package/package.json +7 -8
  71. package/src/cli/commands/__tests__/build.test.ts +2 -4
  72. package/src/cli/commands/__tests__/prepare-package.test.ts +5 -28
  73. package/src/cli/commands/__tests__/start.test.ts +3 -5
  74. package/src/cli/commands/build.ts +0 -2
  75. package/src/cli/commands/prepare-package.ts +4 -7
  76. package/src/cli/commands/start.ts +1 -3
  77. package/src/cli/tasks/__tests__/swc-compile-package.test.ts +71 -12
  78. package/src/cli/tasks/swc-compile-package.ts +21 -20
  79. package/src/cli/utils/__tests__/bundle.test.ts +48 -7
  80. package/src/cli/utils/__tests__/copy-files.test.ts +5 -5
  81. package/src/cli/utils/bundle.ts +27 -5
  82. package/src/cli/utils/copy-files.ts +16 -6
  83. package/src/cli/utils/get-module-type.ts +2 -18
  84. package/src/cli/utils/index.ts +1 -1
  85. package/src/cli/utils/ts-config.ts +64 -0
  86. package/src/utils/__tests__/get-configuration.test.ts +20 -0
  87. package/src/utils/get-configuration.ts +12 -0
  88. package/src/webpack/__tests__/create-webpack-config-shared-dependencies.test.ts +0 -1
  89. package/src/webpack/__tests__/create-webpack-config-web-component.test.ts +17 -28
  90. package/src/webpack/__tests__/create-webpack-config.test.ts +27 -38
  91. package/src/webpack/configs/index.ts +0 -1
  92. package/src/webpack/configs/optimization-config.ts +6 -6
  93. package/src/webpack/configs/output-config.ts +4 -2
  94. package/src/webpack/configs/plugins/html-plugin.ts +5 -2
  95. package/src/webpack/configs/plugins/virtual-modules-plugin.ts +15 -2
  96. package/src/webpack/configs/utils/__tests__/generate-metadata.test.ts +3 -1
  97. package/src/webpack/configs/utils/generate-metadata.ts +6 -1
  98. package/src/webpack/create-webpack-config.ts +0 -2
  99. package/src/webpack/types.ts +1 -0
  100. package/src/webpack/utils/index.ts +0 -1
  101. package/dist/__mocks__/create-package.d.ts +0 -3
  102. package/dist/__mocks__/create-package.d.ts.map +0 -1
  103. package/dist/__mocks__/index.d.ts +0 -2
  104. package/dist/__mocks__/index.d.ts.map +0 -1
  105. package/dist/cli/commands/review/__mocks__/expect-calls.d.ts +0 -23
  106. package/dist/cli/commands/review/__mocks__/expect-calls.d.ts.map +0 -1
  107. package/dist/cli/commands/review/__mocks__/index.d.ts +0 -2
  108. package/dist/cli/commands/review/__mocks__/index.d.ts.map +0 -1
  109. package/dist/cli/commands/review/rules/__mocks__/index.d.ts +0 -4
  110. package/dist/cli/commands/review/rules/__mocks__/index.d.ts.map +0 -1
  111. package/dist/cli/commands/review/rules/__mocks__/mock-config.d.ts +0 -7
  112. package/dist/cli/commands/review/rules/__mocks__/mock-config.d.ts.map +0 -1
  113. package/dist/cli/commands/review/rules/__mocks__/mock-packages.d.ts +0 -21
  114. package/dist/cli/commands/review/rules/__mocks__/mock-packages.d.ts.map +0 -1
  115. package/dist/cli/commands/review/rules/__mocks__/mock-project.d.ts +0 -3
  116. package/dist/cli/commands/review/rules/__mocks__/mock-project.d.ts.map +0 -1
  117. package/dist/cli/utils/style-extensions.d.ts +0 -2
  118. package/dist/cli/utils/style-extensions.d.ts.map +0 -1
  119. package/dist/cli/utils/style-extensions.js +0 -17
  120. package/dist/cli/utils/style-extensions.js.map +0 -1
  121. package/dist/cli/utils/tcm.d.ts +0 -6
  122. package/dist/cli/utils/tcm.d.ts.map +0 -1
  123. package/dist/cli/utils/tcm.js +0 -72
  124. package/dist/cli/utils/tcm.js.map +0 -1
  125. package/dist/webpack/__mocks__/file-rules.d.ts +0 -3
  126. package/dist/webpack/__mocks__/file-rules.d.ts.map +0 -1
  127. package/dist/webpack/__mocks__/index.d.ts +0 -3
  128. package/dist/webpack/__mocks__/index.d.ts.map +0 -1
  129. package/dist/webpack/__mocks__/style-rules.d.ts +0 -8
  130. package/dist/webpack/__mocks__/style-rules.d.ts.map +0 -1
  131. package/dist/webpack/configs/cache-config.d.ts +0 -6
  132. package/dist/webpack/configs/cache-config.d.ts.map +0 -1
  133. package/dist/webpack/configs/cache-config.js +0 -34
  134. package/dist/webpack/configs/cache-config.js.map +0 -1
  135. package/dist/webpack/utils/feature-cohort.d.ts +0 -5
  136. package/dist/webpack/utils/feature-cohort.d.ts.map +0 -1
  137. package/dist/webpack/utils/feature-cohort.js +0 -26
  138. package/dist/webpack/utils/feature-cohort.js.map +0 -1
  139. package/dist/webpack/utils/hash-mod.d.ts +0 -9
  140. package/dist/webpack/utils/hash-mod.d.ts.map +0 -1
  141. package/dist/webpack/utils/hash-mod.js +0 -39
  142. package/dist/webpack/utils/hash-mod.js.map +0 -1
  143. package/src/cli/utils/__tests__/tcm.test.ts +0 -195
  144. package/src/cli/utils/style-extensions.ts +0 -1
  145. package/src/cli/utils/tcm.ts +0 -66
  146. package/src/webpack/configs/cache-config.ts +0 -25
  147. package/src/webpack/utils/feature-cohort.ts +0 -19
  148. package/src/webpack/utils/hash-mod.ts +0 -32
@@ -7,6 +7,7 @@ import {
7
7
  getConfiguration,
8
8
  getFolders,
9
9
  getPackageData,
10
+ hasHeadlessBundle,
10
11
  loadSharedDependencies,
11
12
  } from '../../../utils';
12
13
  import { createWebpackConfig } from '../../../webpack';
@@ -27,6 +28,7 @@ jest.mock('../../../utils', () => ({
27
28
  ...jest.requireActual('../../../utils'),
28
29
  getFolders: jest.fn(),
29
30
  getPackageData: jest.fn(),
31
+ hasHeadlessBundle: jest.fn(),
30
32
  loadSharedDependencies: jest.fn(),
31
33
  log: { info: jest.fn() }, // suppress test output
32
34
  }));
@@ -174,6 +176,8 @@ describe('[startup] Cli Utils', () => {
174
176
  const destination = 'dist';
175
177
  const dependencies = { foo: '1.0.1' };
176
178
  const sharedDependencies = { react: 'SharedDependencies.React' };
179
+ let defaultCreateOverrides: Parameters<typeof createWebpackConfig>[0];
180
+ let defaultCreateOptions: Parameters<typeof createWebpackConfig>[1];
177
181
 
178
182
  beforeEach(() => {
179
183
  vol.fromJSON(webComponentFS());
@@ -184,22 +188,42 @@ describe('[startup] Cli Utils', () => {
184
188
  dependencies,
185
189
  });
186
190
  jest.mocked(loadSharedDependencies).mockReturnValue(sharedDependencies);
191
+ defaultCreateOverrides = { configuration: { mode: 'production' } };
192
+ defaultCreateOptions = {
193
+ buildStat: options?.buildStat,
194
+ name: expectPackageName(),
195
+ };
187
196
  });
188
197
 
189
198
  test('builds full and "embed" packages', async () => {
190
- const createOptions = { buildStat: options?.buildStat, name: expectPackageName() };
191
- const overrides = { configuration: { mode: 'production' } };
192
-
193
199
  await subject();
194
200
 
195
- expect(createWebpackConfig).toHaveBeenCalledWith(overrides, createOptions);
196
- expect(createWebpackConfig).toHaveBeenCalledWith(overrides, {
197
- ...createOptions,
201
+ expect(createWebpackConfig).toHaveBeenCalledWith(
202
+ defaultCreateOverrides,
203
+ defaultCreateOptions
204
+ );
205
+ expect(createWebpackConfig).toHaveBeenCalledWith(defaultCreateOverrides, {
206
+ ...defaultCreateOptions,
198
207
  embed: true,
199
208
  });
200
209
  expect(compiler.run).toHaveBeenCalledTimes(2);
201
210
  });
202
211
 
212
+ describe('when headless file exists', () => {
213
+ beforeEach(() => {
214
+ jest.mocked(hasHeadlessBundle).mockReturnValue(true);
215
+ });
216
+
217
+ test('builds headless package', async () => {
218
+ await subject();
219
+
220
+ expect(createWebpackConfig).toHaveBeenCalledWith(defaultCreateOverrides, {
221
+ ...defaultCreateOptions,
222
+ headless: true,
223
+ });
224
+ });
225
+ });
226
+
203
227
  function itUsesConfig(params: { config: () => Record<string, any>; name: string }) {
204
228
  test(`uses ${params.name}`, async () => {
205
229
  const { configuration, plugins } = params.config();
@@ -384,13 +408,30 @@ describe('[startup] Cli Utils', () => {
384
408
  });
385
409
  expect(webpack).toHaveBeenNthCalledWith(1, createWebpackResult);
386
410
  expect(webpack).toHaveBeenNthCalledWith(2, createWebpackResult);
387
- expect(compiler.watch).toHaveBeenCalled();
411
+ expect(compiler.watch).toHaveBeenCalledTimes(1);
388
412
  expect(WebpackDevServer).toHaveBeenCalledWith(
389
413
  { host: 'localhost', port: 8080 },
390
414
  compiler
391
415
  );
392
416
  });
393
417
 
418
+ describe('when headless file exists', () => {
419
+ beforeEach(() => {
420
+ jest.mocked(hasHeadlessBundle).mockReturnValue(true);
421
+ });
422
+
423
+ test('runs watch on headless bundle', async () => {
424
+ await subject();
425
+
426
+ expect(createWebpackConfig).toHaveBeenCalledWith(
427
+ expect.any(Object),
428
+ expect.objectContaining({ headless: true })
429
+ );
430
+ expect(webpack).toHaveBeenNthCalledWith(3, createWebpackResult);
431
+ expect(compiler.watch).toHaveBeenCalledTimes(2);
432
+ });
433
+ });
434
+
394
435
  describe('when webpack.devServer is set to false in package.json', () => {
395
436
  beforeEach(() => {
396
437
  vol.fromJSON(webComponentFS({ webpack: { devServer: false } } as any));
@@ -6,10 +6,10 @@ import fs from 'fs';
6
6
  import { getFolders, log } from '../../../utils';
7
7
  import * as compileSassModule from '../compile-sass';
8
8
 
9
- import { assetExtensions, copyFiles, styleExtensions } from '../copy-files';
9
+ import { copyFiles, styleExtensions, supportedExtensions } from '../copy-files';
10
10
 
11
11
  jest.mock('cpx2', () => ({
12
- copy: jest.fn(),
12
+ copySync: jest.fn(),
13
13
  watch: jest.fn(),
14
14
  }));
15
15
  jest.mock('../../../utils', () => ({
@@ -27,7 +27,7 @@ jest.mock('../../../utils', () => ({
27
27
  describe(`[startup] cli utils (${copyFiles.name})`, () => {
28
28
  const source = fs.mkdtempSync('src');
29
29
  const destination = 'dist';
30
- const files = `${source}/**/*.{${[...assetExtensions, ...styleExtensions].join(',')}}`;
30
+ const files = `${source}/**/*.{${supportedExtensions.join()}}`;
31
31
  let options: Parameters<typeof copyFiles>[0];
32
32
 
33
33
  beforeEach(() => {
@@ -72,7 +72,7 @@ describe(`[startup] cli utils (${copyFiles.name})`, () => {
72
72
  test('copies assets and styles from source to destination', async () => {
73
73
  await subject();
74
74
 
75
- expect(cpx.copy).toHaveBeenCalledWith(files, destination);
75
+ expect(cpx.copySync).toHaveBeenCalledWith(files, destination);
76
76
  });
77
77
 
78
78
  describe.each(styleExtensions)('with %s module', extension => {
@@ -151,7 +151,7 @@ describe(`[startup] cli utils (${copyFiles.name})`, () => {
151
151
  test('does not copy files', async () => {
152
152
  await subject();
153
153
 
154
- expect(cpx.copy).not.toHaveBeenCalledWith();
154
+ expect(cpx.copySync).not.toHaveBeenCalledWith();
155
155
  });
156
156
  });
157
157
  });
@@ -6,7 +6,13 @@ import { getPortPromise } from 'portfinder';
6
6
  import webpack, { Configuration } from 'webpack';
7
7
  import WebpackDevServer from 'webpack-dev-server';
8
8
 
9
- import { getPackageName, isDevServerDisabled, isWebComponent, log } from '../../utils';
9
+ import {
10
+ getPackageName,
11
+ hasHeadlessBundle,
12
+ isDevServerDisabled,
13
+ isWebComponent,
14
+ log,
15
+ } from '../../utils';
10
16
  import { Overrides, createWebpackConfig } from '../../webpack';
11
17
 
12
18
  interface Options {
@@ -43,10 +49,18 @@ export async function bundle(options: Options = {}) {
43
49
  plugins: config?.plugins,
44
50
  };
45
51
 
46
- return Promise.all([
52
+ const bundles = [
47
53
  run(createWebpackConfig(webpackConfig, { embed: true, ...webpackOptions })),
48
54
  run(createWebpackConfig(webpackConfig, webpackOptions)),
49
- ]);
55
+ ];
56
+
57
+ if (hasHeadlessBundle()) {
58
+ bundles.push(
59
+ run(createWebpackConfig(webpackConfig, { ...webpackOptions, headless: true }))
60
+ );
61
+ }
62
+
63
+ return Promise.all(bundles);
50
64
  }
51
65
 
52
66
  return run(config ?? createWebpackConfig({ configuration: { mode } }, webpackOptions));
@@ -66,12 +80,20 @@ export async function bundleWatch(options: Options = {}) {
66
80
  plugins: config?.plugins,
67
81
  };
68
82
 
69
- return Promise.all([
83
+ const bundles = [
70
84
  runWatch(createWebpackConfig(webpackConfig, { embed: true, ...configOverrides })),
71
85
  isDevServerDisabled()
72
86
  ? runWatch(createWebpackConfig(webpackConfig, configOverrides))
73
87
  : runServe(createWebpackConfig(webpackConfig, configOverrides)),
74
- ]);
88
+ ];
89
+
90
+ if (hasHeadlessBundle()) {
91
+ bundles.push(
92
+ runWatch(createWebpackConfig(webpackConfig, { ...configOverrides, headless: true }))
93
+ );
94
+ }
95
+
96
+ return Promise.all(bundles);
75
97
  }
76
98
 
77
99
  const webpackConfig =
@@ -7,8 +7,17 @@ import { getFolders, log } from '../../utils';
7
7
  import { compileLess } from './compile-less';
8
8
  import { compileSass } from './compile-sass';
9
9
 
10
- export const assetExtensions = ['eot', 'gif', 'jpg', 'otf', 'png', 'svg', 'ttf', 'woff', 'woff2'];
10
+ const assetExtensions = ['eot', 'gif', 'jpg', 'otf', 'png', 'svg', 'ttf', 'woff', 'woff2'];
11
+ const resourceExtensions = ['json'];
11
12
  export const styleExtensions = ['css', 'less', 'scss'];
13
+ const styleTypeDefinitionExtensions = styleExtensions.map(ext => `${ext}.d.ts`);
14
+
15
+ export const supportedExtensions = [
16
+ assetExtensions,
17
+ resourceExtensions,
18
+ styleExtensions,
19
+ styleTypeDefinitionExtensions,
20
+ ].flat();
12
21
 
13
22
  const styleModuleRegex = new RegExp(`\\.module\\.(${styleExtensions.join('|')})$`);
14
23
 
@@ -22,7 +31,7 @@ export async function copyFiles({ watch }: CopyFilesOptions) {
22
31
  return;
23
32
  }
24
33
 
25
- const filesPattern = `${source}/**/*.{${[...assetExtensions, ...styleExtensions].join()}}`;
34
+ const filesPattern = `${source}/**/*.{${supportedExtensions.join()}}`;
26
35
  const creator = new DtsCreator({ camelCase: true, namedExports: true });
27
36
  const generateDefinitions = async (file: string) => {
28
37
  try {
@@ -35,13 +44,14 @@ export async function copyFiles({ watch }: CopyFilesOptions) {
35
44
  };
36
45
 
37
46
  if (!watch) {
38
- log.info(`Copying ${filesPattern} -> ${destination}`);
39
- cpx.copy(filesPattern, destination);
40
-
41
47
  const modulesPattern = `${source}/**/*.module.{${styleExtensions.join()}}`;
42
48
  log.info(`Generating types for ${modulesPattern}`);
43
49
  const modules = await glob(modulesPattern);
44
50
  await Promise.all(modules.map(generateDefinitions));
51
+
52
+ log.info(`Copying ${filesPattern} -> ${destination}`);
53
+ cpx.copySync(filesPattern, destination);
54
+
45
55
  return;
46
56
  }
47
57
 
@@ -49,7 +59,7 @@ export async function copyFiles({ watch }: CopyFilesOptions) {
49
59
  cpx.watch(filesPattern, destination, {
50
60
  initialCopy: false,
51
61
  // Optimization so cpx2 doesn't waste resources watching irrelevant files
52
- ignore: ['__tests__', '__mocks__', '**/*.ts', '**/*.tsx'],
62
+ ignore: ['__tests__', '__mocks__', '*.*', ...supportedExtensions.map(ext => `!*.${ext}`)],
53
63
  })
54
64
  .on('copy', async ({ srcPath, dstPath }: { srcPath: string; dstPath: string }) => {
55
65
  log.info(`Copied ${srcPath} -> ${dstPath}`);
@@ -1,21 +1,5 @@
1
- import path from 'path';
2
-
3
- import { readJson } from '../../utils';
1
+ import { TSConfig } from './ts-config';
4
2
 
5
3
  export function getModuleType(configPath: string): string {
6
- const config = readJson<{ compilerOptions?: { module: string }; extends?: string }>(configPath);
7
-
8
- if (config.compilerOptions?.module) {
9
- return config.compilerOptions.module.toLowerCase();
10
- }
11
-
12
- if (config.extends) {
13
- return getModuleType(
14
- /^\.?\.\//.test(config.extends)
15
- ? path.join(path.dirname(configPath), config.extends)
16
- : require.resolve(config.extends)
17
- );
18
- }
19
-
20
- return 'commonjs';
4
+ return new TSConfig(configPath).getValue('compilerOptions.module', 'commonjs');
21
5
  }
@@ -11,6 +11,6 @@ export * from './lerna-exec';
11
11
  export * from './pipe-stdout';
12
12
  export * from './process-tree';
13
13
  export * from './set-node-options';
14
- export * from './tcm';
14
+ export * from './ts-config';
15
15
  export * from './type-check';
16
16
  export * from './watch-stdout';
@@ -0,0 +1,64 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { readJson } from '../../utils';
4
+
5
+ export class TSConfig {
6
+ private readonly json: Map<string, any>;
7
+
8
+ constructor(private readonly configPath: string) {
9
+ this.json = new Map();
10
+ }
11
+
12
+ // Overload signatures
13
+ getValue<T>(key: string, defaultValue: T): NoInfer<T>;
14
+ getValue<T>(key: string, defaultValue?: T): T | undefined;
15
+
16
+ // Implementation signature
17
+ getValue<T>(key: string, defaultValue?: T) {
18
+ return this.getValueInternal<T>(this.configPath, key.split('.'), defaultValue);
19
+ }
20
+
21
+ private getValueInternal<T>(configPath: string, key: string[], defaultValue?: T): T {
22
+ const config = this.getJson<{ extends?: string }>(configPath);
23
+ const value = key.reduce<any>((result, el) => {
24
+ return result && typeof result === 'object' ? result[el] : undefined;
25
+ }, config);
26
+
27
+ if (value !== undefined) {
28
+ return value;
29
+ }
30
+
31
+ const extendsPath = this.getExtendsPath(configPath, config);
32
+ if (extendsPath) {
33
+ return this.getValueInternal(extendsPath, key, defaultValue);
34
+ }
35
+
36
+ return defaultValue as T;
37
+ }
38
+
39
+ private getExtendsPath(configPath: string, config: { extends?: string }) {
40
+ if (!config.extends) {
41
+ return;
42
+ }
43
+
44
+ if (!/^\.?\.\//.test(config.extends)) {
45
+ return require.resolve(config.extends);
46
+ }
47
+
48
+ const dir = path.dirname(configPath);
49
+ return [config.extends, `${config.extends}.json`]
50
+ .map(name => path.join(dir, name))
51
+ .find(fs.existsSync);
52
+ }
53
+
54
+ private getJson<T>(configPath: string): T {
55
+ let result: T = this.json.get(configPath);
56
+
57
+ if (!result) {
58
+ result = readJson<T>(configPath);
59
+ this.json.set(configPath, result);
60
+ }
61
+
62
+ return result;
63
+ }
64
+ }
@@ -10,6 +10,7 @@ import {
10
10
  getStylelintConfiguration,
11
11
  getWebComponentBranchConfigs,
12
12
  getWebComponentConfiguration,
13
+ hasHeadlessBundle,
13
14
  isBundle,
14
15
  isCustomStyleRules,
15
16
  isDevServerDisabled,
@@ -332,6 +333,25 @@ describe('[startup] Utils', () => {
332
333
  });
333
334
  });
334
335
 
336
+ describe(`${hasHeadlessBundle.name}`, () => {
337
+ const subject = () => hasHeadlessBundle();
338
+
339
+ itReturns(subject, false);
340
+
341
+ describe('when src/headless.ts exists', () => {
342
+ beforeEach(() =>
343
+ vol.fromJSON({
344
+ 'src/headless.ts': '',
345
+ 'tsconfig.json': JSON.stringify({
346
+ compilerOptions: { rootDir: 'src', outDir: 'path' },
347
+ }),
348
+ })
349
+ );
350
+
351
+ itReturns(subject, true);
352
+ });
353
+ });
354
+
335
355
  describe.each([
336
356
  {
337
357
  fn: isBundle,
@@ -8,6 +8,7 @@ import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-serv
8
8
 
9
9
  import { ReviewConfiguration } from '../cli/commands/review';
10
10
  import { log } from './log';
11
+ import { getFolders } from './get-folders';
11
12
  import { readJson, readJsonSafe } from './read-json';
12
13
 
13
14
  export const allowedWebpackDevServerOptions = ['headers', 'port', 'proxy', 'static'] as const;
@@ -175,6 +176,17 @@ export function getWebComponentBranchConfigs(locationOrJson: LocationOrJson = '.
175
176
  return config?.branches;
176
177
  }
177
178
 
179
+ export function hasHeadlessBundle() {
180
+ let source: string;
181
+ try {
182
+ source = getFolders().source;
183
+ } catch {
184
+ return false;
185
+ }
186
+ const headlessPath = path.join(source, 'headless.ts');
187
+ return fs.existsSync(headlessPath);
188
+ }
189
+
178
190
  export function getSwcCompilePackageConfiguration() {
179
191
  return getConfiguration()['swc-compile-package'] ?? {};
180
192
  }
@@ -38,7 +38,6 @@ jest.mock('../../utils', () => ({
38
38
  jest.mock('../configs/utils/get-launchdarkly-sdk-version');
39
39
  jest.mock('../utils', () => ({
40
40
  ...jest.requireActual('../utils'),
41
- featureCohort: jest.fn(),
42
41
  getCallerFile: jest.fn(),
43
42
  getModuleEntryPath: jest.fn(),
44
43
  }));
@@ -17,7 +17,7 @@ import {
17
17
  } from '../../utils';
18
18
  import { webComponentStyleRules } from '../__mocks__';
19
19
  import { generateMetadata } from '../configs/utils';
20
- import { featureCohort, getCallerFile, splitByEntry } from '../utils';
20
+ import { getCallerFile, splitByEntry } from '../utils';
21
21
 
22
22
  import { createWebpackConfig } from '../index';
23
23
 
@@ -45,7 +45,6 @@ jest.mock('../../utils', () => ({
45
45
  jest.mock('../configs/utils/generate-metadata');
46
46
  jest.mock('../utils', () => ({
47
47
  ...jest.requireActual('../utils'),
48
- featureCohort: jest.fn(),
49
48
  getCallerFile: jest.fn(),
50
49
  }));
51
50
 
@@ -322,6 +321,22 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
322
321
  });
323
322
  });
324
323
 
324
+ describe('when headless option is set to true', () => {
325
+ beforeEach(() => (options.headless = true));
326
+
327
+ test(`omits design system and registers headless`, () => {
328
+ expect(subject().plugins).toContainEqual(
329
+ new VirtualModulesPlugin({
330
+ [indexPath()]: [
331
+ "import { registerHeadless } from '@servicetitan/web-components';",
332
+ "import { connectedCallback, disconnectedCallback } from './headless';",
333
+ 'registerHeadless(connectedCallback, disconnectedCallback);',
334
+ ].join('\n'),
335
+ })
336
+ );
337
+ });
338
+ });
339
+
325
340
  describe.each([webpackDevConfigFileName, webpackProdConfigFileName])(
326
341
  'when invoked from "%s"',
327
342
  configFileName => {
@@ -333,32 +348,6 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
333
348
  }
334
349
  );
335
350
 
336
- describe('when featureCohort is active', () => {
337
- beforeEach(() => {
338
- jest.mocked(featureCohort).mockImplementation((_: number[], fn: Function) => fn());
339
- });
340
-
341
- test('configures "cache"', () => {
342
- expect(subject().cache).toEqual({
343
- type: 'filesystem',
344
- name: `${packageName}__full`,
345
- profile: true,
346
- });
347
- });
348
-
349
- describe('when embed option is set to true', () => {
350
- beforeEach(() => (options.embed = true));
351
-
352
- test('changes "cache.name" suffix to "__light"', () => {
353
- expect(subject().cache).toEqual(
354
- expect.objectContaining({
355
- name: `${packageName}__light`,
356
- })
357
- );
358
- });
359
- });
360
- });
361
-
362
351
  describe('when mode is production', () => {
363
352
  beforeEach(() =>
364
353
  Object.assign((overrides.configuration ??= {}), { mode: 'production' })
@@ -29,7 +29,7 @@ import {
29
29
  log,
30
30
  } from '../../utils';
31
31
  import { fileRules, productionStyleRules, styleRules } from '../__mocks__';
32
- import { featureCohort, splitByEntry } from '../utils';
32
+ import { splitByEntry } from '../utils';
33
33
 
34
34
  import { createWebpackConfig } from '../index';
35
35
 
@@ -67,10 +67,6 @@ jest.mock('../../utils', () => ({
67
67
  loadSharedDependencies: jest.fn(),
68
68
  log: { debug: jest.fn(), info: jest.fn(), warning: jest.fn() },
69
69
  }));
70
- jest.mock('../utils', () => ({
71
- ...jest.requireActual('../utils'),
72
- featureCohort: jest.fn(),
73
- }));
74
70
 
75
71
  describe(`[startup] ${createWebpackConfig.name}`, () => {
76
72
  const source = 'src';
@@ -172,9 +168,7 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
172
168
  });
173
169
 
174
170
  test('configures "output.path"', () => {
175
- expect(subject().output!.path).toEqual(
176
- expect.stringContaining(path.join(destination, 'bundle'))
177
- );
171
+ expect(subject().output!.path).toEqual(path.join(process.cwd(), destination, 'bundle'));
178
172
  });
179
173
 
180
174
  test('configures "optimization"', () => {
@@ -471,6 +465,27 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
471
465
  );
472
466
  });
473
467
  });
468
+
469
+ describe('headless option is true', () => {
470
+ beforeEach(() => setOptions({ headless: true }));
471
+
472
+ test('configures "output.path"', () => {
473
+ expect(subject().output!.path).toEqual(
474
+ path.join(process.cwd(), destination, 'bundle', 'headless')
475
+ );
476
+ });
477
+
478
+ describe('when mode is "production"', () => {
479
+ beforeEach(() => {
480
+ overrides.configuration ??= {};
481
+ Object.assign(overrides.configuration, { mode: 'production' });
482
+ });
483
+
484
+ test('omits optimization.runtimeChunk', () => {
485
+ expect(subject().optimization?.runtimeChunk).toBeUndefined();
486
+ });
487
+ });
488
+ });
474
489
  });
475
490
  });
476
491
 
@@ -487,40 +502,14 @@ describe(`[startup] ${createWebpackConfig.name}`, () => {
487
502
  )
488
503
  );
489
504
  });
490
- });
491
-
492
- describe('when featureCohort is active', () => {
493
- beforeEach(() => {
494
- jest.mocked(featureCohort).mockImplementation((_: number[], fn: Function) => fn());
495
- });
496
-
497
- test('configures "cache"', () => {
498
- expect(subject().cache).toEqual({
499
- type: 'filesystem',
500
- name: packageName,
501
- profile: true,
502
- });
503
- });
504
505
 
505
506
  describe('when package is web component', () => {
506
507
  beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(true));
507
508
 
508
- function itAppendsSuffixToPackageName(suffix: string) {
509
- test(`appends "${suffix}" to package name`, () => {
510
- expect(subject().cache).toEqual(
511
- expect.objectContaining({
512
- name: `${packageName}${suffix}`,
513
- })
514
- );
515
- });
516
- }
517
-
518
- itAppendsSuffixToPackageName('__full');
519
-
520
- describe('when embed option is set to true', () => {
521
- beforeEach(() => setOptions({ embed: true }));
522
-
523
- itAppendsSuffixToPackageName('__light');
509
+ test('configures "output.path"', () => {
510
+ expect(subject().output!.path).toEqual(
511
+ path.join(process.cwd(), destination, 'bundle', 'light')
512
+ );
524
513
  });
525
514
  });
526
515
  });
@@ -1,5 +1,4 @@
1
1
  export * from './amd-config';
2
- export * from './cache-config';
3
2
  export * from './dev-server-config';
4
3
  export * from './devtool-config';
5
4
  export * from './entry.config';
@@ -8,7 +8,7 @@ type ConfigWithCacheGroups = Config & { splitChunks: { cacheGroups: Record<strin
8
8
  type Result = Pick<Configuration, 'optimization'>;
9
9
 
10
10
  export function optimizationConfig(context: Context, _: Overrides): Result {
11
- const { isProduction } = context;
11
+ const { headless, isProduction } = context;
12
12
 
13
13
  const optimization: ConfigWithCacheGroups = {
14
14
  chunkIds: isProduction ? 'deterministic' : 'named',
@@ -17,7 +17,7 @@ export function optimizationConfig(context: Context, _: Overrides): Result {
17
17
  splitChunks: { cacheGroups: {} },
18
18
  };
19
19
 
20
- if (isProduction) {
20
+ if (!headless && isProduction) {
21
21
  optimization.runtimeChunk = 'single';
22
22
  }
23
23
 
@@ -58,8 +58,8 @@ function minimizeConfig(optimization: ConfigWithCacheGroups, context: Context) {
58
58
  }
59
59
 
60
60
  function sharedDependenciesConfig(optimization: ConfigWithCacheGroups, context: Context) {
61
- const { isExposeSharedDependencies } = context;
62
- if (!isExposeSharedDependencies) {
61
+ const { headless, isExposeSharedDependencies } = context;
62
+ if (!isExposeSharedDependencies || headless) {
63
63
  return;
64
64
  }
65
65
 
@@ -80,8 +80,8 @@ function sharedDependenciesConfig(optimization: ConfigWithCacheGroups, context:
80
80
  }
81
81
 
82
82
  function webComponentConfig(optimization: ConfigWithCacheGroups, context: Context) {
83
- const { isWebComponent, isProduction } = context;
84
- if (!isWebComponent) {
83
+ const { headless, isWebComponent, isProduction } = context;
84
+ if (!isWebComponent || headless) {
85
85
  return;
86
86
  }
87
87
 
@@ -6,12 +6,14 @@ type Config = Configuration['output'];
6
6
  type Result = Pick<Configuration, 'output'>;
7
7
 
8
8
  export function outputConfig(context: Context, _: Overrides): Result {
9
- const { destination, embed, isProduction, isWebComponent, name } = context;
9
+ const { destination, embed, headless, isProduction, isWebComponent, name } = context;
10
+
11
+ const bundleDir = headless ? 'headless' : embed ? 'light' : 'full';
10
12
 
11
13
  const output: Config = {
12
14
  filename: '[name].bundle.js',
13
15
  path: isWebComponent
14
- ? path.join(process.cwd(), `${destination}/bundle/${embed ? 'light' : 'full'}`)
16
+ ? path.join(process.cwd(), `${destination}/bundle/${bundleDir}`)
15
17
  : path.join(process.cwd(), `${destination}/bundle`),
16
18
  };
17
19