vovk-cli 0.0.1-beta.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.
- package/.eslintrc.js +20 -0
- package/README.md +1 -0
- package/dist/getProjectInfo/directoryExists.d.ts +1 -0
- package/dist/getProjectInfo/directoryExists.js +16 -0
- package/dist/getProjectInfo/getConfig.d.ts +7 -0
- package/dist/getProjectInfo/getConfig.js +29 -0
- package/dist/getProjectInfo/getCwdPath.d.ts +1 -0
- package/dist/getProjectInfo/getCwdPath.js +19 -0
- package/dist/getProjectInfo/getSrcRoot.d.ts +1 -0
- package/dist/getProjectInfo/getSrcRoot.js +19 -0
- package/dist/getProjectInfo/index.d.ts +48 -0
- package/dist/getProjectInfo/index.js +78 -0
- package/dist/getProjectInfo/readConfig.d.ts +3 -0
- package/dist/getProjectInfo/readConfig.js +73 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +104 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +173 -0
- package/dist/locateSegments.d.ts +5 -0
- package/dist/locateSegments.js +58 -0
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +27 -0
- package/dist/server/createMetadataServer.d.ts +5 -0
- package/dist/server/createMetadataServer.js +31 -0
- package/dist/server/diffMetadata.d.ts +43 -0
- package/dist/server/diffMetadata.js +77 -0
- package/dist/server/ensureMetadataFiles.d.ts +3 -0
- package/dist/server/ensureMetadataFiles.js +100 -0
- package/dist/server/generateClient.d.ts +7 -0
- package/dist/server/generateClient.js +98 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +285 -0
- package/dist/server/isMetadataEmpty.d.ts +2 -0
- package/dist/server/isMetadataEmpty.js +7 -0
- package/dist/server/logDiffResult.d.ts +3 -0
- package/dist/server/logDiffResult.js +84 -0
- package/dist/server/writeOneMetadataFile.d.ts +11 -0
- package/dist/server/writeOneMetadataFile.js +34 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +2 -0
- package/dist/utils/debounceWithArgs.d.ts +2 -0
- package/dist/utils/debounceWithArgs.js +20 -0
- package/dist/utils/fileExists.d.ts +1 -0
- package/dist/utils/fileExists.js +16 -0
- package/dist/utils/getAvailablePort.d.ts +10 -0
- package/dist/utils/getAvailablePort.js +47 -0
- package/package.json +43 -0
- package/src/getProjectInfo/directoryExists.ts +10 -0
- package/src/getProjectInfo/getConfig.ts +29 -0
- package/src/getProjectInfo/getCwdPath.ts +15 -0
- package/src/getProjectInfo/getSrcRoot.ts +14 -0
- package/src/getProjectInfo/index.ts +63 -0
- package/src/getProjectInfo/readConfig.ts +50 -0
- package/src/index.ts +112 -0
- package/src/init.ts +174 -0
- package/src/locateSegments.ts +40 -0
- package/src/postinstall.ts +27 -0
- package/src/server/createMetadataServer.ts +30 -0
- package/src/server/diffMetadata.ts +110 -0
- package/src/server/ensureMetadataFiles.ts +92 -0
- package/src/server/generateClient.ts +108 -0
- package/src/server/index.ts +306 -0
- package/src/server/isMetadataEmpty.ts +6 -0
- package/src/server/logDiffResult.ts +114 -0
- package/src/server/writeOneMetadataFile.ts +44 -0
- package/src/types.ts +58 -0
- package/src/utils/debounceWithArgs.ts +22 -0
- package/src/utils/fileExists.ts +10 -0
- package/src/utils/getAvailablePort.ts +50 -0
- package/test/data/segments/[[...vovk]]/route.ts +0 -0
- package/test/data/segments/bar/[[...custom]]/route.ts +0 -0
- package/test/data/segments/baz/[[...vovk]]/noroute.ts +0 -0
- package/test/data/segments/foo/[[...vovk]]/route.ts +0 -0
- package/test/data/segments/garply/waldo/route.ts +0 -0
- package/test/data/segments/grault/xxxx/[[...vovk]]/noroute.ts +0 -0
- package/test/data/segments/quux/corge/[[...vovk]]/route.ts +0 -0
- package/test/index.ts +3 -0
- package/test/metadata-diff.test.ts +300 -0
- package/test/metadata-write.test.ts +82 -0
- package/test/utils.test.ts +49 -0
- package/tsconfig.json +11 -0
- package/tsconfig.test.json +4 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.VovkCLIServer = void 0;
|
|
30
|
+
const chokidar = __importStar(require("chokidar"));
|
|
31
|
+
const http = __importStar(require("http"));
|
|
32
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
33
|
+
const getProjectInfo_1 = __importDefault(require("../getProjectInfo"));
|
|
34
|
+
const path_1 = __importDefault(require("path"));
|
|
35
|
+
const ensureMetadataFiles_1 = require("./ensureMetadataFiles");
|
|
36
|
+
const createMetadataServer_1 = __importDefault(require("./createMetadataServer"));
|
|
37
|
+
const writeOneMetadataFile_1 = __importDefault(require("./writeOneMetadataFile"));
|
|
38
|
+
const logDiffResult_1 = __importDefault(require("./logDiffResult"));
|
|
39
|
+
const generateClient_1 = __importDefault(require("./generateClient"));
|
|
40
|
+
const locateSegments_1 = __importDefault(require("../locateSegments"));
|
|
41
|
+
const debounceWithArgs_1 = __importDefault(require("../utils/debounceWithArgs"));
|
|
42
|
+
const lodash_1 = require("lodash");
|
|
43
|
+
class VovkCLIServer {
|
|
44
|
+
#projectInfo;
|
|
45
|
+
#segments = [];
|
|
46
|
+
#metadata = {};
|
|
47
|
+
#isWatching = false;
|
|
48
|
+
#modulesWatcher = null;
|
|
49
|
+
#segmentWatcher = null;
|
|
50
|
+
#watchSegments = () => {
|
|
51
|
+
const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
|
|
52
|
+
const { apiDir, log, metadataOutFullPath } = this.#projectInfo;
|
|
53
|
+
const getSegmentName = (filePath) => path_1.default.relative(apiDir, filePath).replace(segmentReg, '');
|
|
54
|
+
log.debug(`Watching segments in ${apiDir}`);
|
|
55
|
+
this.#segmentWatcher = chokidar
|
|
56
|
+
.watch(apiDir, {
|
|
57
|
+
persistent: true,
|
|
58
|
+
ignoreInitial: true,
|
|
59
|
+
})
|
|
60
|
+
.on('add', (filePath) => {
|
|
61
|
+
log.debug(`File ${filePath} has been added to segments folder`);
|
|
62
|
+
if (segmentReg.test(filePath)) {
|
|
63
|
+
const segmentName = getSegmentName(filePath);
|
|
64
|
+
this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
|
|
65
|
+
? this.#segments
|
|
66
|
+
: [...this.#segments, { routeFilePath: filePath, segmentName }];
|
|
67
|
+
log.info(`Segment "${segmentName}" has been added`);
|
|
68
|
+
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
69
|
+
void (0, ensureMetadataFiles_1.debouncedEnsureMetadataFiles)(metadataOutFullPath, this.#segments.map((s) => s.segmentName), this.#projectInfo // TODO refactor
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.on('change', (filePath) => {
|
|
74
|
+
log.debug(`File ${filePath} has been changed at segments folder`);
|
|
75
|
+
if (segmentReg.test(filePath)) {
|
|
76
|
+
void this.#ping(getSegmentName(filePath));
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
80
|
+
.on('addDir', async (dirPath) => {
|
|
81
|
+
log.debug(`Directory ${dirPath} has been added to segments folder`);
|
|
82
|
+
this.#segments = await (0, locateSegments_1.default)(this.#projectInfo.apiDir);
|
|
83
|
+
for (const { segmentName } of this.#segments) {
|
|
84
|
+
void this.#ping(segmentName);
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
88
|
+
.on('unlinkDir', async (dirPath) => {
|
|
89
|
+
log.debug(`Directory ${dirPath} has been removed from segments folder`);
|
|
90
|
+
this.#segments = await (0, locateSegments_1.default)(this.#projectInfo.apiDir);
|
|
91
|
+
for (const { segmentName } of this.#segments) {
|
|
92
|
+
void this.#ping(segmentName);
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
.on('unlink', (filePath) => {
|
|
96
|
+
log.debug(`File ${filePath} has been removed from segments folder`);
|
|
97
|
+
if (segmentReg.test(filePath)) {
|
|
98
|
+
const segmentName = getSegmentName(filePath);
|
|
99
|
+
this.#segments = this.#segments.filter((s) => s.segmentName !== segmentName);
|
|
100
|
+
log.info(`Segment "${segmentName}" has been removed`);
|
|
101
|
+
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
102
|
+
void (0, ensureMetadataFiles_1.debouncedEnsureMetadataFiles)(metadataOutFullPath, this.#segments.map((s) => s.segmentName), this.#projectInfo // TODO refactor
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
.on('ready', () => {
|
|
107
|
+
log.debug('Segments watcher is ready');
|
|
108
|
+
})
|
|
109
|
+
.on('error', (error) => {
|
|
110
|
+
log.error(`Error watching segments folder: ${error.message}`);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
#watchModules = () => {
|
|
114
|
+
const { config, log } = this.#projectInfo;
|
|
115
|
+
log.debug(`Watching modules in ${config.modulesDir}`);
|
|
116
|
+
this.#modulesWatcher = chokidar
|
|
117
|
+
.watch(config.modulesDir, {
|
|
118
|
+
persistent: true,
|
|
119
|
+
ignoreInitial: true,
|
|
120
|
+
})
|
|
121
|
+
.on('add', (filePath) => {
|
|
122
|
+
log.debug(`File ${filePath} has been added to modules folder`);
|
|
123
|
+
void this.#processControllerChange(filePath);
|
|
124
|
+
})
|
|
125
|
+
.on('change', (filePath) => {
|
|
126
|
+
log.debug(`File ${filePath} has been changed at modules folder`);
|
|
127
|
+
void this.#processControllerChange(filePath);
|
|
128
|
+
})
|
|
129
|
+
.on('unlink', (filePath) => {
|
|
130
|
+
log.debug(`File ${filePath} has been removed from modules folder`);
|
|
131
|
+
})
|
|
132
|
+
.on('addDir', () => {
|
|
133
|
+
for (const { segmentName } of this.#segments) {
|
|
134
|
+
void this.#ping(segmentName);
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
.on('unlinkDir', () => {
|
|
138
|
+
for (const { segmentName } of this.#segments) {
|
|
139
|
+
void this.#ping(segmentName);
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
.on('ready', () => {
|
|
143
|
+
log.debug('Modules watcher is ready');
|
|
144
|
+
})
|
|
145
|
+
.on('error', (error) => {
|
|
146
|
+
log.error(`Error watching modules folder: ${error.message}`);
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
#watchConfig = () => {
|
|
150
|
+
const { log } = this.#projectInfo;
|
|
151
|
+
log.debug(`Watching config files`);
|
|
152
|
+
let isInitial = true;
|
|
153
|
+
const handle = (0, lodash_1.debounce)(async () => {
|
|
154
|
+
this.#projectInfo = await (0, getProjectInfo_1.default)();
|
|
155
|
+
if (!isInitial) {
|
|
156
|
+
log.info('Config file has been updated');
|
|
157
|
+
isInitial = false;
|
|
158
|
+
}
|
|
159
|
+
await this.#modulesWatcher?.close();
|
|
160
|
+
await this.#segmentWatcher?.close();
|
|
161
|
+
this.#watchModules();
|
|
162
|
+
this.#watchSegments();
|
|
163
|
+
}, 1000);
|
|
164
|
+
chokidar
|
|
165
|
+
.watch('vovk.config.{js,mjs,cjs}', {
|
|
166
|
+
persistent: true,
|
|
167
|
+
cwd: process.cwd(),
|
|
168
|
+
ignoreInitial: false,
|
|
169
|
+
depth: 0,
|
|
170
|
+
})
|
|
171
|
+
.on('add', () => void handle())
|
|
172
|
+
.on('change', () => void handle())
|
|
173
|
+
.on('unlink', () => void handle())
|
|
174
|
+
.on('ready', () => {
|
|
175
|
+
log.debug('Config files watcher is ready');
|
|
176
|
+
})
|
|
177
|
+
.on('error', (error) => {
|
|
178
|
+
log.error(`Error watching config files: ${error.message}`);
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
#watch() {
|
|
182
|
+
if (this.#isWatching)
|
|
183
|
+
throw new Error('Already watching');
|
|
184
|
+
const { apiDir, log, config } = this.#projectInfo;
|
|
185
|
+
log.info(`Watching segments in ${apiDir} and modules in ${config.modulesDir}. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
|
|
186
|
+
this.#watchSegments();
|
|
187
|
+
this.#watchModules();
|
|
188
|
+
this.#watchConfig();
|
|
189
|
+
}
|
|
190
|
+
#processControllerChange = async (filePath) => {
|
|
191
|
+
const { log } = this.#projectInfo;
|
|
192
|
+
const code = await promises_1.default.readFile(filePath, 'utf-8').catch(() => null);
|
|
193
|
+
if (typeof code !== 'string')
|
|
194
|
+
return;
|
|
195
|
+
const nameOfClasReg = /\bclass\s+([A-Za-z_]\w*)(?:\s*<[^>]*>)?\s*\{/g;
|
|
196
|
+
const namesOfClasses = [...code.matchAll(nameOfClasReg)].map((match) => match[1]);
|
|
197
|
+
const importRegex = /import\s*{[^}]*\b(initVovk|get|post|put|del|head|options|worker)\b[^}]*}\s*from\s*['"]vovk['"]/;
|
|
198
|
+
if (importRegex.test(code) && namesOfClasses.length) {
|
|
199
|
+
const affectedSegments = this.#segments.filter((s) => {
|
|
200
|
+
const metadata = this.#metadata[s.segmentName];
|
|
201
|
+
if (!metadata)
|
|
202
|
+
return false;
|
|
203
|
+
return namesOfClasses.some((name) => metadata.controllers[name] || metadata.workers[name]);
|
|
204
|
+
});
|
|
205
|
+
if (affectedSegments.length) {
|
|
206
|
+
log.debug(`A file with controller or worker ${namesOfClasses.join(', ')} have been modified at path "${filePath}". Segment(s) affected: ${affectedSegments.map((s) => s.segmentName).join(', ')}`);
|
|
207
|
+
for (const segment of affectedSegments) {
|
|
208
|
+
await this.#ping(segment.segmentName);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
#ping = (0, debounceWithArgs_1.default)((segmentName) => {
|
|
214
|
+
const { apiEntryPoint, log } = this.#projectInfo;
|
|
215
|
+
const endpoint = `${apiEntryPoint}/${segmentName ? `${segmentName}/` : ''}_vovk-ping_`;
|
|
216
|
+
log.debug(`Pinging segment "${segmentName}" at ${endpoint}`);
|
|
217
|
+
const req = http.get(endpoint, (resp) => {
|
|
218
|
+
if (resp.statusCode !== 200) {
|
|
219
|
+
log.warn(`Ping to segment "${segmentName}" failed with status code ${resp.statusCode}. Expected 200.`);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
req.on('error', (err) => {
|
|
223
|
+
log.error(`Error during HTTP request made to ${endpoint}: ${err.message}`);
|
|
224
|
+
});
|
|
225
|
+
}, 500);
|
|
226
|
+
#createMetadataServer() {
|
|
227
|
+
const { metadataOutFullPath, log } = this.#projectInfo;
|
|
228
|
+
return (0, createMetadataServer_1.default)(async ({ metadata }) => {
|
|
229
|
+
const segment = this.#segments.find((s) => s.segmentName === metadata.segmentName);
|
|
230
|
+
if (!segment) {
|
|
231
|
+
log.debug(`Segment "${metadata.segmentName}" not found`);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.#metadata[metadata.segmentName] = metadata;
|
|
235
|
+
if (metadata.emitMetadata) {
|
|
236
|
+
const now = Date.now();
|
|
237
|
+
const { diffResult } = await (0, writeOneMetadataFile_1.default)({
|
|
238
|
+
metadataOutFullPath,
|
|
239
|
+
metadata,
|
|
240
|
+
skipIfExists: false,
|
|
241
|
+
});
|
|
242
|
+
const timeTook = Date.now() - now;
|
|
243
|
+
if (diffResult) {
|
|
244
|
+
(0, logDiffResult_1.default)(segment.segmentName, diffResult, this.#projectInfo);
|
|
245
|
+
log.info(`Metadata for segment "${metadata.segmentName}" has been updated in ${timeTook}ms`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else if (metadata && (!(0, lodash_1.isEmpty)(metadata.controllers) || !(0, lodash_1.isEmpty)(metadata.workers))) {
|
|
249
|
+
log.error(`Non-empty metadata provided for segment "${metadata.segmentName}" but emitMetadata is false`);
|
|
250
|
+
}
|
|
251
|
+
if (this.#segments.every((s) => this.#metadata[s.segmentName])) {
|
|
252
|
+
log.debug(`All segments with emitMetadata=true have metadata.`);
|
|
253
|
+
await (0, generateClient_1.default)(this.#projectInfo, this.#segments, this.#metadata);
|
|
254
|
+
}
|
|
255
|
+
}, (error) => {
|
|
256
|
+
log.error(String(error));
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async startServer({ clientOutDir } = {}) {
|
|
260
|
+
this.#projectInfo = await (0, getProjectInfo_1.default)({ clientOutDir });
|
|
261
|
+
this.#segments = await (0, locateSegments_1.default)(this.#projectInfo.apiDir);
|
|
262
|
+
const { vovkPort, log, metadataOutFullPath } = this.#projectInfo;
|
|
263
|
+
const server = this.#createMetadataServer();
|
|
264
|
+
if (!vovkPort) {
|
|
265
|
+
log.error('No port provided for Vovk Server. Exiting...');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
await (0, ensureMetadataFiles_1.debouncedEnsureMetadataFiles)(metadataOutFullPath, this.#segments.map((s) => s.segmentName), this.#projectInfo);
|
|
269
|
+
server.listen(vovkPort, () => {
|
|
270
|
+
log.info(`Vovk Metadata Server is running on port ${vovkPort}. Happy coding!`);
|
|
271
|
+
});
|
|
272
|
+
// Ping every segment in 3 seconds in order to update metadata and start watching
|
|
273
|
+
setTimeout(() => {
|
|
274
|
+
for (const { segmentName } of this.#segments) {
|
|
275
|
+
void this.#ping(segmentName);
|
|
276
|
+
}
|
|
277
|
+
this.#watch();
|
|
278
|
+
}, 3000);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
exports.VovkCLIServer = VovkCLIServer;
|
|
282
|
+
const env = process.env;
|
|
283
|
+
if (env.__VOVK_START_SERVER_IN_STANDALONE_MODE__ === 'true') {
|
|
284
|
+
void new VovkCLIServer().startServer();
|
|
285
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = isMetadataEmpty;
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
function isMetadataEmpty(metadata) {
|
|
6
|
+
return (0, lodash_1.isEmpty)(metadata.controllers) && (0, lodash_1.isEmpty)(metadata.workers);
|
|
7
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = logDiffResult;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
9
|
+
const diffNormalized = [];
|
|
10
|
+
diffResult.workers.added.forEach((name) => {
|
|
11
|
+
diffNormalized.push({ what: 'worker', type: 'added', name });
|
|
12
|
+
});
|
|
13
|
+
diffResult.workers.removed.forEach((name) => {
|
|
14
|
+
diffNormalized.push({ what: 'worker', type: 'removed', name });
|
|
15
|
+
});
|
|
16
|
+
diffResult.controllers.added.forEach((name) => {
|
|
17
|
+
diffNormalized.push({ what: 'controller', type: 'added', name });
|
|
18
|
+
});
|
|
19
|
+
diffResult.controllers.removed.forEach((name) => {
|
|
20
|
+
diffNormalized.push({ what: 'controller', type: 'removed', name });
|
|
21
|
+
});
|
|
22
|
+
diffResult.controllers.handlers.forEach((handler) => {
|
|
23
|
+
handler.added.forEach((name) => {
|
|
24
|
+
diffNormalized.push({ what: 'controllerHandler', type: 'added', name: `${handler.nameOfClass}.${name}` });
|
|
25
|
+
});
|
|
26
|
+
handler.removed.forEach((name) => {
|
|
27
|
+
diffNormalized.push({ what: 'controllerHandler', type: 'removed', name: `${handler.nameOfClass}.${name}` });
|
|
28
|
+
});
|
|
29
|
+
handler.changed.forEach((name) => {
|
|
30
|
+
diffNormalized.push({ what: 'controllerHandler', type: 'changed', name: `${handler.nameOfClass}.${name}` });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
const LIMIT = 5;
|
|
34
|
+
for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
|
|
35
|
+
switch (diffNormalizedItem.what) {
|
|
36
|
+
case 'worker':
|
|
37
|
+
switch (diffNormalizedItem.type) {
|
|
38
|
+
case 'added':
|
|
39
|
+
projectInfo.log.info(`New worker ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been added to segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
40
|
+
break;
|
|
41
|
+
case 'removed':
|
|
42
|
+
projectInfo.log.info(`Worker ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been removed from segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
case 'controller':
|
|
47
|
+
switch (diffNormalizedItem.type) {
|
|
48
|
+
case 'added':
|
|
49
|
+
projectInfo.log.info(`New controller ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been added to segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
50
|
+
break;
|
|
51
|
+
case 'removed':
|
|
52
|
+
projectInfo.log.info(`Controller ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been removed from segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
case 'workerHandler':
|
|
57
|
+
switch (diffNormalizedItem.type) {
|
|
58
|
+
case 'added':
|
|
59
|
+
projectInfo.log.info(`New worker method ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been added in segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
60
|
+
break;
|
|
61
|
+
case 'removed':
|
|
62
|
+
projectInfo.log.info(`Worker method ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been removed in segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
case 'controllerHandler':
|
|
67
|
+
switch (diffNormalizedItem.type) {
|
|
68
|
+
case 'added':
|
|
69
|
+
projectInfo.log.info(`New controller method ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been added in segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
70
|
+
break;
|
|
71
|
+
case 'removed':
|
|
72
|
+
projectInfo.log.info(`Controller method ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been removed in segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
73
|
+
break;
|
|
74
|
+
case 'changed':
|
|
75
|
+
projectInfo.log.info(`Controller method ${chalk_1.default.white.bold(diffNormalizedItem.name)} has been changed in segment "${chalk_1.default.white.bold(segmentName)}"`);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (diffNormalized.length > LIMIT) {
|
|
82
|
+
projectInfo.log.info(`...and ${diffNormalized.length - LIMIT} more changes`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { VovkMetadata } from 'vovk';
|
|
2
|
+
import { DiffResult } from './diffMetadata';
|
|
3
|
+
export declare const ROOT_SEGMENT_SCHEMA_NAME = "_root";
|
|
4
|
+
export default function writeOneMetadataFile({ metadataOutFullPath, metadata, skipIfExists, }: {
|
|
5
|
+
metadataOutFullPath: string;
|
|
6
|
+
metadata: VovkMetadata;
|
|
7
|
+
skipIfExists?: boolean;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
isCreated: boolean;
|
|
10
|
+
diffResult: DiffResult | null;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ROOT_SEGMENT_SCHEMA_NAME = void 0;
|
|
7
|
+
exports.default = writeOneMetadataFile;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const diffMetadata_1 = __importDefault(require("./diffMetadata"));
|
|
11
|
+
exports.ROOT_SEGMENT_SCHEMA_NAME = '_root';
|
|
12
|
+
async function writeOneMetadataFile({ metadataOutFullPath, metadata, skipIfExists = false, }) {
|
|
13
|
+
const segmentPath = path_1.default.join(metadataOutFullPath, `${metadata.segmentName || exports.ROOT_SEGMENT_SCHEMA_NAME}.json`);
|
|
14
|
+
if (skipIfExists) {
|
|
15
|
+
try {
|
|
16
|
+
await promises_1.default.stat(segmentPath);
|
|
17
|
+
return { isCreated: false, diffResult: null };
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// File doesn't exist
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
await promises_1.default.mkdir(path_1.default.dirname(segmentPath), { recursive: true });
|
|
24
|
+
const metadataStr = JSON.stringify(metadata, null, 2);
|
|
25
|
+
const existing = await promises_1.default.readFile(segmentPath, 'utf-8').catch(() => null);
|
|
26
|
+
if (existing === metadataStr) {
|
|
27
|
+
return { isCreated: false, diffResult: null };
|
|
28
|
+
}
|
|
29
|
+
await promises_1.default.writeFile(segmentPath, metadataStr);
|
|
30
|
+
if (existing) {
|
|
31
|
+
return { isCreated: false, diffResult: (0, diffMetadata_1.default)(JSON.parse(existing), metadata) };
|
|
32
|
+
}
|
|
33
|
+
return { isCreated: true, diffResult: null };
|
|
34
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { LogLevelNames } from 'loglevel';
|
|
2
|
+
export type KnownAny = any;
|
|
3
|
+
export type VovkEnv = {
|
|
4
|
+
PORT?: string;
|
|
5
|
+
VOVK_CLIENT_OUT_DIR?: string;
|
|
6
|
+
VOVK_METADATA_OUT_DIR?: string;
|
|
7
|
+
VOVK_FETCHER?: string;
|
|
8
|
+
VOVK_VALIDATE_ON_CLIENT?: string;
|
|
9
|
+
VOVK_PORT?: string;
|
|
10
|
+
VOVK_MODULES_DIR?: string;
|
|
11
|
+
VOVK_VALIDATION_LIBRARY?: string;
|
|
12
|
+
VOVK_ORIGIN?: string;
|
|
13
|
+
VOVK_ROOT_ENTRY?: string;
|
|
14
|
+
VOVK_API_ENTRY_POINT?: string;
|
|
15
|
+
VOVK_ROOT_SEGMENT_MODULES_DIR_NAME?: string;
|
|
16
|
+
VOVK_LOG_LEVEL?: LogLevelNames;
|
|
17
|
+
__VOVK_START_SERVER_IN_STANDALONE_MODE__?: 'true';
|
|
18
|
+
};
|
|
19
|
+
export type VovkConfig = {
|
|
20
|
+
clientOutDir?: string;
|
|
21
|
+
metadataOutDir?: string;
|
|
22
|
+
fetcher?: string;
|
|
23
|
+
validateOnClient?: string | null;
|
|
24
|
+
modulesDir?: string;
|
|
25
|
+
validationLibrary?: string | null;
|
|
26
|
+
rootEntry?: string;
|
|
27
|
+
origin?: string;
|
|
28
|
+
rootSegmentModulesDirName?: string;
|
|
29
|
+
logLevel?: LogLevelNames;
|
|
30
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = debounceWithArgs;
|
|
7
|
+
const debounce_1 = __importDefault(require("lodash/debounce"));
|
|
8
|
+
function debounceWithArgs(fn, wait) {
|
|
9
|
+
const debouncedFunctions = new Map();
|
|
10
|
+
return (...args) => {
|
|
11
|
+
const key = JSON.stringify(args);
|
|
12
|
+
if (!debouncedFunctions.has(key)) {
|
|
13
|
+
debouncedFunctions.set(key, (0, debounce_1.default)(fn, wait));
|
|
14
|
+
}
|
|
15
|
+
const debouncedFn = debouncedFunctions.get(key);
|
|
16
|
+
if (debouncedFn) {
|
|
17
|
+
debouncedFn(...args);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function fileExists(filePath: string): Promise<boolean>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = fileExists;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
async function fileExists(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
await promises_1.default.access(filePath);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds an available port starting from a given port.
|
|
3
|
+
* @param {number} startPort - The port to start checking from.
|
|
4
|
+
* @param {number} maxAttempts - The maximum number of attempts to find an available port.
|
|
5
|
+
* @param {number} attempt - The current attempt number.
|
|
6
|
+
* @param {(failedPort: number, tryingPort: number) => void} onWarning - The callback function for warnings.
|
|
7
|
+
* @returns {Promise<string>}
|
|
8
|
+
*/
|
|
9
|
+
declare function getAvailablePort(startPort: number, maxAttempts: number, attempt: number, onWarning: (failedPort: number, tryingPort: number) => void): Promise<string>;
|
|
10
|
+
export default getAvailablePort;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const net_1 = __importDefault(require("net"));
|
|
7
|
+
/**
|
|
8
|
+
* Checks if a port is available.
|
|
9
|
+
* @param {number} port - The port to check.
|
|
10
|
+
* @param {(isAvailable: boolean) => void} callback - The callback function.
|
|
11
|
+
*/
|
|
12
|
+
function checkPort(port, callback) {
|
|
13
|
+
const server = net_1.default.createServer();
|
|
14
|
+
server.listen(port, () => {
|
|
15
|
+
server.close(() => {
|
|
16
|
+
callback(true); // Port is available
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
server.on('error', () => {
|
|
20
|
+
callback(false);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Finds an available port starting from a given port.
|
|
25
|
+
* @param {number} startPort - The port to start checking from.
|
|
26
|
+
* @param {number} maxAttempts - The maximum number of attempts to find an available port.
|
|
27
|
+
* @param {number} attempt - The current attempt number.
|
|
28
|
+
* @param {(failedPort: number, tryingPort: number) => void} onWarning - The callback function for warnings.
|
|
29
|
+
* @returns {Promise<string>}
|
|
30
|
+
*/
|
|
31
|
+
function getAvailablePort(startPort, maxAttempts, attempt, onWarning) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
checkPort(startPort, (isAvailable) => {
|
|
34
|
+
if (isAvailable) {
|
|
35
|
+
resolve(startPort.toString()); // Found an available port
|
|
36
|
+
}
|
|
37
|
+
else if (attempt < maxAttempts) {
|
|
38
|
+
onWarning(startPort, startPort + 1);
|
|
39
|
+
getAvailablePort(startPort + 1, maxAttempts, attempt + 1, onWarning).then(resolve, reject);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
reject('No available ports found');
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
exports.default = getAvailablePort;
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vovk-cli",
|
|
3
|
+
"version": "0.0.1-beta.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"vovk": "./dist/index.js"
|
|
6
|
+
},
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall_": "node ./dist/postinstall.js",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"test": "ts-node test/index.ts",
|
|
12
|
+
"ncu": "npm-check-updates -u -x chalk",
|
|
13
|
+
"npm-publish": "npm publish"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/finom/vovk.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"nextjs",
|
|
21
|
+
"router",
|
|
22
|
+
"cli",
|
|
23
|
+
"vovk"
|
|
24
|
+
],
|
|
25
|
+
"author": "Andrii Gubanov",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/finom/vovk/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://vovk.dev",
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vovk": "^3.0.0-beta.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@inquirer/prompts": "^5.3.8",
|
|
36
|
+
"chalk": "^4.1.2",
|
|
37
|
+
"chokidar": "^3.6.0",
|
|
38
|
+
"commander": "^12.1.0",
|
|
39
|
+
"concurrently": "^8.2.2",
|
|
40
|
+
"lodash": "^4.17.21",
|
|
41
|
+
"loglevel": "^1.9.1"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import readConfig from './readConfig';
|
|
2
|
+
import type { VovkConfig, VovkEnv } from '../types';
|
|
3
|
+
import getCwdPath from './getCwdPath';
|
|
4
|
+
import getSrcRoot from './getSrcRoot';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
export default async function getConfig({ clientOutDir }: { clientOutDir?: string }) {
|
|
8
|
+
const env = process.env as VovkEnv;
|
|
9
|
+
const userConfig = await readConfig();
|
|
10
|
+
const srcRoot = await getSrcRoot();
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const config: Required<VovkConfig> = {
|
|
13
|
+
modulesDir: path.join(
|
|
14
|
+
cwd,
|
|
15
|
+
env.VOVK_MODULES_DIR ?? userConfig.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/')
|
|
16
|
+
),
|
|
17
|
+
validateOnClient: getCwdPath(env.VOVK_VALIDATE_ON_CLIENT ?? userConfig.validateOnClient ?? null),
|
|
18
|
+
validationLibrary: getCwdPath(env.VOVK_VALIDATION_LIBRARY ?? userConfig.validationLibrary ?? null),
|
|
19
|
+
fetcher: getCwdPath(env.VOVK_FETCHER ?? userConfig.fetcher ?? 'vovk/client/defaultFetcher'),
|
|
20
|
+
metadataOutDir: env.VOVK_METADATA_OUT_DIR ?? userConfig.metadataOutDir ?? './.vovk-schema',
|
|
21
|
+
clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? userConfig.clientOutDir ?? './node_modules/.vovk',
|
|
22
|
+
origin: (env.VOVK_ORIGIN ?? userConfig.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
23
|
+
rootEntry: env.VOVK_ROOT_ENTRY ?? userConfig.rootEntry ?? 'api',
|
|
24
|
+
rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? userConfig.rootSegmentModulesDirName ?? '',
|
|
25
|
+
logLevel: env.VOVK_LOG_LEVEL ?? userConfig.logLevel ?? 'debug', // TODO: change to 'warn' when v3 is ready
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return { config, srcRoot };
|
|
29
|
+
}
|