@tramvai/cli 3.12.0 → 3.14.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 (65) hide show
  1. package/lib/api/start/providers/application/shared.d.ts.map +1 -1
  2. package/lib/api/start/providers/application/shared.js +5 -3
  3. package/lib/api/start/providers/application/shared.js.map +1 -1
  4. package/lib/api/start/providers/child-app/shared.d.ts.map +1 -1
  5. package/lib/api/start/providers/child-app/shared.js +6 -4
  6. package/lib/api/start/providers/child-app/shared.js.map +1 -1
  7. package/lib/api/start/providers/module/shared.d.ts.map +1 -1
  8. package/lib/api/start/providers/module/shared.js +6 -4
  9. package/lib/api/start/providers/module/shared.js.map +1 -1
  10. package/lib/api/start/utils/stopServer.d.ts.map +1 -1
  11. package/lib/api/start/utils/stopServer.js.map +1 -1
  12. package/lib/api/start-prod/application.d.ts.map +1 -1
  13. package/lib/api/start-prod/application.js +0 -2
  14. package/lib/api/start-prod/application.js.map +1 -1
  15. package/lib/api/start-prod/providers/shared.d.ts.map +1 -1
  16. package/lib/api/start-prod/providers/shared.js +3 -1
  17. package/lib/api/start-prod/providers/shared.js.map +1 -1
  18. package/lib/commands/analyze/command.d.ts.map +1 -1
  19. package/lib/commands/analyze/command.js +2 -0
  20. package/lib/commands/analyze/command.js.map +1 -1
  21. package/lib/commands/build/command.d.ts.map +1 -1
  22. package/lib/commands/build/command.js +2 -0
  23. package/lib/commands/build/command.js.map +1 -1
  24. package/lib/commands/start/command.d.ts.map +1 -1
  25. package/lib/commands/start/command.js +2 -0
  26. package/lib/commands/start/command.js.map +1 -1
  27. package/lib/commands/start-prod/command.d.ts.map +1 -1
  28. package/lib/commands/start-prod/command.js +2 -0
  29. package/lib/commands/start-prod/command.js.map +1 -1
  30. package/lib/commands/static/application.d.ts.map +1 -1
  31. package/lib/commands/static/application.js +8 -3
  32. package/lib/commands/static/application.js.map +1 -1
  33. package/lib/di/providers/network.d.ts.map +1 -1
  34. package/lib/di/providers/network.js +6 -0
  35. package/lib/di/providers/network.js.map +1 -1
  36. package/lib/models/port-manager.d.ts +21 -7
  37. package/lib/models/port-manager.d.ts.map +1 -1
  38. package/lib/models/port-manager.js +140 -26
  39. package/lib/models/port-manager.js.map +1 -1
  40. package/lib/schema/autogeneratedSchema.json +15 -15
  41. package/lib/validators/commands/checkSwcDependencies.d.ts +3 -0
  42. package/lib/validators/commands/checkSwcDependencies.d.ts.map +1 -0
  43. package/lib/validators/commands/checkSwcDependencies.js +40 -0
  44. package/lib/validators/commands/checkSwcDependencies.js.map +1 -0
  45. package/package.json +5 -2
  46. package/schema.json +15 -15
  47. package/src/api/benchmark/__integration__/start.test.ts +0 -2
  48. package/src/api/start/__integration__/start.test.ts +14 -95
  49. package/src/api/start/providers/application/shared.ts +4 -2
  50. package/src/api/start/providers/child-app/shared.ts +5 -2
  51. package/src/api/start/providers/module/shared.ts +5 -2
  52. package/src/api/start/utils/stopServer.ts +1 -0
  53. package/src/api/start-prod/application.ts +1 -4
  54. package/src/api/start-prod/providers/shared.ts +5 -2
  55. package/src/commands/analyze/command.ts +2 -0
  56. package/src/commands/build/command.ts +2 -0
  57. package/src/commands/start/command.ts +2 -0
  58. package/src/commands/start-prod/command.ts +2 -0
  59. package/src/commands/static/application.ts +10 -4
  60. package/src/di/providers/network.ts +6 -0
  61. package/src/library/swc/__integration__/swc.start.test.ts +0 -2
  62. package/src/models/port-manager.ts +154 -35
  63. package/src/schema/autogeneratedSchema.json +15 -15
  64. package/src/validators/commands/checkSwcDependencies.spec.ts +117 -0
  65. package/src/validators/commands/checkSwcDependencies.ts +40 -0
