@redocly/cli 1.3.0 → 1.4.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.
- package/CHANGELOG.md +19 -0
- package/README.md +15 -7
- package/lib/__mocks__/@redocly/openapi-core.d.ts +1 -0
- package/lib/__mocks__/@redocly/openapi-core.js +4 -3
- package/lib/__mocks__/utils.d.ts +2 -0
- package/lib/__mocks__/utils.js +3 -1
- package/lib/__tests__/commands/build-docs.test.js +2 -2
- package/lib/__tests__/commands/bundle.test.js +7 -7
- package/lib/__tests__/commands/join.test.js +25 -18
- package/lib/__tests__/commands/lint.test.js +15 -15
- package/lib/__tests__/commands/push-region.test.js +2 -2
- package/lib/__tests__/commands/push.test.js +30 -30
- package/lib/__tests__/fetch-with-timeout.test.js +2 -2
- package/lib/__tests__/utils.test.js +63 -32
- package/lib/__tests__/wrapper.test.js +3 -3
- package/lib/assert-node-version.js +1 -1
- package/lib/commands/build-docs/index.js +9 -9
- package/lib/commands/build-docs/types.d.ts +2 -2
- package/lib/commands/build-docs/utils.js +10 -10
- package/lib/commands/bundle.d.ts +1 -1
- package/lib/commands/bundle.js +25 -25
- package/lib/commands/join.d.ts +1 -1
- package/lib/commands/join.js +49 -48
- package/lib/commands/lint.d.ts +1 -1
- package/lib/commands/lint.js +22 -22
- package/lib/commands/login.d.ts +1 -1
- package/lib/commands/login.js +3 -3
- package/lib/commands/preview-docs/index.d.ts +1 -1
- package/lib/commands/preview-docs/index.js +7 -7
- package/lib/commands/preview-docs/preview-server/hot.js +19 -2
- package/lib/commands/preview-docs/preview-server/preview-server.js +15 -14
- package/lib/commands/preview-docs/preview-server/server.d.ts +3 -1
- package/lib/commands/preview-docs/preview-server/server.js +2 -2
- package/lib/commands/push.d.ts +2 -2
- package/lib/commands/push.js +31 -31
- package/lib/commands/split/__tests__/index.test.js +9 -9
- package/lib/commands/split/index.d.ts +2 -2
- package/lib/commands/split/index.js +41 -40
- package/lib/commands/split/types.d.ts +2 -2
- package/lib/commands/split/types.js +2 -2
- package/lib/commands/stats.d.ts +1 -1
- package/lib/commands/stats.js +9 -9
- package/lib/fetch-with-timeout.js +5 -2
- package/lib/index.js +11 -12
- package/lib/types.d.ts +6 -6
- package/lib/update-version-notifier.js +18 -18
- package/lib/utils.d.ts +6 -3
- package/lib/utils.js +66 -38
- package/lib/wrapper.js +5 -5
- package/package.json +4 -3
- package/src/__mocks__/@redocly/openapi-core.ts +1 -0
- package/src/__mocks__/utils.ts +2 -0
- package/src/__tests__/commands/join.test.ts +37 -7
- package/src/__tests__/utils.test.ts +45 -1
- package/src/commands/join.ts +8 -3
- package/src/commands/preview-docs/preview-server/hot.js +19 -2
- package/src/commands/preview-docs/preview-server/preview-server.ts +6 -4
- package/src/commands/preview-docs/preview-server/server.ts +2 -2
- package/src/commands/split/__tests__/index.test.ts +14 -5
- package/src/commands/split/index.ts +25 -17
- package/src/fetch-with-timeout.ts +3 -0
- package/src/index.ts +0 -1
- package/src/utils.ts +40 -1
- package/tsconfig.tsbuildinfo +1 -1
package/lib/utils.js
CHANGED
|
@@ -20,7 +20,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
20
20
|
return t;
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.cleanRawInput = exports.cleanArgs = exports.sendTelemetry = exports.cleanColors = exports.checkIfRulesetExist = exports.sortTopLevelKeysForOas = exports.loadConfigAndHandleErrors = exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printConfigLintTotals = exports.printLintTotals = exports.HandledError = exports.handleError = exports.pluralize = exports.writeYaml = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.langToExt = exports.escapeLanguageName = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackApisOrExit = void 0;
|
|
23
|
+
exports.cleanRawInput = exports.cleanArgs = exports.sendTelemetry = exports.cleanColors = exports.checkIfRulesetExist = exports.sortTopLevelKeysForOas = exports.loadConfigAndHandleErrors = exports.isSubdir = exports.exitWithError = exports.printUnusedWarnings = exports.getOutputFileName = exports.printConfigLintTotals = exports.printLintTotals = exports.HandledError = exports.handleError = exports.pluralize = exports.getAndValidateFileExtension = exports.writeJson = exports.writeYaml = exports.writeToFileByExtension = exports.readYaml = exports.promptUser = exports.saveBundle = exports.dumpBundle = exports.CircularJSONNotSupportedError = exports.langToExt = exports.escapeLanguageName = exports.pathToFilename = exports.printExecutionTime = exports.getExecutionTime = exports.getFallbackApisOrExit = void 0;
|
|
24
24
|
const fetch_with_timeout_1 = require("./fetch-with-timeout");
|
|
25
25
|
const path_1 = require("path");
|
|
26
26
|
const colorette_1 = require("colorette");
|
|
@@ -46,7 +46,7 @@ function getFallbackApisOrExit(argsApis, config) {
|
|
|
46
46
|
const filteredInvalidEntrypoints = res.filter(({ path }) => !isApiPathValid(path));
|
|
47
47
|
if (isNotEmptyArray(filteredInvalidEntrypoints)) {
|
|
48
48
|
for (const { path } of filteredInvalidEntrypoints) {
|
|
49
|
-
process.stderr.write(colorette_1.yellow(`\n${path_1.relative(process.cwd(), path)} ${colorette_1.red(`does not exist or is invalid.\n\n`)}`));
|
|
49
|
+
process.stderr.write((0, colorette_1.yellow)(`\n${(0, path_1.relative)(process.cwd(), path)} ${(0, colorette_1.red)(`does not exist or is invalid.\n\n`)}`));
|
|
50
50
|
}
|
|
51
51
|
exitWithError('Please provide a valid path.');
|
|
52
52
|
}
|
|
@@ -55,7 +55,7 @@ function getFallbackApisOrExit(argsApis, config) {
|
|
|
55
55
|
}
|
|
56
56
|
exports.getFallbackApisOrExit = getFallbackApisOrExit;
|
|
57
57
|
function getConfigDirectory(config) {
|
|
58
|
-
return config.configFile ? path_1.dirname(config.configFile) : process.cwd();
|
|
58
|
+
return config.configFile ? (0, path_1.dirname)(config.configFile) : process.cwd();
|
|
59
59
|
}
|
|
60
60
|
function isNotEmptyArray(args) {
|
|
61
61
|
return Array.isArray(args) && !!args.length;
|
|
@@ -65,11 +65,11 @@ function isApiPathValid(apiPath) {
|
|
|
65
65
|
exitWithError('Path cannot be empty.');
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
return fs.existsSync(apiPath) || openapi_core_1.isAbsoluteUrl(apiPath) ? apiPath : undefined;
|
|
68
|
+
return fs.existsSync(apiPath) || (0, openapi_core_1.isAbsoluteUrl)(apiPath) ? apiPath : undefined;
|
|
69
69
|
}
|
|
70
70
|
function fallbackToAllDefinitions(apis, config) {
|
|
71
71
|
return Object.entries(apis).map(([alias, { root }]) => ({
|
|
72
|
-
path: openapi_core_1.isAbsoluteUrl(root) ? root : path_1.resolve(getConfigDirectory(config), root),
|
|
72
|
+
path: (0, openapi_core_1.isAbsoluteUrl)(root) ? root : (0, path_1.resolve)(getConfigDirectory(config), root),
|
|
73
73
|
alias,
|
|
74
74
|
}));
|
|
75
75
|
}
|
|
@@ -81,14 +81,14 @@ function getAliasOrPath(config, aliasOrPath) {
|
|
|
81
81
|
path: aliasOrPath,
|
|
82
82
|
// find alias by path, take the first match
|
|
83
83
|
alias: (_c = (_b = Object.entries(config.apis).find(([_alias, api]) => {
|
|
84
|
-
return path_1.resolve(api.root) === path_1.resolve(aliasOrPath);
|
|
84
|
+
return (0, path_1.resolve)(api.root) === (0, path_1.resolve)(aliasOrPath);
|
|
85
85
|
})) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : undefined,
|
|
86
86
|
};
|
|
87
87
|
}
|
|
88
88
|
function expandGlobsInEntrypoints(args, config) {
|
|
89
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
90
90
|
return (yield Promise.all(args.map((aliasOrPath) => __awaiter(this, void 0, void 0, function* () {
|
|
91
|
-
return glob.hasMagic(aliasOrPath) && !openapi_core_1.isAbsoluteUrl(aliasOrPath)
|
|
91
|
+
return glob.hasMagic(aliasOrPath) && !(0, openapi_core_1.isAbsoluteUrl)(aliasOrPath)
|
|
92
92
|
? (yield glob.__promisify__(aliasOrPath)).map((g) => getAliasOrPath(config, g))
|
|
93
93
|
: getAliasOrPath(config, aliasOrPath);
|
|
94
94
|
})))).flat();
|
|
@@ -102,7 +102,7 @@ function getExecutionTime(startedAt) {
|
|
|
102
102
|
exports.getExecutionTime = getExecutionTime;
|
|
103
103
|
function printExecutionTime(commandName, startedAt, api) {
|
|
104
104
|
const elapsed = getExecutionTime(startedAt);
|
|
105
|
-
process.stderr.write(colorette_1.gray(`\n${api}: ${commandName} processed in ${elapsed}\n\n`));
|
|
105
|
+
process.stderr.write((0, colorette_1.gray)(`\n${api}: ${commandName} processed in ${elapsed}\n\n`));
|
|
106
106
|
}
|
|
107
107
|
exports.printExecutionTime = printExecutionTime;
|
|
108
108
|
function pathToFilename(path, pathSeparator) {
|
|
@@ -153,7 +153,7 @@ function dumpBundle(obj, format, dereference) {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
else {
|
|
156
|
-
return openapi_core_1.stringifyYaml(obj, {
|
|
156
|
+
return (0, openapi_core_1.stringifyYaml)(obj, {
|
|
157
157
|
noRefs: !dereference,
|
|
158
158
|
lineWidth: -1,
|
|
159
159
|
});
|
|
@@ -161,7 +161,7 @@ function dumpBundle(obj, format, dereference) {
|
|
|
161
161
|
}
|
|
162
162
|
exports.dumpBundle = dumpBundle;
|
|
163
163
|
function saveBundle(filename, output) {
|
|
164
|
-
fs.mkdirSync(path_1.dirname(filename), { recursive: true });
|
|
164
|
+
fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
|
|
165
165
|
fs.writeFileSync(filename, output);
|
|
166
166
|
}
|
|
167
167
|
exports.saveBundle = saveBundle;
|
|
@@ -196,19 +196,47 @@ function promptUser(query, hideUserInput = false) {
|
|
|
196
196
|
}
|
|
197
197
|
exports.promptUser = promptUser;
|
|
198
198
|
function readYaml(filename) {
|
|
199
|
-
return openapi_core_1.parseYaml(fs.readFileSync(filename, 'utf-8'), { filename });
|
|
199
|
+
return (0, openapi_core_1.parseYaml)(fs.readFileSync(filename, 'utf-8'), { filename });
|
|
200
200
|
}
|
|
201
201
|
exports.readYaml = readYaml;
|
|
202
|
+
function writeToFileByExtension(data, filePath, noRefs) {
|
|
203
|
+
const ext = getAndValidateFileExtension(filePath);
|
|
204
|
+
if (ext === 'json') {
|
|
205
|
+
writeJson(data, filePath);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
writeYaml(data, filePath, noRefs);
|
|
209
|
+
}
|
|
210
|
+
exports.writeToFileByExtension = writeToFileByExtension;
|
|
202
211
|
function writeYaml(data, filename, noRefs = false) {
|
|
203
|
-
const content = openapi_core_1.stringifyYaml(data, { noRefs });
|
|
212
|
+
const content = (0, openapi_core_1.stringifyYaml)(data, { noRefs });
|
|
204
213
|
if (process.env.NODE_ENV === 'test') {
|
|
205
214
|
process.stderr.write(content);
|
|
206
215
|
return;
|
|
207
216
|
}
|
|
208
|
-
fs.mkdirSync(path_1.dirname(filename), { recursive: true });
|
|
217
|
+
fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
|
|
209
218
|
fs.writeFileSync(filename, content);
|
|
210
219
|
}
|
|
211
220
|
exports.writeYaml = writeYaml;
|
|
221
|
+
function writeJson(data, filename) {
|
|
222
|
+
const content = JSON.stringify(data, null, 2);
|
|
223
|
+
if (process.env.NODE_ENV === 'test') {
|
|
224
|
+
process.stderr.write(content);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
fs.mkdirSync((0, path_1.dirname)(filename), { recursive: true });
|
|
228
|
+
fs.writeFileSync(filename, content);
|
|
229
|
+
}
|
|
230
|
+
exports.writeJson = writeJson;
|
|
231
|
+
function getAndValidateFileExtension(fileName) {
|
|
232
|
+
const ext = fileName.split('.').pop();
|
|
233
|
+
if (['yaml', 'yml', 'json'].includes(ext)) {
|
|
234
|
+
return ext;
|
|
235
|
+
}
|
|
236
|
+
process.stderr.write((0, colorette_1.yellow)(`Unsupported file extension: ${ext}. Using yaml.\n`));
|
|
237
|
+
return 'yaml';
|
|
238
|
+
}
|
|
239
|
+
exports.getAndValidateFileExtension = getAndValidateFileExtension;
|
|
212
240
|
function pluralize(label, num) {
|
|
213
241
|
if (label.endsWith('is')) {
|
|
214
242
|
[label] = label.split(' ');
|
|
@@ -229,7 +257,7 @@ function handleError(e, ref) {
|
|
|
229
257
|
return exitWithError(`Failed to parse API description at ${ref}:\n\n - ${e.message}.`);
|
|
230
258
|
case CircularJSONNotSupportedError: {
|
|
231
259
|
return exitWithError(`Detected circular reference which can't be converted to JSON.\n` +
|
|
232
|
-
`Try to use ${colorette_1.blue('yaml')} output or remove ${colorette_1.blue('--dereferenced')}.`);
|
|
260
|
+
`Try to use ${(0, colorette_1.blue)('yaml')} output or remove ${(0, colorette_1.blue)('--dereferenced')}.`);
|
|
233
261
|
}
|
|
234
262
|
case SyntaxError:
|
|
235
263
|
return exitWithError(`Syntax error: ${e.message} ${(_b = (_a = e.stack) === null || _a === void 0 ? void 0 : _a.split('\n\n')) === null || _b === void 0 ? void 0 : _b[0]}`);
|
|
@@ -246,32 +274,32 @@ class HandledError extends Error {
|
|
|
246
274
|
exports.HandledError = HandledError;
|
|
247
275
|
function printLintTotals(totals, definitionsCount) {
|
|
248
276
|
const ignored = totals.ignored
|
|
249
|
-
? colorette_1.yellow(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
|
|
277
|
+
? (0, colorette_1.yellow)(`${totals.ignored} ${pluralize('problem is', totals.ignored)} explicitly ignored.\n\n`)
|
|
250
278
|
: '';
|
|
251
279
|
if (totals.errors > 0) {
|
|
252
|
-
process.stderr.write(colorette_1.red(`❌ Validation failed with ${totals.errors} ${pluralize('error', totals.errors)}${totals.warnings > 0
|
|
280
|
+
process.stderr.write((0, colorette_1.red)(`❌ Validation failed with ${totals.errors} ${pluralize('error', totals.errors)}${totals.warnings > 0
|
|
253
281
|
? ` and ${totals.warnings} ${pluralize('warning', totals.warnings)}`
|
|
254
282
|
: ''}.\n${ignored}`));
|
|
255
283
|
}
|
|
256
284
|
else if (totals.warnings > 0) {
|
|
257
|
-
process.stderr.write(colorette_1.green(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n`));
|
|
258
|
-
process.stderr.write(colorette_1.yellow(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`));
|
|
285
|
+
process.stderr.write((0, colorette_1.green)(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n`));
|
|
286
|
+
process.stderr.write((0, colorette_1.yellow)(`You have ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n${ignored}`));
|
|
259
287
|
}
|
|
260
288
|
else {
|
|
261
|
-
process.stderr.write(colorette_1.green(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n${ignored}`));
|
|
289
|
+
process.stderr.write((0, colorette_1.green)(`Woohoo! Your API ${pluralize('description is', definitionsCount)} valid. 🎉\n${ignored}`));
|
|
262
290
|
}
|
|
263
291
|
if (totals.errors > 0) {
|
|
264
|
-
process.stderr.write(colorette_1.gray(`run \`redocly lint --generate-ignore-file\` to add all problems to the ignore file.\n`));
|
|
292
|
+
process.stderr.write((0, colorette_1.gray)(`run \`redocly lint --generate-ignore-file\` to add all problems to the ignore file.\n`));
|
|
265
293
|
}
|
|
266
294
|
process.stderr.write('\n');
|
|
267
295
|
}
|
|
268
296
|
exports.printLintTotals = printLintTotals;
|
|
269
297
|
function printConfigLintTotals(totals) {
|
|
270
298
|
if (totals.errors > 0) {
|
|
271
|
-
process.stderr.write(colorette_1.red(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.`));
|
|
299
|
+
process.stderr.write((0, colorette_1.red)(`❌ Your config has ${totals.errors} ${pluralize('error', totals.errors)}.`));
|
|
272
300
|
}
|
|
273
301
|
else if (totals.warnings > 0) {
|
|
274
|
-
process.stderr.write(colorette_1.yellow(`⚠️ Your config has ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n`));
|
|
302
|
+
process.stderr.write((0, colorette_1.yellow)(`⚠️ Your config has ${totals.warnings} ${pluralize('warning', totals.warnings)}.\n`));
|
|
275
303
|
}
|
|
276
304
|
}
|
|
277
305
|
exports.printConfigLintTotals = printConfigLintTotals;
|
|
@@ -281,21 +309,21 @@ function getOutputFileName(entrypoint, entries, output, ext) {
|
|
|
281
309
|
}
|
|
282
310
|
let outputFile = output;
|
|
283
311
|
if (entries > 1) {
|
|
284
|
-
ext = ext || path_1.extname(entrypoint).substring(1);
|
|
312
|
+
ext = ext || (0, path_1.extname)(entrypoint).substring(1);
|
|
285
313
|
if (!types_1.outputExtensions.includes(ext)) {
|
|
286
314
|
throw new Error(`Invalid file extension: ${ext}.`);
|
|
287
315
|
}
|
|
288
|
-
outputFile = path_1.join(output, path_1.basename(entrypoint, path_1.extname(entrypoint))) + '.' + ext;
|
|
316
|
+
outputFile = (0, path_1.join)(output, (0, path_1.basename)(entrypoint, (0, path_1.extname)(entrypoint))) + '.' + ext;
|
|
289
317
|
}
|
|
290
318
|
else {
|
|
291
319
|
if (output) {
|
|
292
|
-
ext = ext || path_1.extname(output).substring(1);
|
|
320
|
+
ext = ext || (0, path_1.extname)(output).substring(1);
|
|
293
321
|
}
|
|
294
|
-
ext = ext || path_1.extname(entrypoint).substring(1);
|
|
322
|
+
ext = ext || (0, path_1.extname)(entrypoint).substring(1);
|
|
295
323
|
if (!types_1.outputExtensions.includes(ext)) {
|
|
296
324
|
throw new Error(`Invalid file extension: ${ext}.`);
|
|
297
325
|
}
|
|
298
|
-
outputFile = path_1.join(path_1.dirname(outputFile), path_1.basename(outputFile, path_1.extname(outputFile))) + '.' + ext;
|
|
326
|
+
outputFile = (0, path_1.join)((0, path_1.dirname)(outputFile), (0, path_1.basename)(outputFile, (0, path_1.extname)(outputFile))) + '.' + ext;
|
|
299
327
|
}
|
|
300
328
|
return { outputFile, ext };
|
|
301
329
|
}
|
|
@@ -303,13 +331,13 @@ exports.getOutputFileName = getOutputFileName;
|
|
|
303
331
|
function printUnusedWarnings(config) {
|
|
304
332
|
const { preprocessors, rules, decorators } = config.getUnusedRules();
|
|
305
333
|
if (rules.length) {
|
|
306
|
-
process.stderr.write(colorette_1.yellow(`[WARNING] Unused rules found in ${colorette_1.blue(config.configFile || '')}: ${rules.join(', ')}.\n`));
|
|
334
|
+
process.stderr.write((0, colorette_1.yellow)(`[WARNING] Unused rules found in ${(0, colorette_1.blue)(config.configFile || '')}: ${rules.join(', ')}.\n`));
|
|
307
335
|
}
|
|
308
336
|
if (preprocessors.length) {
|
|
309
|
-
process.stderr.write(colorette_1.yellow(`[WARNING] Unused preprocessors found in ${colorette_1.blue(config.configFile || '')}: ${preprocessors.join(', ')}.\n`));
|
|
337
|
+
process.stderr.write((0, colorette_1.yellow)(`[WARNING] Unused preprocessors found in ${(0, colorette_1.blue)(config.configFile || '')}: ${preprocessors.join(', ')}.\n`));
|
|
310
338
|
}
|
|
311
339
|
if (decorators.length) {
|
|
312
|
-
process.stderr.write(colorette_1.yellow(`[WARNING] Unused decorators found in ${colorette_1.blue(config.configFile || '')}: ${decorators.join(', ')}.\n`));
|
|
340
|
+
process.stderr.write((0, colorette_1.yellow)(`[WARNING] Unused decorators found in ${(0, colorette_1.blue)(config.configFile || '')}: ${decorators.join(', ')}.\n`));
|
|
313
341
|
}
|
|
314
342
|
if (rules.length || preprocessors.length) {
|
|
315
343
|
process.stderr.write(`Check the spelling and verify the added plugin prefix.\n`);
|
|
@@ -317,7 +345,7 @@ function printUnusedWarnings(config) {
|
|
|
317
345
|
}
|
|
318
346
|
exports.printUnusedWarnings = printUnusedWarnings;
|
|
319
347
|
function exitWithError(message) {
|
|
320
|
-
process.stderr.write(colorette_1.red(message) + '\n\n');
|
|
348
|
+
process.stderr.write((0, colorette_1.red)(message) + '\n\n');
|
|
321
349
|
throw new HandledError(message);
|
|
322
350
|
}
|
|
323
351
|
exports.exitWithError = exitWithError;
|
|
@@ -325,14 +353,14 @@ exports.exitWithError = exitWithError;
|
|
|
325
353
|
* Checks if dir is subdir of parent
|
|
326
354
|
*/
|
|
327
355
|
function isSubdir(parent, dir) {
|
|
328
|
-
const relativePath = path_1.relative(parent, dir);
|
|
329
|
-
return !!relativePath && !/^..($|\/)/.test(relativePath) && !path_1.isAbsolute(relativePath);
|
|
356
|
+
const relativePath = (0, path_1.relative)(parent, dir);
|
|
357
|
+
return !!relativePath && !/^..($|\/)/.test(relativePath) && !(0, path_1.isAbsolute)(relativePath);
|
|
330
358
|
}
|
|
331
359
|
exports.isSubdir = isSubdir;
|
|
332
360
|
function loadConfigAndHandleErrors(options = {}) {
|
|
333
361
|
return __awaiter(this, void 0, void 0, function* () {
|
|
334
362
|
try {
|
|
335
|
-
return yield openapi_core_1.loadConfig(options);
|
|
363
|
+
return yield (0, openapi_core_1.loadConfig)(options);
|
|
336
364
|
}
|
|
337
365
|
catch (e) {
|
|
338
366
|
handleError(e, '');
|
|
@@ -399,7 +427,7 @@ function sortOas3Keys(document) {
|
|
|
399
427
|
}
|
|
400
428
|
function checkIfRulesetExist(rules) {
|
|
401
429
|
const ruleset = Object.assign(Object.assign(Object.assign({}, rules.oas2), rules.oas3_0), rules.oas3_0);
|
|
402
|
-
if (utils_1.isEmptyObject(ruleset)) {
|
|
430
|
+
if ((0, utils_1.isEmptyObject)(ruleset)) {
|
|
403
431
|
exitWithError('⚠️ No rules were configured. Learn how to configure rules: https://redocly.com/docs/cli/rules/');
|
|
404
432
|
}
|
|
405
433
|
}
|
|
@@ -426,7 +454,7 @@ function sendTelemetry(argv, exit_code, has_config) {
|
|
|
426
454
|
command,
|
|
427
455
|
arguments: cleanArgs(args),
|
|
428
456
|
node_version: process.version,
|
|
429
|
-
npm_version: child_process_1.execSync('npm -v').toString().replace('\n', ''),
|
|
457
|
+
npm_version: (0, child_process_1.execSync)('npm -v').toString().replace('\n', ''),
|
|
430
458
|
version: update_version_notifier_1.version,
|
|
431
459
|
exit_code,
|
|
432
460
|
environment: process.env.REDOCLY_ENVIRONMENT,
|
|
@@ -434,7 +462,7 @@ function sendTelemetry(argv, exit_code, has_config) {
|
|
|
434
462
|
raw_input: cleanRawInput(process.argv.slice(2)),
|
|
435
463
|
has_config,
|
|
436
464
|
};
|
|
437
|
-
yield fetch_with_timeout_1.default(`https://api.redocly.com/registry/telemetry/cli`, {
|
|
465
|
+
yield (0, fetch_with_timeout_1.default)(`https://api.redocly.com/registry/telemetry/cli`, {
|
|
438
466
|
method: 'POST',
|
|
439
467
|
headers: {
|
|
440
468
|
'content-type': 'application/json',
|
|
@@ -458,7 +486,7 @@ function cleanString(value) {
|
|
|
458
486
|
if (!value) {
|
|
459
487
|
return value;
|
|
460
488
|
}
|
|
461
|
-
if (openapi_core_1.isAbsoluteUrl(value)) {
|
|
489
|
+
if ((0, openapi_core_1.isAbsoluteUrl)(value)) {
|
|
462
490
|
return value.split('://')[0] + '://url';
|
|
463
491
|
}
|
|
464
492
|
if (isFile(value)) {
|
package/lib/wrapper.js
CHANGED
|
@@ -20,15 +20,15 @@ function commandWrapper(commandHandler) {
|
|
|
20
20
|
let hasConfig;
|
|
21
21
|
let telemetry;
|
|
22
22
|
try {
|
|
23
|
-
if (argv.config && !openapi_core_1.doesYamlFileExist(argv.config)) {
|
|
24
|
-
utils_1.exitWithError('Please, provide valid path to the configuration file');
|
|
23
|
+
if (argv.config && !(0, openapi_core_1.doesYamlFileExist)(argv.config)) {
|
|
24
|
+
(0, utils_1.exitWithError)('Please, provide valid path to the configuration file');
|
|
25
25
|
}
|
|
26
|
-
const config = (yield utils_1.loadConfigAndHandleErrors({
|
|
26
|
+
const config = (yield (0, utils_1.loadConfigAndHandleErrors)({
|
|
27
27
|
configPath: argv.config,
|
|
28
28
|
customExtends: argv.extends,
|
|
29
29
|
region: argv.region,
|
|
30
30
|
files: argv.files,
|
|
31
|
-
processRawConfig: lint_1.lintConfigCallback(argv, update_version_notifier_1.version),
|
|
31
|
+
processRawConfig: (0, lint_1.lintConfigCallback)(argv, update_version_notifier_1.version),
|
|
32
32
|
}));
|
|
33
33
|
telemetry = config.telemetry;
|
|
34
34
|
hasConfig = !config.styleguide.recommendedFallback;
|
|
@@ -41,7 +41,7 @@ function commandWrapper(commandHandler) {
|
|
|
41
41
|
}
|
|
42
42
|
finally {
|
|
43
43
|
if (process.env.REDOCLY_TELEMETRY !== 'off' && telemetry !== 'off') {
|
|
44
|
-
yield utils_1.sendTelemetry(argv, code, hasConfig);
|
|
44
|
+
yield (0, utils_1.sendTelemetry)(argv, code, hasConfig);
|
|
45
45
|
}
|
|
46
46
|
process.once('beforeExit', () => {
|
|
47
47
|
process.exit(code);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@redocly/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"Roman Hotsiy <roman@redoc.ly> (https://redoc.ly/)"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@redocly/openapi-core": "1.
|
|
39
|
+
"@redocly/openapi-core": "1.4.1",
|
|
40
40
|
"chokidar": "^3.5.1",
|
|
41
41
|
"colorette": "^1.2.0",
|
|
42
42
|
"core-js": "^3.32.1",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"glob": "^7.1.6",
|
|
45
45
|
"handlebars": "^4.7.6",
|
|
46
46
|
"mobx": "^6.0.4",
|
|
47
|
+
"node-fetch": "^2.6.1",
|
|
47
48
|
"react": "^17.0.0 || ^18.2.0",
|
|
48
49
|
"react-dom": "^17.0.0 || ^18.2.0",
|
|
49
50
|
"redoc": "~2.1.2",
|
|
@@ -59,6 +60,6 @@
|
|
|
59
60
|
"@types/react-dom": "^17.0.0 || ^18.2.7",
|
|
60
61
|
"@types/semver": "^7.5.0",
|
|
61
62
|
"@types/yargs": "17.0.5",
|
|
62
|
-
"typescript": "^
|
|
63
|
+
"typescript": "^5.2.2"
|
|
63
64
|
}
|
|
64
65
|
}
|
|
@@ -31,6 +31,7 @@ export const doesYamlFileExist = jest.fn();
|
|
|
31
31
|
export const bundleDocument = jest.fn(() => Promise.resolve({ problems: {} }));
|
|
32
32
|
export const detectSpec = jest.fn();
|
|
33
33
|
export const isAbsoluteUrl = jest.fn();
|
|
34
|
+
export const stringifyYaml = jest.fn((data) => data);
|
|
34
35
|
|
|
35
36
|
export class BaseResolver {
|
|
36
37
|
cache = new Map<string, Promise<Document | ResolveError>>();
|
package/src/__mocks__/utils.ts
CHANGED
|
@@ -17,3 +17,5 @@ export const writeYaml = jest.fn();
|
|
|
17
17
|
export const loadConfigAndHandleErrors = jest.fn(() => ConfigFixture);
|
|
18
18
|
export const checkIfRulesetExist = jest.fn();
|
|
19
19
|
export const sortTopLevelKeysForOas = jest.fn((document) => document);
|
|
20
|
+
export const getAndValidateFileExtension = jest.fn((fileName: string) => fileName.split('.').pop());
|
|
21
|
+
export const writeToFileByExtension = jest.fn();
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { handleJoin } from '../../commands/join';
|
|
2
|
-
import { exitWithError, writeYaml } from '../../utils';
|
|
2
|
+
import { exitWithError, writeToFileByExtension, writeYaml } from '../../utils';
|
|
3
3
|
import { yellow } from 'colorette';
|
|
4
4
|
import { detectSpec } from '@redocly/openapi-core';
|
|
5
5
|
import { loadConfig } from '../../__mocks__/@redocly/openapi-core';
|
|
6
6
|
import { ConfigFixture } from '../fixtures/config';
|
|
7
7
|
|
|
8
8
|
jest.mock('../../utils');
|
|
9
|
+
|
|
9
10
|
jest.mock('colorette');
|
|
10
11
|
|
|
11
12
|
describe('handleJoin fails', () => {
|
|
@@ -80,7 +81,7 @@ describe('handleJoin fails', () => {
|
|
|
80
81
|
);
|
|
81
82
|
});
|
|
82
83
|
|
|
83
|
-
it('should call
|
|
84
|
+
it('should call writeToFileByExtension function', async () => {
|
|
84
85
|
(detectSpec as jest.Mock).mockReturnValue('oas3_0');
|
|
85
86
|
await handleJoin(
|
|
86
87
|
{
|
|
@@ -90,10 +91,14 @@ describe('handleJoin fails', () => {
|
|
|
90
91
|
'cli-version'
|
|
91
92
|
);
|
|
92
93
|
|
|
93
|
-
expect(
|
|
94
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
95
|
+
expect.any(Object),
|
|
96
|
+
'openapi.yaml',
|
|
97
|
+
expect.any(Boolean)
|
|
98
|
+
);
|
|
94
99
|
});
|
|
95
100
|
|
|
96
|
-
it('should call
|
|
101
|
+
it('should call writeToFileByExtension function for OpenAPI 3.1', async () => {
|
|
97
102
|
(detectSpec as jest.Mock).mockReturnValue('oas3_1');
|
|
98
103
|
await handleJoin(
|
|
99
104
|
{
|
|
@@ -103,10 +108,14 @@ describe('handleJoin fails', () => {
|
|
|
103
108
|
'cli-version'
|
|
104
109
|
);
|
|
105
110
|
|
|
106
|
-
expect(
|
|
111
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
112
|
+
expect.any(Object),
|
|
113
|
+
'openapi.yaml',
|
|
114
|
+
expect.any(Boolean)
|
|
115
|
+
);
|
|
107
116
|
});
|
|
108
117
|
|
|
109
|
-
it('should call
|
|
118
|
+
it('should call writeToFileByExtension function with custom output file', async () => {
|
|
110
119
|
(detectSpec as jest.Mock).mockReturnValue('oas3_0');
|
|
111
120
|
await handleJoin(
|
|
112
121
|
{
|
|
@@ -117,7 +126,28 @@ describe('handleJoin fails', () => {
|
|
|
117
126
|
'cli-version'
|
|
118
127
|
);
|
|
119
128
|
|
|
120
|
-
expect(
|
|
129
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
130
|
+
expect.any(Object),
|
|
131
|
+
'output.yml',
|
|
132
|
+
expect.any(Boolean)
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should call writeToFileByExtension function with json file extension', async () => {
|
|
137
|
+
(detectSpec as jest.Mock).mockReturnValue('oas3_0');
|
|
138
|
+
await handleJoin(
|
|
139
|
+
{
|
|
140
|
+
apis: ['first.json', 'second.yaml'],
|
|
141
|
+
},
|
|
142
|
+
ConfigFixture as any,
|
|
143
|
+
'cli-version'
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(writeToFileByExtension).toHaveBeenCalledWith(
|
|
147
|
+
expect.any(Object),
|
|
148
|
+
'openapi.json',
|
|
149
|
+
expect.any(Boolean)
|
|
150
|
+
);
|
|
121
151
|
});
|
|
122
152
|
|
|
123
153
|
it('should call skipDecorators and skipPreprocessors', async () => {
|
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
HandledError,
|
|
13
13
|
cleanArgs,
|
|
14
14
|
cleanRawInput,
|
|
15
|
+
getAndValidateFileExtension,
|
|
16
|
+
writeYaml,
|
|
17
|
+
writeJson,
|
|
18
|
+
writeToFileByExtension,
|
|
15
19
|
} from '../utils';
|
|
16
20
|
import {
|
|
17
21
|
ResolvedApi,
|
|
@@ -19,11 +23,13 @@ import {
|
|
|
19
23
|
isAbsoluteUrl,
|
|
20
24
|
ResolveError,
|
|
21
25
|
YamlParseError,
|
|
26
|
+
stringifyYaml,
|
|
22
27
|
} from '@redocly/openapi-core';
|
|
23
28
|
import { blue, red, yellow } from 'colorette';
|
|
24
|
-
import { existsSync, statSync } from 'fs';
|
|
29
|
+
import { existsSync, statSync, writeFileSync } from 'fs';
|
|
25
30
|
import * as path from 'path';
|
|
26
31
|
import * as process from 'process';
|
|
32
|
+
import * as utils from '../utils';
|
|
27
33
|
|
|
28
34
|
jest.mock('os');
|
|
29
35
|
jest.mock('colorette');
|
|
@@ -554,4 +560,42 @@ describe('cleanRawInput', () => {
|
|
|
554
560
|
'redocly lint file-json --format stylish --extends=minimal --skip-rule operation-4xx-response'
|
|
555
561
|
);
|
|
556
562
|
});
|
|
563
|
+
|
|
564
|
+
describe('validateFileExtension', () => {
|
|
565
|
+
it('should return current file extension', () => {
|
|
566
|
+
expect(getAndValidateFileExtension('test.json')).toEqual('json');
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('should return yaml and print warning if file extension does not supported', () => {
|
|
570
|
+
const stderrMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
571
|
+
(yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
|
|
572
|
+
|
|
573
|
+
expect(getAndValidateFileExtension('test.xml')).toEqual('yaml');
|
|
574
|
+
expect(stderrMock).toHaveBeenCalledWith(`Unsupported file extension: xml. Using yaml.\n`);
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
describe('writeToFileByExtension', () => {
|
|
579
|
+
beforeEach(() => {
|
|
580
|
+
jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn());
|
|
581
|
+
(yellow as jest.Mock<any, any>).mockImplementation((text: string) => text);
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
afterEach(() => {
|
|
585
|
+
jest.restoreAllMocks();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it('should call stringifyYaml function', () => {
|
|
589
|
+
writeToFileByExtension('test data', 'test.yaml');
|
|
590
|
+
expect(stringifyYaml).toHaveBeenCalledWith('test data', { noRefs: false });
|
|
591
|
+
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('should call JSON.stringify function', () => {
|
|
595
|
+
const stringifySpy = jest.spyOn(JSON, 'stringify').mockImplementation((data) => data);
|
|
596
|
+
writeToFileByExtension('test data', 'test.json');
|
|
597
|
+
expect(stringifySpy).toHaveBeenCalledWith('test data', null, 2);
|
|
598
|
+
expect(process.stderr.write).toHaveBeenCalledWith(`test data`);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
557
601
|
});
|
package/src/commands/join.ts
CHANGED
|
@@ -25,9 +25,10 @@ import {
|
|
|
25
25
|
printExecutionTime,
|
|
26
26
|
handleError,
|
|
27
27
|
printLintTotals,
|
|
28
|
-
writeYaml,
|
|
29
28
|
exitWithError,
|
|
30
29
|
sortTopLevelKeysForOas,
|
|
30
|
+
getAndValidateFileExtension,
|
|
31
|
+
writeToFileByExtension,
|
|
31
32
|
} from '../utils';
|
|
32
33
|
import { isObject, isString, keysOf } from '../js-utils';
|
|
33
34
|
import {
|
|
@@ -70,16 +71,19 @@ export type JoinOptions = {
|
|
|
70
71
|
|
|
71
72
|
export async function handleJoin(argv: JoinOptions, config: Config, packageVersion: string) {
|
|
72
73
|
const startedAt = performance.now();
|
|
74
|
+
|
|
73
75
|
if (argv.apis.length < 2) {
|
|
74
76
|
return exitWithError(`At least 2 apis should be provided. \n\n`);
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
const fileExtension = getAndValidateFileExtension(argv.output || argv.apis[0]);
|
|
80
|
+
|
|
77
81
|
const {
|
|
78
82
|
'prefix-components-with-info-prop': prefixComponentsWithInfoProp,
|
|
79
83
|
'prefix-tags-with-filename': prefixTagsWithFilename,
|
|
80
84
|
'prefix-tags-with-info-prop': prefixTagsWithInfoProp,
|
|
81
85
|
'without-x-tag-groups': withoutXTagGroups,
|
|
82
|
-
output: specFilename =
|
|
86
|
+
output: specFilename = `openapi.${fileExtension}`,
|
|
83
87
|
} = argv;
|
|
84
88
|
|
|
85
89
|
const usedTagsOptions = [
|
|
@@ -229,7 +233,8 @@ export async function handleJoin(argv: JoinOptions, config: Config, packageVersi
|
|
|
229
233
|
return exitWithError(`Please fix conflicts before running ${yellow('join')}.`);
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
|
|
236
|
+
writeToFileByExtension(sortTopLevelKeysForOas(joinedDef), specFilename, noRefs);
|
|
237
|
+
|
|
233
238
|
printExecutionTime('join', startedAt, specFilename);
|
|
234
239
|
|
|
235
240
|
function populateTags({
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
(function run() {
|
|
2
2
|
const Socket = window.SimpleWebsocket;
|
|
3
3
|
const port = window.__OPENAPI_CLI_WS_PORT;
|
|
4
|
+
const host = window.__OPENAPI_CLI_WS_HOST;
|
|
4
5
|
|
|
5
6
|
let socket;
|
|
6
7
|
|
|
7
8
|
reconnect();
|
|
8
9
|
|
|
10
|
+
function getFormattedHost() {
|
|
11
|
+
// Use localhost when bound to all interfaces
|
|
12
|
+
if (host === '::' || host === '0.0.0.0') {
|
|
13
|
+
return 'localhost';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Other IPv6 addresses must be wrapped in brackets
|
|
17
|
+
if (host.includes('::')) {
|
|
18
|
+
return `[${host}]`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Otherwise return as-is
|
|
22
|
+
return host;
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
function reconnect() {
|
|
10
|
-
socket = new Socket(`ws
|
|
26
|
+
socket = new Socket(`ws://${getFormattedHost()}:${port}`);
|
|
11
27
|
socket.on('connect', () => {
|
|
12
28
|
socket.send('{"type": "ping"}');
|
|
13
29
|
});
|
|
@@ -29,13 +45,14 @@
|
|
|
29
45
|
|
|
30
46
|
socket.on('close', () => {
|
|
31
47
|
socket.destroy();
|
|
32
|
-
console.log('Connection lost, trying to reconnect in 4s');
|
|
48
|
+
console.log('[hot] Connection lost, trying to reconnect in 4s');
|
|
33
49
|
setTimeout(() => {
|
|
34
50
|
reconnect();
|
|
35
51
|
}, 4000);
|
|
36
52
|
});
|
|
37
53
|
|
|
38
54
|
socket.on('error', () => {
|
|
55
|
+
console.log('[hot] Error connecting to hot reloading server');
|
|
39
56
|
socket.destroy();
|
|
40
57
|
});
|
|
41
58
|
}
|
|
@@ -12,7 +12,8 @@ function getPageHTML(
|
|
|
12
12
|
htmlTemplate: string,
|
|
13
13
|
redocOptions: object = {},
|
|
14
14
|
useRedocPro: boolean,
|
|
15
|
-
wsPort: number
|
|
15
|
+
wsPort: number,
|
|
16
|
+
host: string
|
|
16
17
|
) {
|
|
17
18
|
let templateSrc = readFileSync(htmlTemplate, 'utf-8');
|
|
18
19
|
|
|
@@ -28,6 +29,7 @@ function getPageHTML(
|
|
|
28
29
|
<script>
|
|
29
30
|
window.__REDOC_EXPORT = '${useRedocPro ? 'RedoclyReferenceDocs' : 'Redoc'}';
|
|
30
31
|
window.__OPENAPI_CLI_WS_PORT = ${wsPort};
|
|
32
|
+
window.__OPENAPI_CLI_WS_HOST = "${host}";
|
|
31
33
|
</script>
|
|
32
34
|
<script src="/simplewebsocket.min.js"></script>
|
|
33
35
|
<script src="/hot.js"></script>
|
|
@@ -67,7 +69,7 @@ export default async function startPreviewServer(
|
|
|
67
69
|
|
|
68
70
|
if (request.url?.endsWith('/') || path.extname(request.url!) === '') {
|
|
69
71
|
respondWithGzip(
|
|
70
|
-
getPageHTML(htmlTemplate || defaultTemplate, getOptions(), useRedocPro, wsPort),
|
|
72
|
+
getPageHTML(htmlTemplate || defaultTemplate, getOptions(), useRedocPro, wsPort, host),
|
|
71
73
|
request,
|
|
72
74
|
response,
|
|
73
75
|
{
|
|
@@ -143,7 +145,7 @@ export default async function startPreviewServer(
|
|
|
143
145
|
console.timeEnd(colorette.dim(`GET ${request.url}`));
|
|
144
146
|
};
|
|
145
147
|
|
|
146
|
-
const wsPort = await getPort({ portRange: [32201, 32301] });
|
|
148
|
+
const wsPort = await getPort({ port: 32201, portRange: [32201, 32301], host });
|
|
147
149
|
|
|
148
150
|
const server = startHttpServer(port, host, handler);
|
|
149
151
|
server.on('listening', () => {
|
|
@@ -152,5 +154,5 @@ export default async function startPreviewServer(
|
|
|
152
154
|
);
|
|
153
155
|
});
|
|
154
156
|
|
|
155
|
-
return startWsServer(wsPort);
|
|
157
|
+
return startWsServer(wsPort, host);
|
|
156
158
|
}
|
|
@@ -62,8 +62,8 @@ export function startHttpServer(port: number, host: string, handler: http.Reques
|
|
|
62
62
|
return http.createServer(handler).listen(port, host);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export function startWsServer(port: number) {
|
|
66
|
-
const socketServer = new SocketServer({ port, clientTracking: true });
|
|
65
|
+
export function startWsServer(port: number, host: string) {
|
|
66
|
+
const socketServer = new SocketServer({ port, host, clientTracking: true });
|
|
67
67
|
|
|
68
68
|
socketServer.on('connection', (socket: any) => {
|
|
69
69
|
socket.on('data', (data: string) => {
|