@tramvai/cli 2.147.1 → 2.148.1

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 (83) hide show
  1. package/README.md +3 -3
  2. package/lib/api/build/providers/shared.js +4 -3
  3. package/lib/api/build/providers/shared.js.map +1 -1
  4. package/lib/api/start/application.js.map +1 -1
  5. package/lib/api/start/index.js +5 -2
  6. package/lib/api/start/index.js.map +1 -1
  7. package/lib/api/start/providers/application/shared.js +2 -5
  8. package/lib/api/start/providers/application/shared.js.map +1 -1
  9. package/lib/api/start/providers/child-app/shared.js +2 -2
  10. package/lib/api/start/providers/child-app/shared.js.map +1 -1
  11. package/lib/api/start/providers/module/shared.js +2 -2
  12. package/lib/api/start/providers/module/shared.js.map +1 -1
  13. package/lib/api/start-prod/application.js +2 -0
  14. package/lib/api/start-prod/application.js.map +1 -1
  15. package/lib/api/start-prod/index.js +5 -2
  16. package/lib/api/start-prod/index.js.map +1 -1
  17. package/lib/api/start-prod/providers/application.js +2 -5
  18. package/lib/api/start-prod/providers/application.js.map +1 -1
  19. package/lib/api/start-prod/providers/child-app.js +2 -5
  20. package/lib/api/start-prod/providers/child-app.js.map +1 -1
  21. package/lib/commands/createApp.js +1 -0
  22. package/lib/commands/createApp.js.map +1 -1
  23. package/lib/commands/static/application.js +4 -1
  24. package/lib/commands/static/application.js.map +1 -1
  25. package/lib/config/configManager.d.ts +0 -3
  26. package/lib/config/configManager.js +5 -5
  27. package/lib/config/configManager.js.map +1 -1
  28. package/lib/di/providers/config.js +1 -1
  29. package/lib/di/providers/config.js.map +1 -1
  30. package/lib/di/providers/index.d.ts +1 -0
  31. package/lib/di/providers/index.js +1 -0
  32. package/lib/di/providers/index.js.map +1 -1
  33. package/lib/di/providers/network.d.ts +2 -0
  34. package/lib/di/providers/network.js +17 -0
  35. package/lib/di/providers/network.js.map +1 -0
  36. package/lib/di/tokens/index.d.ts +1 -0
  37. package/lib/di/tokens/index.js +1 -0
  38. package/lib/di/tokens/index.js.map +1 -1
  39. package/lib/di/tokens/network.d.ts +4 -0
  40. package/lib/di/tokens/network.js +6 -0
  41. package/lib/di/tokens/network.js.map +1 -0
  42. package/lib/library/webpack/constants/stats.d.ts +1 -1
  43. package/lib/library/webpack/utils/threadLoader.js +1 -1
  44. package/lib/library/webpack/utils/threadLoader.js.map +1 -1
  45. package/lib/models/config.js +8 -2
  46. package/lib/models/config.js.map +1 -1
  47. package/lib/models/port-manager.d.ts +30 -0
  48. package/lib/models/port-manager.js +72 -0
  49. package/lib/models/port-manager.js.map +1 -0
  50. package/lib/schema/topLevelSchema.d.ts +3 -0
  51. package/lib/schema/topLevelSchema.js +3 -0
  52. package/lib/schema/topLevelSchema.js.map +1 -1
  53. package/lib/utils/detectPortSync.d.ts +0 -13
  54. package/lib/utils/detectPortSync.js +0 -21
  55. package/lib/utils/detectPortSync.js.map +1 -1
  56. package/package.json +4 -5
  57. package/schema.json +3 -0
  58. package/src/api/build/providers/shared.ts +5 -11
  59. package/src/api/start/__integration__/start.test.ts +3 -3
  60. package/src/api/start/application.ts +1 -0
  61. package/src/api/start/index.ts +5 -2
  62. package/src/api/start/providers/application/shared.ts +6 -12
  63. package/src/api/start/providers/child-app/shared.ts +5 -4
  64. package/src/api/start/providers/module/shared.ts +5 -4
  65. package/src/api/start-prod/application.ts +5 -1
  66. package/src/api/start-prod/index.ts +5 -2
  67. package/src/api/start-prod/providers/application.ts +8 -16
  68. package/src/api/start-prod/providers/child-app.ts +6 -7
  69. package/src/commands/createApp.ts +2 -0
  70. package/src/commands/static/application.ts +7 -0
  71. package/src/config/configManager.ts +6 -5
  72. package/src/di/providers/config.ts +1 -1
  73. package/src/di/providers/index.ts +1 -0
  74. package/src/di/providers/network.ts +16 -0
  75. package/src/di/tokens/index.ts +1 -0
  76. package/src/di/tokens/network.ts +5 -0
  77. package/src/library/webpack/utils/threadLoader.ts +1 -1
  78. package/src/models/config.spec.ts +83 -0
  79. package/src/models/config.ts +8 -2
  80. package/src/models/port-manager.ts +79 -0
  81. package/src/schema/topLevelSchema.js +3 -0
  82. package/src/schema/tramvai.spec.ts +8 -2
  83. package/src/utils/detectPortSync.ts +0 -24