@@ -1,29 +1,43 @@
1
+ import { access, outputFile, appendFile, readFile } from 'fs-extra';
2
+ import { join } from 'path';
3
+ import findCacheDir from 'find-cache-dir';
1
4
  import detectPort from 'detect-port';
5
+ import { lock } from 'proper-lockfile';
2
6
 
3
7
  import type { ConfigEntry } from '../typings/configEntry/common';
4
8
  import type { Params as StartParams } from '../api/start';
5
9
  import type { Params as StartProdParams } from '../api/start-prod';
10
+ import type { Logger } from './logger';
6
11
 
7
12
  interface NetworkConstructorPayload {
8
13
  configEntry: ConfigEntry;
9
14
  commandParams: StartParams | StartProdParams;
15
+ logger: Logger;
10
16
  }
11
17
 
12
18
  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;
19
+ static readonly DEFAULT_PORT = 3000;
20
+ static readonly DEFAULT_MODULE_PORT = 3040;
21
+ static readonly DEFAULT_STATIC_PORT = 4000;
22
+ static readonly DEFAULT_MODULE_STATIC_PORT = 4040;
17
23
 
18
- private configEntry: ConfigEntry;
19
- private commandParams: StartParams | StartProdParams;
24
+ private readonly configEntry: ConfigEntry;
25
+ private readonly commandParams: StartParams | StartProdParams;
26
+ private readonly cachePath: string;
27
+ private readonly logger: Logger;
20
28
 
21
29
  public port: number | null = null;
22
30
  public staticPort: number | null = null;
23
31
 
24
- constructor({ configEntry, commandParams }: NetworkConstructorPayload) {
32
+ constructor({ configEntry, commandParams, logger }: NetworkConstructorPayload) {
25
33
  this.configEntry = configEntry;
26
34
  this.commandParams = commandParams;
35
+ this.logger = logger;
36
+
37
+ this.cachePath = join(
38
+ findCacheDir({ cwd: __dirname, create: true, name: 'tramvai' }),
39
+ 'used-ports'
40
+ );
27
41
  }
28
42
 
29
43
  /**
@@ -33,47 +47,152 @@ export class PortManager {
33
47
  * Also, handle zero port (it means any random port) as the edge case,
34
48
  * because we must pass a final number to the config manager.
35
49
  */
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);
50
+ public async computeAvailablePorts() {
51
+ await this.createCacheFile();
52
+ const release = await this.lockCacheFile();
53
+
54
+ try {
55
+ if (this.commandParams.port !== undefined && this.commandParams.port !== 0) {
56
+ // @ts-expect-error There is a string actually
57
+ this.port = parseInt(this.commandParams.port, 10);
58
+ }
59
+
60
+ if (this.commandParams.staticPort !== undefined && this.commandParams.staticPort !== 0) {
61
+ // @ts-expect-error There is a string actually
62
+ this.staticPort = parseInt(this.commandParams.staticPort, 10);
63
+ }
64
+
65
+ switch (this.configEntry.type) {
66
+ case 'child-app':
67
+ await this.forChildApp();
68
+ break;
69
+
70
+ case 'module':
71
+ await this.forModule();
72
+ break;
73
+
74
+ case 'application':
75
+ await this.forApplication();
76
+ break;
77
+
78
+ default:
79
+ break;
80
+ }
81
+
82
+ await this.appendCacheFile([this.port, this.staticPort].join(','));
83
+ } catch (error) {
84
+ this.logger.event({
85
+ type: 'warning',
86
+ event: 'PORT_MANAGER:GET_AVAILABLE_PORTS',
87
+ message: `Can't get free ports for ${this.configEntry.type}:`,
88
+ payload: error.message ?? '',
89
+ });
90
+ } finally {
91
+ await release();
40
92
  }
93
+ }
94
+
95
+ /**
96
+ * Cleanup a cache file by removing ports were written previously.
97
+ */
98
+ public async cleanup() {
99
+ const release = await this.lockCacheFile();
41
100
 
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);
101
+ try {
102
+ const cache = await this.readCacheFile();
103
+
104
+ await this.writeCacheFile(
105
+ cache
106
+ .filter(Boolean)
107
+ .filter((port) => port !== this.port.toString() && port !== this.staticPort.toString())
108
+ .join(',')
109
+ );
110
+ } catch (error) {
111
+ this.logger.event({
112
+ type: 'warning',
113
+ event: 'PORT_MANAGER:CLEANUP',
114
+ message: "Can't perform a cleanup of previously used ports:",
115
+ payload: error.message ?? '',
116
+ });
117
+ } finally {
118
+ await release();
45
119
  }
