@servicetitan/startup 31.0.0 → 31.2.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 (159) hide show
  1. package/bin/index.js +8 -0
  2. package/dist/cli/commands/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.js +1 -7
  4. package/dist/cli/commands/build.js.map +1 -1
  5. package/dist/cli/commands/prepare-package.d.ts +1 -2
  6. package/dist/cli/commands/prepare-package.d.ts.map +1 -1
  7. package/dist/cli/commands/prepare-package.js +4 -6
  8. package/dist/cli/commands/prepare-package.js.map +1 -1
  9. package/dist/cli/commands/run-task.test.js +59 -0
  10. package/dist/cli/commands/run-task.test.js.map +1 -0
  11. package/dist/cli/commands/start.d.ts.map +1 -1
  12. package/dist/cli/commands/start.js +2 -11
  13. package/dist/cli/commands/start.js.map +1 -1
  14. package/dist/cli/tasks/swc-cli.d.js +3 -0
  15. package/dist/cli/tasks/swc-cli.d.js.map +1 -0
  16. package/dist/cli/tasks/swc-compile-package.d.ts.map +1 -1
  17. package/dist/cli/tasks/swc-compile-package.js +22 -19
  18. package/dist/cli/tasks/swc-compile-package.js.map +1 -1
  19. package/dist/cli/types/cpx2.d.js +3 -0
  20. package/dist/cli/types/cpx2.d.js.map +1 -0
  21. package/dist/cli/utils/bundle.d.ts +2 -2
  22. package/dist/cli/utils/bundle.d.ts.map +1 -1
  23. package/dist/cli/utils/bundle.js +18 -4
  24. package/dist/cli/utils/bundle.js.map +1 -1
  25. package/dist/cli/utils/copy-files.d.ts +1 -1
  26. package/dist/cli/utils/copy-files.d.ts.map +1 -1
  27. package/dist/cli/utils/copy-files.js +18 -11
  28. package/dist/cli/utils/copy-files.js.map +1 -1
  29. package/dist/cli/utils/get-module-type.d.ts.map +1 -1
  30. package/dist/cli/utils/get-module-type.js +2 -16
  31. package/dist/cli/utils/get-module-type.js.map +1 -1
  32. package/dist/cli/utils/index.d.ts +1 -1
  33. package/dist/cli/utils/index.d.ts.map +1 -1
  34. package/dist/cli/utils/index.js +1 -1
  35. package/dist/cli/utils/index.js.map +1 -1
  36. package/dist/cli/utils/ts-config.d.ts +11 -0
  37. package/dist/cli/utils/ts-config.d.ts.map +1 -0
  38. package/dist/cli/utils/ts-config.js +80 -0
  39. package/dist/cli/utils/ts-config.js.map +1 -0
  40. package/dist/utils/get-configuration.d.ts +1 -0
  41. package/dist/utils/get-configuration.d.ts.map +1 -1
  42. package/dist/utils/get-configuration.js +14 -0
  43. package/dist/utils/get-configuration.js.map +1 -1
  44. package/dist/utils/log.d.ts +1 -0
  45. package/dist/utils/log.d.ts.map +1 -1
  46. package/dist/utils/log.js +9 -0
  47. package/dist/utils/log.js.map +1 -1
  48. package/dist/webpack/configs/dev-server-config.d.ts.map +1 -1
  49. package/dist/webpack/configs/dev-server-config.js +11 -0
  50. package/dist/webpack/configs/dev-server-config.js.map +1 -1
  51. package/dist/webpack/configs/index.d.ts +0 -1
  52. package/dist/webpack/configs/index.d.ts.map +1 -1
  53. package/dist/webpack/configs/index.js +0 -1
  54. package/dist/webpack/configs/index.js.map +1 -1
  55. package/dist/webpack/configs/optimization-config.js +6 -6
  56. package/dist/webpack/configs/optimization-config.js.map +1 -1
  57. package/dist/webpack/configs/output-config.d.ts.map +1 -1
  58. package/dist/webpack/configs/output-config.js +3 -2
  59. package/dist/webpack/configs/output-config.js.map +1 -1
  60. package/dist/webpack/configs/plugins/html-plugin.d.ts +1 -1
  61. package/dist/webpack/configs/plugins/html-plugin.d.ts.map +1 -1
  62. package/dist/webpack/configs/plugins/html-plugin.js +2 -2
  63. package/dist/webpack/configs/plugins/html-plugin.js.map +1 -1
  64. package/dist/webpack/configs/plugins/virtual-modules-plugin.js +14 -4
  65. package/dist/webpack/configs/plugins/virtual-modules-plugin.js.map +1 -1
  66. package/dist/webpack/configs/utils/generate-metadata.d.ts.map +1 -1
  67. package/dist/webpack/configs/utils/generate-metadata.js +4 -0
  68. package/dist/webpack/configs/utils/generate-metadata.js.map +1 -1
  69. package/dist/webpack/create-webpack-config.d.ts.map +1 -1
  70. package/dist/webpack/create-webpack-config.js +0 -1
  71. package/dist/webpack/create-webpack-config.js.map +1 -1
  72. package/dist/webpack/types.d.ts +1 -0
  73. package/dist/webpack/types.d.ts.map +1 -1
  74. package/dist/webpack/utils/index.d.ts +0 -1
  75. package/dist/webpack/utils/index.d.ts.map +1 -1
  76. package/dist/webpack/utils/index.js +0 -1
  77. package/dist/webpack/utils/index.js.map +1 -1
  78. package/package.json +7 -8
  79. package/src/cli/commands/__tests__/build.test.ts +2 -4
  80. package/src/cli/commands/__tests__/prepare-package.test.ts +5 -28
  81. package/src/cli/commands/__tests__/start.test.ts +3 -5
  82. package/src/cli/commands/build.ts +0 -2
  83. package/src/cli/commands/prepare-package.ts +4 -7
  84. package/src/cli/commands/start.ts +1 -3
  85. package/src/cli/tasks/__tests__/swc-compile-package.test.ts +71 -12
  86. package/src/cli/tasks/swc-compile-package.ts +21 -20
  87. package/src/cli/utils/__tests__/bundle.test.ts +48 -7
  88. package/src/cli/utils/__tests__/copy-files.test.ts +5 -5
  89. package/src/cli/utils/bundle.ts +27 -5
  90. package/src/cli/utils/copy-files.ts +16 -6
  91. package/src/cli/utils/get-module-type.ts +2 -18
  92. package/src/cli/utils/index.ts +1 -1
  93. package/src/cli/utils/ts-config.ts +64 -0
  94. package/src/utils/__tests__/get-configuration.test.ts +20 -0
  95. package/src/utils/__tests__/log.test.ts +8 -0
  96. package/src/utils/get-configuration.ts +12 -0
  97. package/src/utils/log.ts +10 -0
  98. package/src/webpack/__tests__/create-webpack-config-shared-dependencies.test.ts +0 -1
  99. package/src/webpack/__tests__/create-webpack-config-web-component.test.ts +17 -28
  100. package/src/webpack/__tests__/create-webpack-config.test.ts +113 -42
  101. package/src/webpack/configs/dev-server-config.ts +13 -1
  102. package/src/webpack/configs/index.ts +0 -1
  103. package/src/webpack/configs/optimization-config.ts +6 -6
  104. package/src/webpack/configs/output-config.ts +4 -2
  105. package/src/webpack/configs/plugins/html-plugin.ts +5 -2
  106. package/src/webpack/configs/plugins/virtual-modules-plugin.ts +15 -2
  107. package/src/webpack/configs/utils/__tests__/generate-metadata.test.ts +3 -1
  108. package/src/webpack/configs/utils/generate-metadata.ts +6 -1
  109. package/src/webpack/create-webpack-config.ts +0 -2
  110. package/src/webpack/types.ts +1 -0
  111. package/src/webpack/utils/index.ts +0 -1
  112. package/dist/__mocks__/create-package.d.ts +0 -3
  113. package/dist/__mocks__/create-package.d.ts.map +0 -1
  114. package/dist/__mocks__/index.d.ts +0 -2
  115. package/dist/__mocks__/index.d.ts.map +0 -1
  116. package/dist/cli/commands/review/__mocks__/expect-calls.d.ts +0 -23
  117. package/dist/cli/commands/review/__mocks__/expect-calls.d.ts.map +0 -1
  118. package/dist/cli/commands/review/__mocks__/index.d.ts +0 -2
  119. package/dist/cli/commands/review/__mocks__/index.d.ts.map +0 -1
  120. package/dist/cli/commands/review/rules/__mocks__/index.d.ts +0 -4
  121. package/dist/cli/commands/review/rules/__mocks__/index.d.ts.map +0 -1
  122. package/dist/cli/commands/review/rules/__mocks__/mock-config.d.ts +0 -7
  123. package/dist/cli/commands/review/rules/__mocks__/mock-config.d.ts.map +0 -1
  124. package/dist/cli/commands/review/rules/__mocks__/mock-packages.d.ts +0 -21
  125. package/dist/cli/commands/review/rules/__mocks__/mock-packages.d.ts.map +0 -1
  126. package/dist/cli/commands/review/rules/__mocks__/mock-project.d.ts +0 -3
  127. package/dist/cli/commands/review/rules/__mocks__/mock-project.d.ts.map +0 -1
  128. package/dist/cli/utils/style-extensions.d.ts +0 -2
  129. package/dist/cli/utils/style-extensions.d.ts.map +0 -1
  130. package/dist/cli/utils/style-extensions.js +0 -17
  131. package/dist/cli/utils/style-extensions.js.map +0 -1
  132. package/dist/cli/utils/tcm.d.ts +0 -6
  133. package/dist/cli/utils/tcm.d.ts.map +0 -1
  134. package/dist/cli/utils/tcm.js +0 -72
  135. package/dist/cli/utils/tcm.js.map +0 -1
  136. package/dist/webpack/__mocks__/file-rules.d.ts +0 -3
  137. package/dist/webpack/__mocks__/file-rules.d.ts.map +0 -1
  138. package/dist/webpack/__mocks__/index.d.ts +0 -3
  139. package/dist/webpack/__mocks__/index.d.ts.map +0 -1
  140. package/dist/webpack/__mocks__/style-rules.d.ts +0 -8
  141. package/dist/webpack/__mocks__/style-rules.d.ts.map +0 -1
  142. package/dist/webpack/configs/cache-config.d.ts +0 -6
  143. package/dist/webpack/configs/cache-config.d.ts.map +0 -1
  144. package/dist/webpack/configs/cache-config.js +0 -34
  145. package/dist/webpack/configs/cache-config.js.map +0 -1
  146. package/dist/webpack/utils/feature-cohort.d.ts +0 -5
  147. package/dist/webpack/utils/feature-cohort.d.ts.map +0 -1
  148. package/dist/webpack/utils/feature-cohort.js +0 -26
  149. package/dist/webpack/utils/feature-cohort.js.map +0 -1
  150. package/dist/webpack/utils/hash-mod.d.ts +0 -9
  151. package/dist/webpack/utils/hash-mod.d.ts.map +0 -1
  152. package/dist/webpack/utils/hash-mod.js +0 -39
  153. package/dist/webpack/utils/hash-mod.js.map +0 -1
  154. package/src/cli/utils/__tests__/tcm.test.ts +0 -195
  155. package/src/cli/utils/style-extensions.ts +0 -1
  156. package/src/cli/utils/tcm.ts +0 -66
  157. package/src/webpack/configs/cache-config.ts +0 -25
  158. package/src/webpack/utils/feature-cohort.ts +0 -19
  159. package/src/webpack/utils/hash-mod.ts +0 -32
