@servicetitan/startup 22.16.0 → 22.17.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 (41) hide show
  1. package/dist/__mocks__/create-package.d.ts +3 -0
  2. package/dist/__mocks__/create-package.d.ts.map +1 -0
  3. package/dist/{cli/__mocks__ → __mocks__}/create-package.js +4 -2
  4. package/dist/__mocks__/create-package.js.map +1 -0
  5. package/dist/__mocks__/index.d.ts.map +1 -0
  6. package/dist/__mocks__/index.js.map +1 -0
  7. package/dist/cli/index.js +25 -23
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/utils/set-node-options.d.ts +2 -1
  10. package/dist/cli/utils/set-node-options.d.ts.map +1 -1
  11. package/dist/cli/utils/set-node-options.js.map +1 -1
  12. package/dist/utils/get-configuration.d.ts +23 -6
  13. package/dist/utils/get-configuration.d.ts.map +1 -1
  14. package/dist/utils/get-configuration.js +19 -1
  15. package/dist/utils/get-configuration.js.map +1 -1
  16. package/package.json +4 -4
  17. package/src/{cli/__mocks__ → __mocks__}/create-package.ts +3 -2
  18. package/src/cli/commands/__tests__/build.test.ts +1 -1
  19. package/src/cli/commands/__tests__/init.test.ts +2 -1
  20. package/src/cli/commands/__tests__/mfe-publish.test.ts +1 -1
  21. package/src/cli/commands/__tests__/start.test.ts +1 -1
  22. package/src/cli/commands/__tests__/styles-check.test.ts +2 -1
  23. package/src/cli/index.ts +28 -40
  24. package/src/cli/utils/__tests__/bundle.test.ts +1 -1
  25. package/src/cli/utils/__tests__/set-node-options.test.ts +2 -1
  26. package/src/cli/utils/__tests__/tsc.test.ts +1 -1
  27. package/src/cli/utils/set-node-options.ts +2 -2
  28. package/src/utils/__tests__/get-jest-config.test.ts +2 -1
  29. package/src/utils/get-configuration.ts +26 -9
  30. package/src/webpack/__tests__/create-development-config.test.ts +306 -0
  31. package/src/webpack/__tests__/create-production-config.test.ts +231 -0
  32. package/src/webpack/__tests__/create-shared-config.test.ts +508 -0
  33. package/src/webpack/__tests__/create-webpack-config.test.ts +74 -0
  34. package/dist/cli/__mocks__/create-package.d.ts +0 -3
  35. package/dist/cli/__mocks__/create-package.d.ts.map +0 -1
  36. package/dist/cli/__mocks__/create-package.js.map +0 -1
  37. package/dist/cli/__mocks__/index.d.ts.map +0 -1
  38. package/dist/cli/__mocks__/index.js.map +0 -1
  39. /package/dist/{cli/__mocks__ → __mocks__}/index.d.ts +0 -0
  40. /package/dist/{cli/__mocks__ → __mocks__}/index.js +0 -0
  41. /package/src/{cli/__mocks__ → __mocks__}/index.ts +0 -0