120
+ }
46
121
 
47
- switch (this.configEntry.type) {
48
- case 'child-app':
49
- await this.forChildApp();
50
- break;
122
+ private async forApplication() {
123
+ this.port = this.port ?? (await this.resolveFreePort(PortManager.DEFAULT_PORT));
124
+ this.staticPort =
125
+ this.staticPort ?? (await this.resolveFreePort(PortManager.DEFAULT_STATIC_PORT));
126
+ }
51
127
 
52
- case 'module':
53
- await this.forModule();
54
- break;
128
+ private async forModule() {
129
+ this.port = this.port ?? (await this.resolveFreePort(PortManager.DEFAULT_MODULE_PORT));
130
+ this.staticPort =
131
+ this.staticPort ?? (await this.resolveFreePort(PortManager.DEFAULT_MODULE_STATIC_PORT));
132
+ }
133
+
134
+ private async forChildApp() {
135
+ this.port = this.port ?? (await this.resolveFreePort(PortManager.DEFAULT_MODULE_PORT));
136
+ this.staticPort =
137
+ this.staticPort ?? (await this.resolveFreePort(PortManager.DEFAULT_MODULE_STATIC_PORT));
138
+ }
139
+
140
+ private lockCacheFile() {
141
+ return lock(this.cachePath, { retries: 10 });
142
+ }
143
+
144
+ private async createCacheFile() {
145
+ try {
146
+ await access(this.cachePath);
147
+ } catch (error) {
148
+ await outputFile(this.cachePath, '');
149
+ }
150
+ }
55
151
 
56
- case 'application':
57
- await this.forApplication();
58
- break;
152
+ private async readCacheFile() {
153
+ try {
154
+ const content = await readFile(this.cachePath, { encoding: 'utf-8' });
59
155
 
60
- default:
61
- break;
156
+ return content.split(',');
157
+ } catch (error) {
158
+ return [];
62
159
  }
63
160
  }
64
161
 
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));
162
+ private async writeCacheFile(content: string) {
163
+ await outputFile(this.cachePath, content);
68
164
  }
69
165
 
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));
166
+ private async appendCacheFile(content: string) {
167
+ await appendFile(this.cachePath, `,${content}`);
73
168
  }
74
169
 
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));
170
+ private async resolveFreePort(initial: number) {
171
+ try {
172
+ const cache = await this.readCacheFile();
173
+ let port = await detectPort(initial);
174
+ let attempts = 1;
175
+
176
+ while (cache.includes(port.toString())) {
177
+ if (attempts >= 3) {
178
+ throw new Error(`Max attempts exceeded (${attempts})`);
179
+ }
180
+
181
+ port = await detectPort(0);
182
+
183
+ attempts++;
184
+ }
185
+
186
+ return port;
187
+ } catch (error) {
188
+ this.logger.event({
189
+ type: 'info',
190
+ event: 'PORT_MANAGER:RESOLVE_FREE_PORT',
191
+ message: "Can't resolve a free port, fallback to an initial one:",
192
+ payload: error.message ?? '',
193
+ });
194
+
195
+ return initial;
196
+ }
78
197
  }
79
198
  }
@@ -1144,23 +1144,23 @@
1144
1144
  "dotAll": {
1145
1145
  "type": "boolean"
1146
1146
  },