@@ -1,9 +1,10 @@
1
+ import { fs, vol } from 'memfs';
1
2
  import { swcDir } from '@swc/cli';
2
- import { getSwcCompilePackageConfiguration, log, readJson } from '../../../utils';
3
+ import { getSwcCompilePackageConfiguration, log } from '../../../utils';
3
4
  import { SwcCompilePackage } from '../swc-compile-package';
4
- import { compilerOptions } from '../../../../tsconfig/base.json';
5
5
  import deepmerge from 'deepmerge';
6
6
 
7
+ jest.mock('fs', () => fs);
7
8
  jest.mock('@swc/cli', () => ({
8
9
  swcDir: jest.fn(),
9
10
  }));
@@ -15,9 +16,7 @@ jest.mock('../../../utils', () => ({
15
16
  destination: 'dist',
16
17
  }),
17
18
  log: { info: jest.fn(), text: jest.fn() },
18
- readJsonSafe: jest.fn().mockReturnValue({}),
19
- readJson: jest.fn().mockReturnValue({}),
20
- getSwcCompilePackageConfiguration: jest.fn().mockReturnValue({}),
19
+ getSwcCompilePackageConfiguration: jest.fn(),
21
20
  }));
22
21
 
23
22
  function overwriteMerge(_destinationArray: unknown[], sourceArray: unknown[]) {
@@ -25,12 +24,27 @@ function overwriteMerge(_destinationArray: unknown[], sourceArray: unknown[]) {
25
24
  }
26
25
 
27
26
  describe(`[startup] ${SwcCompilePackage.name} task`, () => {
27
+ function volumeFromJSON(overrides: Record<string, any> = {}) {
28
+ return vol.fromJSON({
29
+ 'package.json': JSON.stringify({}),
30
+ 'tsconfig.json': JSON.stringify({}),
31
+ ...Object.fromEntries(
32
+ Object.entries(overrides).map(([key, value]) => [key, JSON.stringify(value)])
33
+ ),
34
+ });
35
+ }
36
+
28
37
  beforeEach(() => {
29
38
  globalThis.performance.clearMarks();
30
39
  globalThis.performance.clearMeasures();
31
40
  jest.clearAllMocks();
41
+
42
+ jest.mocked(getSwcCompilePackageConfiguration).mockReturnValue({});
43
+ volumeFromJSON();
32
44
  });
33
45
 
46
+ afterEach(() => vol.reset());
47
+
34
48
  let watch = false;
35
49
 
36
50
  const subject = () => new SwcCompilePackage({ watch }).execute();
@@ -70,8 +84,8 @@ describe(`[startup] ${SwcCompilePackage.name} task`, () => {
70
84
  '**/__tests__/*',
71
85
  '**/*.test.*',
72
86
  '**/__mocks__/*',
87
+ '**/*.stories.*',
73
88
  ],
74
- copyFiles: true,
75
89
  },
76
90
  swcOptions: {
77
91
  jsc: {
@@ -80,16 +94,12 @@ describe(`[startup] ${SwcCompilePackage.name} task`, () => {
80
94
  tsx: true,
81
95
  decorators: true,
82
96
  },
83
- target: compilerOptions.target,
84
97
  transform: {
85
- legacyDecorator: compilerOptions.experimentalDecorators,
86
- decoratorMetadata: compilerOptions.emitDecoratorMetadata,
87
98
  react: {
88
99
  runtime: 'automatic',
89
100
  },
90
101
  },
91
102
  },
92
- sourceMaps: compilerOptions.sourceMap,
93
103
  module: {
94
104
  type: 'es6',
95
105
  },
@@ -119,10 +129,59 @@ describe(`[startup] ${SwcCompilePackage.name} task`, () => {
119
129
  itCallsSwcDir(overrides);
120
130
  });
121
131
 
132
+ describe('when tsconfig contains "exclude"', () => {
133
+ const exclude = ['**/stories.*'];
134
+
135
+ beforeEach(() => {
136
+ volumeFromJSON({
137
+ 'base.json': { exclude },
138
+ 'tsconfig.build.json': { extends: './base' },
139
+ });
140
+ });
141
+
142
+ test('uses exclude config', () => {
143
+ subject();
144
+
145
+ expect(swcDir).toHaveBeenCalledWith(
146
+ expect.objectContaining({
147
+ cliOptions: expect.objectContaining({ ignore: exclude }),
148
+ })
149
+ );
150
+ });
151
+ });
152
+
153
+ describe.each(
154
+ Object.entries({
155
+ target: 'jsc.target',
156
+ experimentalDecorators: 'jsc.transform.legacyDecorator',
157
+ emitDecoratorMetadata: 'jsc.transform.decoratorMetadata',
158
+ sourceMap: 'sourceMaps',
159
+ })
160
+ )('when tsconfig contains "compilerOptions.%s"', (compilerOption, swcOption) => {
161
+ beforeEach(() => {
162
+ volumeFromJSON({
163
+ 'tsconfig.json': { compilerOptions: { [compilerOption]: 'foo' } },
164
+ });
165
+ });
166
+
167
+ test(`configures swcOptions.${swcOption}`, () => {
168
+ subject();
169
+
170
+ const keys = swcOption.split('.').toReversed();
171
+ const expected = keys.slice(1).reduce(
172
+ (result, key) => {
173
+ return expect.objectContaining({ [key]: result });
174
+ },
175
+ expect.objectContaining({ [keys[0]]: 'foo' })
176
+ );
177
+ expect(swcDir).toHaveBeenCalledWith(expect.objectContaining({ swcOptions: expected }));
178
+ });
179
+ });
180
+
122
181
  describe('when package is commonjs', () => {
123
182
  beforeEach(() => {
124
- jest.mocked(readJson).mockReturnValueOnce({
125
- compilerOptions: { module: 'commonjs' },
183
+ volumeFromJSON({
184
+ 'tsconfig.json': { compilerOptions: { module: 'commonjs' } },
126
185
  });
127
186
  });
128
187
 
@@ -1,19 +1,15 @@
1
1
  import { swcDir } from '@swc/cli';
2
2
  import deepmerge from 'deepmerge';
3
3
 
4
- import { compilerOptions as baseCompilerOptions } from '../../../tsconfig/base.json';
4
+ import { getFolders, getSwcCompilePackageConfiguration, getTsConfig, log } from '../../utils';
5
+ import { TSConfig } from '../utils';
5
6
  import { Task } from './task';
6
- import { getFolders, getSwcCompilePackageConfiguration, log, readJson } from '../../utils';
7
7
 
8
8
  interface Args {
9
9
  [key: string]: unknown;
10
10
  watch?: boolean;
11
11
  }
12
12
 
13
- function toLowerCase(input: string | undefined): string | undefined {
14
- return input?.toLowerCase();
15
- }
16
-
17
13
  function overwriteMerge(_destinationArray: unknown[], sourceArray: unknown[]) {
18
14
  return sourceArray;
19
15
  }
@@ -25,8 +21,20 @@ export class SwcCompilePackage extends Task {
25
21
 
26
22
  async execute(): Promise<void> {
27
23
  const { source, destination } = getFolders();
28
- const compilerOptions = readJson('tsconfig.json').compilerOptions ?? {};
29
24
  const packageConfig = getSwcCompilePackageConfiguration();
25
+ const tsConfig = new TSConfig(getTsConfig());
26
+ const target = tsConfig.getValue('compilerOptions.target');
27
+ const legacyDecorator = tsConfig.getValue('compilerOptions.experimentalDecorators');
28
+ const decoratorMetadata = tsConfig.getValue('compilerOptions.emitDecoratorMetadata');
29
+ const sourceMaps = tsConfig.getValue('compilerOptions.sourceMap');
30
+ const module = tsConfig.getValue<string>('compilerOptions.module')?.toLowerCase();
31
+ const ignore = tsConfig.getValue('exclude', [
32
+ '**/*.d.ts',
33
+ '**/__tests__/*',
34
+ '**/*.test.*',
35
+ '**/__mocks__/*',
36
+ '**/*.stories.*',
37
+ ]);
30
38
 
31
39
  return new Promise((resolve, reject) => {
32
40
  swcDir(
@@ -38,13 +46,7 @@ export class SwcCompilePackage extends Task {
38
46
  stripLeadingPaths: true,
39
47
  extensions: ['.ts', '.tsx'],
40
48
  filenames: [source],
41
- ignore: [
42
- '**/*.d.ts',
43
- '**/__tests__/*',
44
- '**/*.test.*',
45
- '**/__mocks__/*',
46
- ],
47
- copyFiles: true,
49
+ ignore,
48
50
  },
49
51
  swcOptions: {
50
52
  jsc: {
@@ -53,20 +55,19 @@ export class SwcCompilePackage extends Task {
53
55
  tsx: true,
54
56
  decorators: true,
55
57
  },
56
- target: baseCompilerOptions.target,
58
+ target,
57
59
  transform: {
58
- legacyDecorator: baseCompilerOptions.experimentalDecorators,
59
- decoratorMetadata: baseCompilerOptions.emitDecoratorMetadata,
60
+ legacyDecorator,
61
+ decoratorMetadata,
60
62
  react: {
61
63
  runtime: 'automatic',
62
64
  },
63
65
  },
64
66
  },
65
- sourceMaps: baseCompilerOptions.sourceMap,
67
+ sourceMaps,
66
68
  module: {
67
69
  type:
68
- toLowerCase(compilerOptions.module) === 'commonjs' ||
69
- toLowerCase(compilerOptions.module) === 'nodenext'
70
+ module === 'commonjs' || module === 'nodenext'
70
71
  ? 'commonjs'
71
72
  : 'es6',
72
73
  },
@@ -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,
@@ -14,6 +14,14 @@ describe(`[startup] Utils`, () => {
14
14
  stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn());
15
15
  });
16
16
 
17
+ test('timestamp() returns the current time in the HH:MM:SS.SSS format', () => {
18
+ jest.useFakeTimers({
19
+ now: new Date('2024-05-14 10:40:00.050'),
20
+ });
21
+
22
+ expect(log.timestamp).toBe('10:40:00.050');
23
+ });
24
+
17
25
  test('text() writes default text', () => {
18
26
  log.text(message);
19
27
 
@@ -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
  }
package/src/utils/log.ts CHANGED
@@ -10,6 +10,16 @@ class Log {
10
10
  this.debugMap = new Map<string, Debugger>();
11
11
  }
12
12
 
13
+ get timestamp() {
14
+ return new Date().toLocaleTimeString(undefined, {
15
+ hour12: false,
16
+ hour: '2-digit',
17
+ minute: '2-digit',
18
+ second: '2-digit',
19
+ fractionalSecondDigits: 3,
20
+ });
21
+ }
22
+
13
23
  text(...text: string[]) {
14
24
  process.stdout.write(chalk(...text) + '\n');
15
25
  }