@@ -0,0 +1,306 @@
1
+ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
2
+ import mockFS from 'mock-fs';
3
+ import path from 'path';
4
+ import {
5
+ Package,
6
+ PackageType,
7
+ getFolders,
8
+ getPackages,
9
+ isCustomStyleRules,
10
+ isExposeSharedDependencies,
11
+ isWebComponent,
12
+ } from '../../utils';
13
+ import { createPackage } from '../../__mocks__';
14
+ import { createConfig as createSharedConfig } from '../shared.config';
15
+ import { featureCohort } from '../utils/feature-cohort';
16
+
17
+ import { createConfig as createDevelopmentConfig } from '../development.config';
18
+
19
+ jest.mock('mini-css-extract-plugin', () =>
20
+ Object.assign(jest.fn(), { loader: 'MiniCssExtractPlugin.loader' })
21
+ );
22
+ jest.mock('../../utils', () => ({
23
+ ...jest.requireActual('../../utils'),
24
+ getFolders: jest.fn(),
25
+ getPackages: jest.fn(),
26
+ isCustomStyleRules: jest.fn(),
27
+ isExposeSharedDependencies: jest.fn(),
28
+ isWebComponent: jest.fn(),
29
+ log: { info: jest.fn() },
30
+ }));
31
+ jest.mock('../shared.config');
32
+ jest.mock('../utils/feature-cohort');
33
+
34
+ describe('createDevelopmentConfig', () => {
35
+ const packageName = '@servicetitan/foo';
36
+ const packageJson = 'package.json';
37
+ const sharedConfiguration = { resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] } };
38
+
39
+ let packages: Package[];
40
+ let overrides: Parameters<typeof createDevelopmentConfig>[0];
41
+ let options: Parameters<typeof createDevelopmentConfig>[1];
42
+
43
+ beforeEach(() => {
44
+ packages = [];
45
+ overrides = { configuration: { mode: 'development' } };
46
+ options = { name: '' };
47
+
48
+ jest.resetAllMocks();
49
+ jest.mocked(MiniCssExtractPlugin).mockImplementation(
50
+ (options: Record<string, any>): any => ({ MiniCssExtractPlugin: options })
51
+ );
52
+ jest.mocked(getPackages).mockImplementation(() => packages);
53
+ jest.mocked(createSharedConfig).mockReturnValue(sharedConfiguration);
54
+
55
+ mockFS({ [packageJson]: JSON.stringify({ name: packageName }) });
56
+ });
57
+
58
+ afterEach(() => mockFS.restore());
59
+
60
+ const subject = () => createDevelopmentConfig(overrides, options);
61
+
62
+ test('includes shared configuration', () => {
63
+ expect(subject()).toEqual(expect.objectContaining(sharedConfiguration));
64
+ });
65
+
66
+ test('includes input configuration', () => {
67
+ expect(subject()).toEqual(expect.objectContaining(overrides.configuration));
68
+ });
69
+
70
+ test('configures "devtool"', () => {
71
+ expect(subject().devtool).toEqual('cheap-module-source-map');
72
+ });
73
+
74
+ test('configures "output.filename"', () => {
75
+ expect(subject().output?.filename).toEqual('[name].bundle.js');
76
+ });
77
+
78
+ test('configures "devServer"', () => {
79
+ expect(subject().devServer).toEqual({
80
+ port: 8080,
81
+ historyApiFallback: true,
82
+ writeToDisk: true,
83
+ headers: {
84
+ 'Access-Control-Allow-Origin': '*',
85
+ 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept',
86
+ },
87
+ });
88
+ });
89
+
90
+ describe('with subordinate TSC packages', () => {
91
+ const source = 'src';
92
+ const destination = 'dist';
93
+
94
+ beforeEach(() => {
95
+ jest.mocked(getFolders).mockReturnValue({ source, destination });
96
+ packages = [
97
+ createPackage({ name: 'bar', type: PackageType.TSC }),
98
+ createPackage({ name: 'baz', type: PackageType.Webpack }),
99
+ ];
100
+ });
101
+
102
+ test('configures "devServer.watchOptions" to ignore TSC packages', () => {
103
+ const ignored = packages
104
+ .filter(({ type }) => type === PackageType.TSC)
105
+ .map(({ location }) => path.join(location, source));
106
+ expect(subject().devServer?.watchOptions).toEqual({ ignored });
107
+ });
108
+ });
109
+
110
+ Object.entries({
111
+ '.css': {
112
+ test: /(\.css)$/,
113
+ exclude: expect.any(Function),
114
+ use: ['style-loader', 'css-loader'],
115
+ },
116
+ '.scss': {
117
+ test: /(\.scss)$/,
118
+ exclude: /\.module.scss$/,
119
+ use: ['style-loader', 'css-loader', 'sass-loader'],
120
+ },
121
+ '.less': {
122
+ test: /(\.less)$/,
123
+ exclude: /\.module.less$/,
124
+ use: [
125
+ 'style-loader',
126
+ 'css-loader',
127
+ {
128
+ loader: 'less-loader',
129
+ options: { lessOptions: { math: 'always' } },
130
+ },
131
+ ],
132
+ },
133
+ '.module.css': {
134
+ test: /\.module.css$/,
135
+ use: [
136
+ {
137
+ loader: 'style-loader',
138
+ options: { esModule: true },
139
+ },
140
+ {
141
+ loader: 'css-loader',
142
+ options: {
143
+ esModule: true,
144
+ modules: { exportLocalsConvention: 'camelCaseOnly', namedExport: true },
145
+ },
146
+ },
147
+ ],
148
+ },
149
+ '.module.scss': {
150
+ test: /\.module.scss$/,
151
+ use: [
152
+ {
153
+ loader: 'style-loader',
154
+ options: { esModule: true },
155
+ },
156
+ {
157
+ loader: 'css-loader',
158
+ options: {
159
+ esModule: true,
160
+ modules: { exportLocalsConvention: 'camelCaseOnly', namedExport: true },
161
+ },
162
+ },
163
+ 'sass-loader',
164
+ ],
165
+ },
166
+ '.module.less': {
167
+ test: /\.module.less$/,
168
+ use: [
169
+ {
170
+ loader: 'style-loader',
171
+ options: { esModule: true },
172
+ },
173
+ {
174
+ loader: 'css-loader',
175
+ options: {
176
+ esModule: true,
177
+ modules: { exportLocalsConvention: 'camelCaseOnly', namedExport: true },
178
+ },
179
+ },
180
+ {
181
+ loader: 'less-loader',
182
+ options: { lessOptions: { math: 'always' } },
183
+ },
184
+ ],
185
+ },
186
+ }).forEach(([extension, rules]) => {
187
+ test(`configures "${extension}" rules`, () => {
188
+ expect(subject().module?.rules).toContainEqual(rules);
189
+ });
190
+
191
+ describe('when package is web component', () => {
192
+ beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(true));
193
+
194
+ test(`configures "${extension}" rules to use MiniCssExtractPlugin.loader instead of style-loader`, () => {
195
+ expect(subject().module?.rules).toContainEqual({
196
+ ...rules,
197
+ use: rules.use.map((loader: string | Record<string, any>) =>
198
+ loader === 'style-loader'
199
+ ? MiniCssExtractPlugin.loader
200
+ : typeof loader === 'object' && loader.loader === 'style-loader'
201
+ ? { ...loader, loader: MiniCssExtractPlugin.loader }
202
+ : loader
203
+ ),
204
+ });
205
+ });
206
+ });
207
+ });
208
+
209
+ test('does not configure "plugins"', () => {
210
+ expect(subject().plugins).toEqual([]);
211
+ });
212
+
213
+ function itConfiguresMiniCssExtractPlugIn() {
214
+ test('configures MiniCssExtractPlugIn plugin', () => {
215
+ expect(subject().plugins).toEqual([
216
+ new MiniCssExtractPlugin({ filename: '[name].bundle.css' }),
217
+ ]);
218
+ });
219
+ }
220
+
221
+ describe('when package is web component', () => {
222
+ beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(true));
223
+
224
+ itConfiguresMiniCssExtractPlugIn();
225
+ });
226
+
227
+ describe('when package exposes shared dependencies', () => {
228
+ beforeEach(() => jest.mocked(isExposeSharedDependencies).mockReturnValue(true));
229
+
230
+ itConfiguresMiniCssExtractPlugIn();
231
+
232
+ describe('when package has custom style rules', () => {
233
+ beforeEach(() => jest.mocked(isCustomStyleRules).mockReturnValue(true));
234
+
235
+ test('does not configure MiniCssExtractPlugIn plugin', () => {
236
+ expect(subject().plugins).toEqual([]);
237
+ });
238
+ });
239
+
240
+ test('configures "design-system.css" rules', () => {
241
+ expect(subject().module?.rules).toContainEqual({
242
+ test: /design-system.css$/,
243
+ use: [MiniCssExtractPlugin.loader, 'css-loader'],
244
+ });
245
+ });
246
+
247
+ test('configures ".css" rule to exclude design-system.css and .module.css files', () => {
248
+ const cssRule: any = subject().module?.rules?.find(({ test }: { test: RegExp }) =>
249
+ '.css'.match(test)
250
+ );
251
+ expect(
252
+ ['design-system.css', 'foo.module.css', 'foo.css'].filter(
253
+ path => !cssRule.exclude(path)
254
+ )
255
+ ).toEqual(['foo.css']);
256
+ });
257
+ });
258
+
259
+ describe('when package has custom style rules', () => {
260
+ beforeEach(() => jest.mocked(isCustomStyleRules).mockReturnValue(true));
261
+
262
+ test('does not configure "module.rules"', () => {
263
+ expect(subject().module?.rules).toEqual([]);
264
+ });
265
+ });
266
+
267
+ test('does not configure "cache"', () => {
268
+ expect(subject().cache).toBeUndefined();
269
+ });
270
+
271
+ describe('when featureCohort is active', () => {
272
+ beforeEach(() => {
273
+ jest.mocked(featureCohort).mockImplementation((_: number[], fn: Function) => fn());
274
+ });
275
+
276
+ test('configures "cache"', () => {
277
+ expect(subject().cache).toEqual({
278
+ type: 'filesystem',
279
+ name: packageName,
280
+ profile: true,
281
+ });
282
+ });
283
+
284
+ describe('when package is web component', () => {
285
+ beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(true));
286
+
287
+ function itAppendsSuffixToPackageName(suffix: string) {
288
+ test(`appends "${suffix}" to package name`, () => {
289
+ expect(subject().cache).toEqual(
290
+ expect.objectContaining({
291
+ name: `${packageName}${suffix}`,
292
+ })
293
+ );
294
+ });
295
+ }
296
+
297
+ itAppendsSuffixToPackageName('__full');
298
+
299
+ describe('when embed option is set to true', () => {
300
+ beforeEach(() => (options.embed = true));
301
+
302
+ itAppendsSuffixToPackageName('__light');
303
+ });
304
+ });
305
+ });
306
+ });
@@ -0,0 +1,231 @@
1
+ import MiniCssExtractPlugin from 'mini-css-extract-plugin';
2
+ import { isCustomStyleRules, isWebComponent } from '../../utils';
3
+ import { createConfig as createSharedConfig } from '../shared.config';
4
+
5
+ import { createConfig as createProductionConfig } from '../production.config';
6
+
7
+ jest.mock('mini-css-extract-plugin', () =>
8
+ Object.assign(jest.fn(), { loader: 'MiniCssExtractPlugin.loader' })
9
+ );
10
+ jest.mock('../shared.config');
11
+ jest.mock('../../utils', () => ({
12
+ ...jest.requireActual('../../utils'),
13
+ isCustomStyleRules: jest.fn(),
14
+ isWebComponent: jest.fn(),
15
+ }));
16
+
17
+ describe('createProductionConfig', () => {
18
+ const sharedConfig = { resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] } };
19
+ let overrides: Parameters<typeof createProductionConfig>[0];
20
+ let options: Parameters<typeof createProductionConfig>[1];
21
+
22
+ beforeEach(() => {
23
+ overrides = {
24
+ configuration: { mode: 'production' },
25
+ plugins: { MiniCssExtractPlugin: { attributes: { foo: 'bar' } } },
26
+ };
27
+ options = { name: '' };
28
+
29
+ jest.resetAllMocks();
30
+ jest.mocked(MiniCssExtractPlugin).mockImplementation(
31
+ (options: Record<string, any>): any => ({ MiniCssExtractPlugin: options })
32
+ );
33
+ jest.mocked(createSharedConfig).mockReturnValue(sharedConfig);
34
+ });
35
+
36
+ const subject = () => createProductionConfig(overrides, options);
37
+
38
+ test('includes shared config', () => {
39
+ expect(subject()).toEqual(expect.objectContaining(sharedConfig));
40
+ });
41
+
42
+ test('includes input configuration', () => {
43
+ expect(subject()).toEqual(expect.objectContaining(overrides.configuration));
44
+ });
45
+
46
+ test('configures "devtool"', () => {
47
+ expect(subject().devtool).toEqual('source-map');
48
+ });
49
+
50
+ test('configures MiniCssExtractPlugin plugin', () => {
51
+ expect(subject().plugins).toContainEqual(
52
+ new MiniCssExtractPlugin({
53
+ ...overrides.plugins?.MiniCssExtractPlugin,
54
+ filename: '[name].[contenthash:8].bundle.css',
55
+ })
56
+ );
57
+ });
58
+
59
+ describe('when package has custom style rules', () => {
60
+ beforeEach(() => jest.mocked(isCustomStyleRules).mockReturnValue(true));
61
+
62
+ test('does not configure "plugins"', () => {
63
+ expect(subject().plugins).toEqual([]);
64
+ });
65
+ });
66
+
67
+ test('configures "output"', () => {
68
+ expect(subject().output).toEqual({
69
+ filename: '[name].[contenthash:8].bundle.js',
70
+ clean: true,
71
+ });
72
+ });
73
+
74
+ describe('with process.env.CLIENT_CDN_PATH', () => {
75
+ const cdnPath = 'http://www.example.com/cdn';
76
+
77
+ beforeEach(() => (process.env.CLIENT_CDN_PATH = cdnPath));
78
+ afterEach(() => delete process.env.CLIENT_CDN_PATH);
79
+
80
+ const subject = () => {
81
+ let result: ReturnType<typeof createProductionConfig>;
82
+
83
+ jest.isolateModules(() => {
84
+ const { createConfig } = require('../production.config');
85
+ result = createConfig(overrides, options);
86
+ });
87
+
88
+ // @ts-expect-error because result is assigned inside callback
89
+ return result;
90
+ };
91
+
92
+ test('configures "output.publicPath"', () => {
93
+ expect(subject().output?.publicPath).toEqual(cdnPath);
94
+ });
95
+ });
96
+
97
+ Object.entries({
98
+ '.css': {
99
+ test: /(\.css)$/,
100
+ exclude: /\.module.css$/,
101
+ use: [MiniCssExtractPlugin.loader, 'css-loader'],
102
+ },
103
+ '.scss': {
104
+ test: /(\.scss)$/,
105
+ exclude: /\.module.scss$/,
106
+ use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
107
+ },
108
+ '.less': {
109
+ test: /(\.less)$/,
110
+ exclude: /\.module.less$/,
111
+ use: [
112
+ MiniCssExtractPlugin.loader,
113
+ 'css-loader',
114
+ {
115
+ loader: 'less-loader',
116
+ options: { lessOptions: { math: 'always' } },
117
+ },
118
+ ],
119
+ },
120
+ '.module.css': {
121
+ test: /\.module.css$/,
122
+ use: [
123
+ {
124
+ loader: MiniCssExtractPlugin.loader,
125
+ options: { esModule: true },
126
+ },
127
+ {
128
+ loader: 'css-loader',
129
+ options: {
130
+ esModule: true,
131
+ modules: {
132
+ localIdentName: '[local]__[hash:base64:5]',
133
+ exportLocalsConvention: 'camelCaseOnly',
134
+ namedExport: true,
135
+ },
136
+ },
137
+ },
138
+ ],
139
+ },
140
+ '.module.scss': {
141
+ test: /\.module.scss$/,
142
+ use: [
143
+ {
144
+ loader: MiniCssExtractPlugin.loader,
145
+ options: { esModule: true },
146
+ },
147
+ {
148
+ loader: 'css-loader',
149
+ options: {
150
+ esModule: true,
151
+ modules: {
152
+ localIdentName: '[local]__[hash:base64:5]',
153
+ exportLocalsConvention: 'camelCaseOnly',
154
+ namedExport: true,
155
+ },
156
+ },
157
+ },
158
+ 'sass-loader',
159
+ ],
160
+ },
161
+ '.module.less': {
162
+ test: /\.module.less$/,
163
+ use: [
164
+ {
165
+ loader: MiniCssExtractPlugin.loader,
166
+ options: { esModule: true },
167
+ },
168
+ {
169
+ loader: 'css-loader',
170
+ options: {
171
+ esModule: true,
172
+ modules: {
173
+ localIdentName: '[local]__[hash:base64:5]',
174
+ exportLocalsConvention: 'camelCaseOnly',
175
+ namedExport: true,
176
+ },
177
+ },
178
+ },
179
+ {
180
+ loader: 'less-loader',
181
+ options: {
182
+ lessOptions: {
183
+ math: 'always',
184
+ },
185
+ },
186
+ },
187
+ ],
188
+ },
189
+ }).forEach(([extension, rules]) => {
190
+ test(`configures "${extension}" rules`, () => {
191
+ expect(subject().module?.rules).toContainEqual(rules);
192
+ });
193
+ });
194
+
195
+ test('configures "optimization"', () => {
196
+ expect(subject().optimization).toEqual({
197
+ runtimeChunk: 'single',
198
+ splitChunks: { cacheGroups: {} },
199
+ });
200
+ });
201
+
202
+ describe('when package is web component', () => {
203
+ beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(true));
204
+
205
+ test('configures "devtool" to false', () => {
206
+ expect(subject().devtool).toBe(false);
207
+ });
208
+
209
+ test('configures "optimization.splitChunks.cacheGroups"', () => {
210
+ expect(subject().optimization?.splitChunks).toEqual({
211
+ cacheGroups: {
212
+ common: {
213
+ name: 'common',
214
+ test: /[\\/]node_modules[\\/](axios|classnames|formstate|mobx|mobx-react|mobx-utils|react|react-dom|react-router|react-router-dom)[\\/]/,
215
+ chunks: 'all',
216
+ },
217
+ kendo: {
218
+ name: 'kendo',
219
+ test: /[\\/]node_modules[\\/](@progress|@telerik)[\\/]/,
220
+ chunks: 'all',
221
+ },
222
+ servicetitan: {
223
+ name: 'servicetitan',
224
+ test: /[\\/]node_modules[\\/]@servicetitan[\\/]/,
225
+ chunks: 'all',
226
+ },
227
+ },
228
+ });
229
+ });
230
+ });
231
+ });