1147
- "__@match@6838": {
1147
+ "__@match@6850": {
1148
1148
  "type": "object",
1149
1149
  "additionalProperties": false
1150
1150
  },
1151
- "__@replace@6840": {
1151
+ "__@replace@6852": {
1152
1152
  "type": "object",
1153
1153
  "additionalProperties": false
1154
1154
  },
1155
- "__@search@6843": {
1155
+ "__@search@6855": {
1156
1156
  "type": "object",
1157
1157
  "additionalProperties": false
1158
1158
  },
1159
- "__@split@6845": {
1159
+ "__@split@6857": {
1160
1160
  "type": "object",
1161
1161
  "additionalProperties": false
1162
1162
  },
1163
- "__@matchAll@6847": {
1163
+ "__@matchAll@6859": {
1164
1164
  "type": "object",
1165
1165
  "additionalProperties": false
1166
1166
  }
@@ -1814,23 +1814,23 @@
1814
1814
  "dotAll": {
1815
1815
  "type": "boolean"
1816
1816
  },
1817
- "__@match@6838": {
1817
+ "__@match@6850": {
1818
1818
  "type": "object",
1819
1819
  "additionalProperties": false
1820
1820
  },
1821
- "__@replace@6840": {
1821
+ "__@replace@6852": {
1822
1822
  "type": "object",
1823
1823
  "additionalProperties": false
1824
1824
  },
1825
- "__@search@6843": {
1825
+ "__@search@6855": {
1826
1826
  "type": "object",
1827
1827
  "additionalProperties": false
1828
1828
  },
1829
- "__@split@6845": {
1829
+ "__@split@6857": {
1830
1830
  "type": "object",
1831
1831
  "additionalProperties": false
1832
1832
  },
1833
- "__@matchAll@6847": {
1833
+ "__@matchAll@6859": {
1834
1834
  "type": "object",
1835
1835
  "additionalProperties": false
1836
1836
  }
@@ -2484,23 +2484,23 @@
2484
2484
  "dotAll": {
2485
2485
  "type": "boolean"
2486
2486
  },
2487
- "__@match@6838": {
2487
+ "__@match@6850": {
2488
2488
  "type": "object",
2489
2489
  "additionalProperties": false
2490
2490
  },
2491
- "__@replace@6840": {
2491
+ "__@replace@6852": {
2492
2492
  "type": "object",
2493
2493
  "additionalProperties": false
2494
2494
  },
2495
- "__@search@6843": {
2495
+ "__@search@6855": {
2496
2496
  "type": "object",
2497
2497
  "additionalProperties": false
2498
2498
  },
2499
- "__@split@6845": {
2499
+ "__@split@6857": {
2500
2500
  "type": "object",
2501
2501
  "additionalProperties": false
2502
2502
  },
2503
- "__@matchAll@6847": {
2503
+ "__@matchAll@6859": {
2504
2504
  "type": "object",
2505
2505
  "additionalProperties": false
2506
2506
  }
@@ -0,0 +1,117 @@
1
+ import { sync as mockResolve } from 'resolve';
2
+ import type { Context } from '../../models/context';
3
+ import { checkSwcDependencies } from './checkSwcDependencies';
4
+
5
+ const mockSwcIntegration = (version, mockCliVersion, mockRootVersion) => {
6
+ jest.mock(
7
+ `/app/basedir/@tramvai/swc-integration/package.json`,
8
+ () => {
9
+ return { dependencies: { '@swc/core': version } };
10
+ },
11
+ { virtual: true }
12
+ );
13
+
14
+ jest.mock(
15
+ `/app/@swc/core/package.json`,
16
+ () => {
17
+ return { version: mockCliVersion };
18
+ },
19
+ { virtual: true }
20
+ );
21
+
22
+ jest.mock(
23
+ `/app/basedir/@swc/core/package.json`,
24
+ () => {
25
+ return { version: mockRootVersion };
26
+ },
27
+ { virtual: true }
28
+ );
29
+ };
30
+
31
+ jest.mock('resolve', () => ({
32
+ sync: jest
33
+ .fn((name) => require.resolve(name))
34
+ .mockImplementation((name, { basedir = '' } = {}) => {
35
+ if (basedir) {
36
+ return `/app/basedir/${name}`;
37
+ }
38
+
39
+ return `/app/${name}`;
40
+ }),
41
+ }));
42
+
43
+ describe('validators/checkSwcDependencies', () => {
44
+ const mockLog = jest.fn();
45
+ const context = {} as Context;
46
+
47
+ beforeEach(() => {
48
+ mockLog.mockClear();
49
+ jest.resetModules();
50
+ (mockResolve as jest.Mock).mockClear();
51
+ });
52
+
53
+ it('returns ok if there is no @tramvai/swc-integration', async () => {
54
+ expect(await checkSwcDependencies(context, {})).toMatchInlineSnapshot(`
55
+ {
56
+ "name": "checkSwcDependencies",
57
+ "status": "ok",
58
+ }
59
+ `);
60
+ });
61
+
62
+ it('returns ok if there is @tramvai/swc-integration with correct version from root & cli', async () => {
63
+ mockSwcIntegration('1.0.0', '1.0.0', '1.0.0');
64
+
65
+ expect(await checkSwcDependencies(context, {})).toMatchInlineSnapshot(`
66
+ {
67
+ "name": "checkSwcDependencies",
68
+ "status": "ok",
69
+ }
70
+ `);
71
+ });
72
+
73
+ it('returns error if there is @tramvai/swc-integration with different version from root & cli', async () => {
74
+ mockSwcIntegration('1.0.1', '1.0.0', '1.0.0');
75
+
76
+ expect(await checkSwcDependencies(context, {})).toMatchInlineSnapshot(`
77
+ {
78
+ "message": "Version of @swc/core mismatch between
79
+ @tramvai/swc-integration (version: 1.0.1),
80
+ @tramvai/cli (version: 1.0.0) and
81
+ process.cwd() (version: 1.0.0)",
82
+ "name": "checkSwcDependencies",
83
+ "status": "error",
84
+ }
85
+ `);
86
+ });
87
+
88
+ it('returns error if there is correct version between @tramvai/swc-integration and root but different in cli', async () => {
89
+ mockSwcIntegration('1.0.0', '1.0.1', '1.0.0');
90
+
91
+ expect(await checkSwcDependencies(context, {})).toMatchInlineSnapshot(`
92
+ {
93
+ "message": "Version of @swc/core mismatch between
94
+ @tramvai/swc-integration (version: 1.0.0),
95
+ @tramvai/cli (version: 1.0.1) and
96
+ process.cwd() (version: 1.0.0)",
97
+ "name": "checkSwcDependencies",
98
+ "status": "error",
99
+ }
100
+ `);
101
+ });
102
+
103
+ it('returns error if there is correct version between @tramvai/swc-integration and cli but different in root', async () => {
104
+ mockSwcIntegration('1.0.0', '1.0.0', '1.0.1');
105
+
106
+ expect(await checkSwcDependencies(context, {})).toMatchInlineSnapshot(`
107
+ {
108
+ "message": "Version of @swc/core mismatch between
109
+ @tramvai/swc-integration (version: 1.0.0),
110
+ @tramvai/cli (version: 1.0.0) and
111
+ process.cwd() (version: 1.0.1)",
112
+ "name": "checkSwcDependencies",
113
+ "status": "error",
114
+ }
115
+ `);
116
+ });
117
+ });
@@ -0,0 +1,40 @@
1
+ import { sync as resolve } from 'resolve';
2
+ import type { Validator } from './validator.h';
3
+
4
+ export const checkSwcDependencies: Validator = async () => {
5
+ const rootDir = process.cwd();
6
+ const packagePath = `@swc/core/package.json`;
7
+ const pathFromCli = resolve(packagePath);
8
+ const pathFromRoot = resolve(packagePath, { basedir: rootDir });
9
+ const pathFromRootToIntegration = resolve(`@tramvai/swc-integration/package.json`, {
10
+ basedir: rootDir,
11
+ });
12
+
13
+ let versionFromIntegration = '';
14
+ let versionFromRoot = '_from_root_version_';
15
+ let versionFromCli = '_from_cli_version_';
16
+ try {
17
+ versionFromIntegration = require(pathFromRootToIntegration).dependencies['@swc/core'];
18
+ versionFromRoot = require(pathFromRoot).version;
19
+ versionFromCli = require(pathFromCli).version;
20
+ } catch (e) {}
21
+
22
+ const allVersionsAreCorrect =
23
+ versionFromRoot === versionFromCli && versionFromCli === versionFromIntegration;
24
+
25
+ if (!versionFromIntegration || allVersionsAreCorrect) {
26
+ return {
27
+ name: 'checkSwcDependencies',
28
+ status: 'ok',
29
+ };
30
+ }
31
+
32
+ return {
33
+ name: 'checkSwcDependencies',
34
+ status: 'error',
35
+ message: `Version of @swc/core mismatch between
36
+ @tramvai/swc-integration (version: ${versionFromIntegration}),
37
+ @tramvai/cli (version: ${versionFromCli}) and
38
+ process.cwd() (version: ${versionFromRoot})`,
39
+ };
40
+ };