@@ -8,6 +8,7 @@ import {
8
8
  configProviders,
9
9
  packageManagerProviders,
10
10
  stdProviders,
11
+ networkProviders,
11
12
  } from '../di/providers';
12
13
  import { COMMAND_MAP_TOKEN, COMMAND_PARAMETERS_TOKEN, COMMAND_RUNNER_TOKEN } from '../di/tokens';
13
14
 
@@ -25,6 +26,7 @@ export const createApp = ({
25
26
  ...commandsProviders,
26
27
  ...packageManagerProviders,
27
28
  ...builderProviders,
29
+ ...networkProviders,
28
30
  ...providers,
29
31
  {
30
32
  provide: COMMAND_PARAMETERS_TOKEN,
@@ -17,6 +17,7 @@ import { startStaticServer } from './staticServer';
17
17
  import { startServer } from './server';
18
18
  import { handleServerOutput } from './utils/handle-server-output';
19
19
  import { appBundleInfo } from '../../utils/dev-app/request';
20
+ import { PortManager } from '../../models/port-manager';
20
21
 
21
22
  // eslint-disable-next-line max-statements
22
23
  export const staticApp = async (
@@ -24,11 +25,17 @@ export const staticApp = async (
24
25
  configEntry: ApplicationConfigEntry,
25
26
  options: Params
26
27
  ): Promise<CommandResult> => {
28
+ const network = new PortManager({ configEntry, commandParams: options });
29
+
30
+ await network.computeAvailablePorts();
31
+
27
32
  const clientConfigManager = createConfigManager(configEntry, {
28
33
  env: 'production',
29
34
  ...options,
30
35
  buildType: 'client',
31
36
  modern: false,
37
+ port: network.port,
38
+ staticPort: network.staticPort,
32
39
  });
33
40
  const serverConfigManager = clientConfigManager.withSettings({ buildType: 'server' });
34
41
 
@@ -13,6 +13,7 @@ import moduleVersion from '../utils/moduleVersion';
13
13
  import { packageVersion } from '../utils/packageVersion';
14
14
  import { showConfig } from './showConfig';
15
15
  import type { Target } from '../typings/target';
16
+ import { PortManager } from '../models/port-manager';
16
17
 
17
18
  // @TODO: maybe split settings depending on env?
18
19
  export interface Settings<E extends Env> {
@@ -92,10 +93,7 @@ export type ConfigManager<
92
93
  assetsPrefix?: string;
93
94
  };
94
95
 
95
- export const DEFAULT_PORT = 3000;
96
96
  export const DEFAULT_STATIC_HOST = 'localhost';
97
- export const DEFAULT_STATIC_PORT = 4000;
98
- export const DEFAULT_STATIC_MODULE_PORT = 4040;
99
97
 
100
98
  // eslint-disable-next-line max-statements, complexity
101
99
  export const createConfigManager = <C extends ConfigEntry = ConfigEntry, E extends Env = Env>(
@@ -153,10 +151,13 @@ export const createConfigManager = <C extends ConfigEntry = ConfigEntry, E exten
153
151
  rootDir,
154
152
  buildType,
155
153
  debug,
156
- port: Number(settings.port ?? DEFAULT_PORT),
154
+ port: Number(settings.port ?? PortManager.DEFAULT_PORT),
157
155
  staticHost: settings.staticHost ?? DEFAULT_STATIC_HOST,
158
156
  staticPort: Number(
159
- settings.staticPort ?? (type === 'module' ? DEFAULT_STATIC_MODULE_PORT : DEFAULT_STATIC_PORT)
157
+ settings.staticPort ??
158
+ (type === 'module'
159
+ ? PortManager.DEFAULT_MODULE_STATIC_PORT
160
+ : PortManager.DEFAULT_STATIC_PORT)
160
161
  ),
161
162
  modern,
162
163
  // eslint-disable-next-line no-nested-ternary
@@ -57,7 +57,7 @@ export const configProviders: readonly Provider[] = [
57
57
  const { content, isSuccessful } = getTramvaiConfig(rootDir);
58
58
 
59
59
  if (!isSuccessful) {
60
- throw new Error('Config neither passed as parameter or found in file system');
60
+ throw new Error('Config neither passed as parameter nor found in file system');
61
61
  }
62
62
 
63
63
  const manager = new ConfigManager({ config: content, syncConfigFile: syncJsonFile });
@@ -4,3 +4,4 @@ export * from './config';
4
4
  export * from './commands';
5
5
  export * from './packageManager';
6
6
  export * from './builder';
7
+ export * from './network';
@@ -0,0 +1,16 @@
1
+ import { provide } from '@tinkoff/dippy';
2
+ import type { Provider } from '@tinkoff/dippy';
3
+
4
+ import { COMMAND_PARAMETERS_TOKEN, CONFIG_ENTRY_TOKEN, PORT_MANAGER_TOKEN } from '../tokens';
5
+ import { PortManager } from '../../models/port-manager';
6
+
7
+ export const networkProviders: readonly Provider[] = [
8
+ provide({
9
+ provide: PORT_MANAGER_TOKEN,
10
+ useClass: PortManager,
11
+ deps: {
12
+ configEntry: CONFIG_ENTRY_TOKEN,
13
+ commandParams: COMMAND_PARAMETERS_TOKEN,
14
+ },
15
+ }),
16
+ ] as const;
@@ -7,3 +7,4 @@ export * from './config';
7
7
  export * from './packageManager';
8
8
  export * from './builder';
9
9
  export * from './server';
10
+ export * from './network';
@@ -0,0 +1,5 @@
1
+ import { createToken } from '@tinkoff/dippy';
2
+
3
+ import type { PortManager } from '../../models/port-manager';
4
+
5
+ export const PORT_MANAGER_TOKEN = createToken<PortManager>('portManager');
@@ -23,7 +23,7 @@ const createWorkerPoolConfig = (configManager: ConfigManager<CliConfigEntry>) =>
23
23
 
24
24
  const isApplicable = (configManager: ConfigManager<CliConfigEntry>) => {
25
25
  return (
26
- // thread-loader uses child_process.fork underhood, and sometimes (50/50) work in these processes does not get into inspector.Session profile
26
+ // thread-loader uses child_process.fork under the hood, and sometimes (50/50) work in these processes does not get into inspector.Session profile
27
27
  !process.env.TRAMVAI_CPU_PROFILE &&
28
28
  // TODO: check that there is still issue with windows systems and thread-loader
29
29
  process.platform !== 'win32' &&
@@ -440,3 +440,86 @@ it('should populate defaults for overridable options', () => {
440
440
  }
441
441
  `);
442
442
  });
443
+
444
+ it('should throw an error if added aditional unknown property', () => {
445
+ const config: any = {
446
+ projects: {
447
+ app: {
448
+ unknownOption: 'something',
449
+ name: 'test-app',
450
+ root: 'src',
451
+ type: 'application',
452
+ output: {
453
+ client: 'assets/compiled',
454
+ },
455
+ sourceMap: false,
456
+ externals: ['test'],
457
+ fileSystemPages: { enabled: true },
458
+ experiments: {
459
+ webpack: {
460
+ backCompat: true,
461
+ },
462
+ transpilation: {
463
+ loader: {
464
+ development: 'swc',
465
+ },
466
+ },
467
+ },
468
+ dedupe: {
469
+ strategy: 'semver',
470
+ },
471
+ define: {
472
+ shared: {
473
+ 'process.env.APP_ID': 'app',
474
+ },
475
+ },
476
+ svgo: {
477
+ plugins: [
478
+ {
479
+ name: 'test-plugin',
480
+ },
481
+ ],
482
+ },
483
+ },
484
+ 'child-app': {
485
+ name: 'test-child-app',
486
+ root: 'packages/child-app',
487
+ type: 'child-app',
488
+ sourceMap: {
489
+ development: true,
490
+ },
491
+ experiments: {
492
+ transpilation: {
493
+ loader: {},
494
+ },
495
+ },
496
+ define: {
497
+ shared: {
498
+ commonProp: 'unknown',
499
+ },
500
+ production: {
501
+ 'process.env.PROD': 'true',
502
+ },
503
+ },
504
+ webpack: {
505
+ resolveAlias: {
506
+ stream: 'browser-stream',
507
+ },
508
+ provide: {
509
+ Buffer: ['buffer', 'Buffer'],
510
+ },
511
+ },
512
+ },
513
+ },
514
+ };
515
+
516
+ expect.assertions(2);
517
+ try {
518
+ const configManager = new ConfigManager({ config, syncConfigFile });
519
+ } catch (e: any) {
520
+ // eslint-disable-next-line jest/no-conditional-expect
521
+ expect(e.message).toMatch('Config validation failed');
522
+ // eslint-disable-next-line jest/no-conditional-expect
523
+ expect(true).toBeTruthy();
524
+ }
525
+ });
@@ -81,9 +81,15 @@ export class ConfigManager {
81
81
  configParameters.projects[projectName] = merge(projectsConfig, entry);
82
82
  });
83
83
 
84
- const ajv = new Ajv({ useDefaults: true });
84
+ const ajv = new Ajv({
85
+ useDefaults: true,
86
+ allowUnionTypes: true,
87
+ strict: true,
88
+ strictSchema: false,
89
+ });
85
90
 
86
- ajv.addKeyword('cli_overridable', {
91
+ ajv.addKeyword({
92
+ keyword: 'cli_overridable',
87
93
  modifying: true,
88
94
  errors: false,
89
95
  validate(schemaValue, currentValue, propSchema) {
@@ -0,0 +1,79 @@
1
+ import detectPort from 'detect-port';
2
+
3
+ import type { ConfigEntry } from '../typings/configEntry/common';
4
+ import type { Params as StartParams } from '../api/start';
5
+ import type { Params as StartProdParams } from '../api/start-prod';
6
+
7
+ interface NetworkConstructorPayload {
8
+ configEntry: ConfigEntry;
9
+ commandParams: StartParams | StartProdParams;
10
+ }
11
+
12
+ export class PortManager {
13
+ static DEFAULT_PORT = 3000;
14
+ static DEFAULT_MODULE_PORT = 4040;
15
+ static DEFAULT_STATIC_PORT = 4000;
16
+ static DEFAULT_MODULE_STATIC_PORT = 4040;
17
+
18
+ private configEntry: ConfigEntry;
19
+ private commandParams: StartParams | StartProdParams;
20
+
21
+ public port: number | null = null;
22
+ public staticPort: number | null = null;
23
+
24
+ constructor({ configEntry, commandParams }: NetworkConstructorPayload) {
25
+ this.configEntry = configEntry;
26
+ this.commandParams = commandParams;
27
+ }
28
+
29
+ /**
30
+ * Try to detect port considering the fact, that if user requests
31
+ * a port explicitly, we should not try to detect a free one.
32
+ *
33
+ * Also, handle zero port (it means any random port) as the edge case,
34
+ * because we must pass a final number to the config manager.
35
+ */
36
+ public async computeAvailablePorts(): Promise<void> {
37
+ if (this.commandParams.port !== undefined && this.commandParams.port !== 0) {
38
+ // @ts-expect-error There is a string actually
39
+ this.port = parseInt(this.commandParams.port, 10);
40
+ }
41
+
42
+ if (this.commandParams.staticPort !== undefined && this.commandParams.staticPort !== 0) {
43
+ // @ts-expect-error There is a string actually
44
+ this.staticPort = parseInt(this.commandParams.staticPort, 10);
45
+ }
46
+
47
+ switch (this.configEntry.type) {
48
+ case 'child-app':
49
+ await this.forChildApp();
50
+ break;
51
+
52
+ case 'module':
53
+ await this.forModule();
54
+ break;
55
+
56
+ case 'application':
57
+ await this.forApplication();
58
+ break;
59
+
60
+ default:
61
+ break;
62
+ }
63
+ }
64
+
65
+ private async forApplication(): Promise<void> {
66
+ this.port = this.port ?? (await detectPort(PortManager.DEFAULT_PORT));
67
+ this.staticPort = this.staticPort ?? (await detectPort(PortManager.DEFAULT_STATIC_PORT));
68
+ }
69
+
70
+ private async forModule(): Promise<void> {
71
+ this.port = this.port ?? (await detectPort(PortManager.DEFAULT_MODULE_PORT));
72
+ this.staticPort = this.staticPort ?? (await detectPort(PortManager.DEFAULT_MODULE_STATIC_PORT));
73
+ }
74
+
75
+ private async forChildApp(): Promise<void> {
76
+ this.port = this.port ?? (await detectPort(PortManager.DEFAULT_MODULE_PORT));
77
+ this.staticPort = this.staticPort ?? (await detectPort(PortManager.DEFAULT_MODULE_STATIC_PORT));
78
+ }
79
+ }
@@ -19,6 +19,7 @@ module.exports = (autogeneratedSchema) => {
19
19
  patternProperties: {
20
20
  '^[$a-zA-Z_-][0-9a-zA-Z_$-]*$': {
21
21
  if: {
22
+ type: 'object',
22
23
  properties: {
23
24
  type: { const: 'application' },
24
25
  },
@@ -26,6 +27,7 @@ module.exports = (autogeneratedSchema) => {
26
27
  then: autogeneratedSchema.properties.application,
27
28
  else: {
28
29
  if: {
30
+ type: 'object',
29
31
  properties: {
30
32
  type: { const: 'module' },
31
33
  },
@@ -33,6 +35,7 @@ module.exports = (autogeneratedSchema) => {
33
35
  then: autogeneratedSchema.properties.module,
34
36
  else: {
35
37
  if: {
38
+ type: 'object',
36
39
  properties: {
37
40
  type: { const: 'child-app' },
38
41
  },
@@ -9,7 +9,7 @@ jest.mock('resolve');
9
9
 
10
10
  describe('JSON schema для tramvai.json', () => {
11
11
  it('Схема успешно компилируется', () => {
12
- const ajv = new Ajv();
12
+ const ajv = new Ajv({ strict: true, strictSchema: false, allowUnionTypes: true });
13
13
 
14
14
  expect(() => ajv.compile(schema)).not.toThrow();
15
15
  });
@@ -35,7 +35,13 @@ describe('JSON schema для tramvai.json', () => {
35
35
 
36
36
  const originalConfig = clone(config);
37
37
 
38
- const ajv = new Ajv({ allErrors: true, useDefaults: true, strictDefaults: 'log' });
38
+ const ajv = new Ajv({
39
+ allErrors: true,
40
+ useDefaults: true,
41
+ strict: true,
42
+ strictSchema: false,
43
+ allowUnionTypes: true,
44
+ });
39
45
  const validate = ajv.compile(schema);
40
46
  const valid = validate(config);
41
47
 
@@ -1,24 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import stripAnsi from 'strip-ansi';
3
-
4
- interface Payload {
5
- request?: number;
6
- fallback: number;
7
- }
8
-
9
- /**
10
- * Try to detect port synchronously considering the fact, that if user requests
11
- * a port explicitly, we should not try to detect a free one.
12
- *
13
- * Also, handle zero port (it means any random port) as the edge case,
14
- * because we must pass a final number to the config manager.
15
- */
16
- export const detectPortSync = ({ request, fallback }: Payload): number => {
17
- if (request !== undefined && request !== 0) {
18
- return request;
19
- }
20
-
21
- const commandResult = execSync(`npx detect-port ${request ?? fallback}`, { encoding: 'utf-8' });
22
-
23
- return parseInt(stripAnsi(commandResult.toString()), 10);
24
- };