vovk-cli 0.0.1-draft.20 → 0.0.1-draft.201
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/LICENSE +1 -1
- package/README.md +29 -1
- package/client-templates/cjs/index.cjs.ejs +14 -0
- package/client-templates/cjs/index.d.cts.ejs +15 -0
- package/client-templates/mjs/index.d.mts.ejs +15 -0
- package/client-templates/mjs/index.mjs.ejs +20 -0
- package/client-templates/packageJson/package.json.ejs +1 -0
- package/client-templates/readme/README.md.ejs +35 -0
- package/client-templates/schemaCjs/schema.cjs.ejs +15 -0
- package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
- package/client-templates/schemaJson/schema.json.ejs +1 -0
- package/client-templates/schemaTs/schema.ts.ejs +17 -0
- package/client-templates/ts/index.ts.ejs +22 -0
- package/dist/bundle/index.d.mts +8 -0
- package/dist/bundle/index.mjs +82 -0
- package/dist/dev/diffSegmentSchema.d.mts +36 -0
- package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +4 -12
- package/dist/dev/ensureSchemaFiles.d.mts +3 -0
- package/dist/dev/ensureSchemaFiles.mjs +14 -31
- package/dist/dev/index.d.mts +5 -2
- package/dist/dev/index.mjs +168 -75
- package/dist/dev/logDiffResult.d.mts +1 -1
- package/dist/dev/logDiffResult.mjs +6 -43
- package/dist/dev/writeMetaJson.d.mts +2 -0
- package/dist/dev/writeMetaJson.mjs +17 -0
- package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
- package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
- package/dist/generate/ensureClient.d.mts +3 -0
- package/dist/generate/ensureClient.mjs +31 -0
- package/dist/generate/generate.d.mts +12 -0
- package/dist/generate/generate.mjs +214 -0
- package/dist/generate/getClientTemplateFiles.d.mts +20 -0
- package/dist/generate/getClientTemplateFiles.mjs +77 -0
- package/dist/generate/getProjectFullSchema.d.mts +3 -0
- package/dist/generate/getProjectFullSchema.mjs +64 -0
- package/dist/generate/getTemplateClientImports.d.mts +18 -0
- package/dist/generate/getTemplateClientImports.mjs +38 -0
- package/dist/generate/index.d.mts +40 -0
- package/dist/generate/index.mjs +216 -0
- package/dist/generate/mergePackages.d.mts +9 -0
- package/dist/generate/mergePackages.mjs +78 -0
- package/dist/generate/writeOneClientFile.d.mts +31 -0
- package/dist/generate/writeOneClientFile.mjs +95 -0
- package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
- package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
- package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +14 -0
- package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +85 -0
- package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
- package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +6 -4
- package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -5
- package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -1
- package/dist/getProjectInfo/getConfig/index.d.mts +17 -0
- package/dist/getProjectInfo/getConfig/index.mjs +83 -0
- package/dist/getProjectInfo/index.d.mts +6 -8
- package/dist/getProjectInfo/index.mjs +12 -21
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +88 -38
- package/dist/init/createConfig.d.mts +2 -2
- package/dist/init/createConfig.mjs +18 -12
- package/dist/init/getTemplateFilesFromPackage.mjs +7 -5
- package/dist/init/index.d.mts +2 -2
- package/dist/init/index.mjs +89 -53
- package/dist/init/installDependencies.mjs +4 -2
- package/dist/init/logUpdateDependenciesError.d.mts +3 -1
- package/dist/init/logUpdateDependenciesError.mjs +7 -1
- package/dist/init/updateDependenciesWithoutInstalling.mjs +41 -13
- package/dist/init/updateNPMScripts.d.mts +3 -1
- package/dist/init/updateNPMScripts.mjs +10 -7
- package/dist/init/updateTypeScriptConfig.d.mts +4 -1
- package/dist/init/updateTypeScriptConfig.mjs +11 -7
- package/dist/initProgram.d.mts +1 -1
- package/dist/initProgram.mjs +17 -16
- package/dist/locateSegments.d.mts +8 -1
- package/dist/locateSegments.mjs +12 -4
- package/dist/new/addClassToSegmentCode.d.mts +1 -2
- package/dist/new/addClassToSegmentCode.mjs +3 -3
- package/dist/new/addCommonTerms.mjs +1 -0
- package/dist/new/index.d.mts +1 -1
- package/dist/new/index.mjs +3 -2
- package/dist/new/newModule.d.mts +2 -1
- package/dist/new/newModule.mjs +18 -16
- package/dist/new/newSegment.d.mts +2 -1
- package/dist/new/newSegment.mjs +17 -9
- package/dist/new/render.d.mts +7 -3
- package/dist/new/render.mjs +29 -8
- package/dist/types.d.mts +40 -41
- package/dist/utils/debounceWithArgs.d.mts +2 -2
- package/dist/utils/debounceWithArgs.mjs +24 -9
- package/dist/utils/formatLoggedSegmentName.d.mts +2 -1
- package/dist/utils/formatLoggedSegmentName.mjs +2 -2
- package/dist/utils/getAvailablePort.mjs +1 -1
- package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
- package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
- package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
- package/dist/utils/pickSegmentFullSchema.mjs +15 -0
- package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
- package/dist/utils/removeUnlistedDirectories.mjs +61 -0
- package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
- package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
- package/module-templates/Controller.ts.ejs +50 -0
- package/module-templates/Service.ts.ejs +28 -0
- package/package.json +37 -21
- package/dist/dev/diffSchema.d.mts +0 -43
- package/dist/dev/ensureClient.d.mts +0 -5
- package/dist/dev/ensureClient.mjs +0 -31
- package/dist/dev/isMetadataEmpty.d.mts +0 -2
- package/dist/dev/isMetadataEmpty.mjs +0 -4
- package/dist/dev/writeOneSchemaFile.d.mts +0 -11
- package/dist/generateClient.d.mts +0 -7
- package/dist/generateClient.mjs +0 -97
- package/dist/getProjectInfo/getConfig.d.mts +0 -11
- package/dist/getProjectInfo/getConfig.mjs +0 -29
- package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
- package/dist/postinstall.d.mts +0 -1
- package/dist/postinstall.mjs +0 -24
- package/templates/controller.ejs +0 -51
- package/templates/service.ejs +0 -27
- package/templates/worker.ejs +0 -24
- /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
- /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
package/dist/dev/index.mjs
CHANGED
|
@@ -1,31 +1,48 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { VovkSchemaIdEnum } from 'vovk';
|
|
3
4
|
import * as chokidar from 'chokidar';
|
|
4
5
|
import { Agent, setGlobalDispatcher } from 'undici';
|
|
5
6
|
import keyBy from 'lodash/keyBy.js';
|
|
6
7
|
import capitalize from 'lodash/capitalize.js';
|
|
7
8
|
import debounce from 'lodash/debounce.js';
|
|
9
|
+
import once from 'lodash/once.js';
|
|
8
10
|
import isEmpty from 'lodash/isEmpty.js';
|
|
9
|
-
import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
|
|
10
|
-
import
|
|
11
|
+
import ensureSchemaFiles, { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
|
|
12
|
+
import writeOneSegmentSchemaFile from './writeOneSegmentSchemaFile.mjs';
|
|
11
13
|
import logDiffResult from './logDiffResult.mjs';
|
|
12
|
-
import ensureClient from '
|
|
14
|
+
import ensureClient from '../generate/ensureClient.mjs';
|
|
13
15
|
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
14
|
-
import
|
|
15
|
-
import locateSegments from '../locateSegments.mjs';
|
|
16
|
+
import { generate } from '../generate/generate.mjs';
|
|
17
|
+
import { locateSegments } from '../locateSegments.mjs';
|
|
16
18
|
import debounceWithArgs from '../utils/debounceWithArgs.mjs';
|
|
17
19
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
20
|
+
import writeMetaJson from './writeMetaJson.mjs';
|
|
18
21
|
export class VovkDev {
|
|
19
22
|
#projectInfo;
|
|
20
23
|
#segments = [];
|
|
21
|
-
#
|
|
24
|
+
#fullSchema = {
|
|
25
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
26
|
+
segments: {},
|
|
27
|
+
meta: {
|
|
28
|
+
$schema: VovkSchemaIdEnum.META,
|
|
29
|
+
config: {
|
|
30
|
+
$schema: VovkSchemaIdEnum.CONFIG,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
22
34
|
#isWatching = false;
|
|
23
35
|
#modulesWatcher = null;
|
|
24
36
|
#segmentWatcher = null;
|
|
25
|
-
#
|
|
37
|
+
#onFirstTimeGenerate = null;
|
|
38
|
+
#schemaOut = null;
|
|
39
|
+
constructor({ schemaOut }) {
|
|
40
|
+
this.#schemaOut = schemaOut ?? null;
|
|
41
|
+
}
|
|
42
|
+
#watchSegments = (callback) => {
|
|
26
43
|
const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
|
|
27
44
|
const { cwd, log, config, apiDir } = this.#projectInfo;
|
|
28
|
-
const schemaOutAbsolutePath = path.
|
|
45
|
+
const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
|
|
29
46
|
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
30
47
|
const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
|
|
31
48
|
log.debug(`Watching segments at ${apiDirAbsolutePath}`);
|
|
@@ -40,7 +57,13 @@ export class VovkDev {
|
|
|
40
57
|
const segmentName = getSegmentName(filePath);
|
|
41
58
|
this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
|
|
42
59
|
? this.#segments
|
|
43
|
-
: [
|
|
60
|
+
: [
|
|
61
|
+
...this.#segments,
|
|
62
|
+
{
|
|
63
|
+
routeFilePath: filePath,
|
|
64
|
+
segmentName,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
44
67
|
log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
|
|
45
68
|
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
46
69
|
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
@@ -54,14 +77,22 @@ export class VovkDev {
|
|
|
54
77
|
})
|
|
55
78
|
.on('addDir', async (dirPath) => {
|
|
56
79
|
log.debug(`Directory ${dirPath} has been added to segments folder`);
|
|
57
|
-
this.#segments = await locateSegments(
|
|
80
|
+
this.#segments = await locateSegments({
|
|
81
|
+
dir: apiDirAbsolutePath,
|
|
82
|
+
config,
|
|
83
|
+
log: this.#projectInfo.log,
|
|
84
|
+
});
|
|
58
85
|
for (const { segmentName } of this.#segments) {
|
|
59
86
|
void this.#requestSchema(segmentName);
|
|
60
87
|
}
|
|
61
88
|
})
|
|
62
89
|
.on('unlinkDir', async (dirPath) => {
|
|
63
90
|
log.debug(`Directory ${dirPath} has been removed from segments folder`);
|
|
64
|
-
this.#segments = await locateSegments(
|
|
91
|
+
this.#segments = await locateSegments({
|
|
92
|
+
dir: apiDirAbsolutePath,
|
|
93
|
+
config,
|
|
94
|
+
log: this.#projectInfo.log,
|
|
95
|
+
});
|
|
65
96
|
for (const { segmentName } of this.#segments) {
|
|
66
97
|
void this.#requestSchema(segmentName);
|
|
67
98
|
}
|
|
@@ -77,13 +108,14 @@ export class VovkDev {
|
|
|
77
108
|
}
|
|
78
109
|
})
|
|
79
110
|
.on('ready', () => {
|
|
111
|
+
callback();
|
|
80
112
|
log.debug('Segments watcher is ready');
|
|
81
113
|
})
|
|
82
114
|
.on('error', (error) => {
|
|
83
115
|
log.error(`Error watching segments folder: ${error?.message ?? 'Unknown error'}`);
|
|
84
116
|
});
|
|
85
117
|
};
|
|
86
|
-
#watchModules = () => {
|
|
118
|
+
#watchModules = (callback) => {
|
|
87
119
|
const { config, cwd, log } = this.#projectInfo;
|
|
88
120
|
const modulesDirAbsolutePath = path.join(cwd, config.modulesDir);
|
|
89
121
|
log.debug(`Watching modules at ${modulesDirAbsolutePath}`);
|
|
@@ -115,30 +147,49 @@ export class VovkDev {
|
|
|
115
147
|
}
|
|
116
148
|
})
|
|
117
149
|
.on('ready', () => {
|
|
150
|
+
callback();
|
|
118
151
|
log.debug('Modules watcher is ready');
|
|
119
152
|
})
|
|
120
153
|
.on('error', (error) => {
|
|
121
154
|
log.error(`Error watching modules folder: ${error?.message ?? 'Unknown error'}`);
|
|
122
155
|
});
|
|
123
156
|
};
|
|
124
|
-
#watchConfig = () => {
|
|
157
|
+
#watchConfig = (callback) => {
|
|
125
158
|
const { log, cwd } = this.#projectInfo;
|
|
126
159
|
log.debug(`Watching config files`);
|
|
127
160
|
let isInitial = true;
|
|
128
161
|
let isReady = false;
|
|
129
162
|
const handle = debounce(async () => {
|
|
130
163
|
this.#projectInfo = await getProjectInfo();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
isInitial = false;
|
|
134
|
-
}
|
|
164
|
+
const { config, apiDir } = this.#projectInfo;
|
|
165
|
+
this.#segments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
|
|
135
166
|
await this.#modulesWatcher?.close();
|
|
136
167
|
await this.#segmentWatcher?.close();
|
|
137
|
-
|
|
138
|
-
|
|
168
|
+
await Promise.all([
|
|
169
|
+
new Promise((resolve) => this.#watchModules(() => resolve(0))),
|
|
170
|
+
new Promise((resolve) => this.#watchSegments(() => resolve(0))),
|
|
171
|
+
]);
|
|
172
|
+
const schemaOutAbsolutePath = path.join(cwd, this.#schemaOut ?? this.#projectInfo.config.schemaOutDir);
|
|
173
|
+
if (isInitial) {
|
|
174
|
+
callback();
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
log.info('Config file has been updated');
|
|
178
|
+
this.#generate();
|
|
179
|
+
}
|
|
180
|
+
await writeMetaJson(schemaOutAbsolutePath, this.#projectInfo);
|
|
181
|
+
isInitial = false;
|
|
139
182
|
}, 1000);
|
|
140
183
|
chokidar
|
|
141
|
-
.watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
184
|
+
// .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
185
|
+
.watch([
|
|
186
|
+
'vovk.config.js',
|
|
187
|
+
'vovk.config.mjs',
|
|
188
|
+
'vovk.config.cjs',
|
|
189
|
+
'.config/vovk.config.js',
|
|
190
|
+
'.config/vovk.config.mjs',
|
|
191
|
+
'.config/vovk.config.cjs',
|
|
192
|
+
], {
|
|
142
193
|
persistent: true,
|
|
143
194
|
cwd,
|
|
144
195
|
ignoreInitial: false,
|
|
@@ -157,14 +208,13 @@ export class VovkDev {
|
|
|
157
208
|
.on('error', (error) => log.error(`Error watching config files: ${error?.message ?? 'Unknown error'}`));
|
|
158
209
|
void handle();
|
|
159
210
|
};
|
|
160
|
-
async #watch() {
|
|
211
|
+
async #watch(callback) {
|
|
161
212
|
if (this.#isWatching)
|
|
162
213
|
throw new Error('Already watching');
|
|
163
214
|
const { log } = this.#projectInfo;
|
|
164
215
|
log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
|
|
165
|
-
await ensureClient(this.#projectInfo);
|
|
166
216
|
// automatically watches segments and modules
|
|
167
|
-
this.#watchConfig();
|
|
217
|
+
this.#watchConfig(callback);
|
|
168
218
|
}
|
|
169
219
|
#processControllerChange = async (filePath) => {
|
|
170
220
|
const { log } = this.#projectInfo;
|
|
@@ -175,72 +225,78 @@ export class VovkDev {
|
|
|
175
225
|
}
|
|
176
226
|
const nameOfClasReg = /\bclass\s+([A-Za-z_]\w*)(?:\s*<[^>]*>)?\s*\{/g;
|
|
177
227
|
const namesOfClasses = [...code.matchAll(nameOfClasReg)].map((match) => match[1]);
|
|
178
|
-
const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options
|
|
228
|
+
const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options)\b[^}]*}\s*from\s*['"]vovk['"]/;
|
|
179
229
|
if (importRegex.test(code) && namesOfClasses.length) {
|
|
180
230
|
const affectedSegments = this.#segments.filter((s) => {
|
|
181
|
-
const
|
|
182
|
-
if (!
|
|
231
|
+
const segmentSchema = this.#fullSchema.segments[s.segmentName];
|
|
232
|
+
if (!segmentSchema)
|
|
183
233
|
return false;
|
|
184
|
-
const controllersByOriginalName = keyBy(
|
|
185
|
-
|
|
186
|
-
return namesOfClasses.some((name) => schema.controllers[name] ||
|
|
187
|
-
schema.workers[name] ||
|
|
188
|
-
controllersByOriginalName[name] ||
|
|
189
|
-
workersByOriginalName[name]);
|
|
234
|
+
const controllersByOriginalName = keyBy(segmentSchema.controllers, 'originalControllerName');
|
|
235
|
+
return namesOfClasses.some((name) => segmentSchema.controllers[name] || controllersByOriginalName[name]);
|
|
190
236
|
});
|
|
191
237
|
if (affectedSegments.length) {
|
|
192
|
-
log.debug(`A file with controller
|
|
238
|
+
log.debug(`A file with controller ${namesOfClasses.join(', ')} have been modified at path "${filePath}". Segment(s) affected: ${JSON.stringify(affectedSegments.map((s) => s.segmentName))}`);
|
|
193
239
|
for (const segment of affectedSegments) {
|
|
194
240
|
await this.#requestSchema(segment.segmentName);
|
|
195
241
|
}
|
|
196
242
|
}
|
|
243
|
+
else {
|
|
244
|
+
log.debug(`The class ${namesOfClasses.join(', ')} does not belong to any segment`);
|
|
245
|
+
}
|
|
197
246
|
}
|
|
198
247
|
else {
|
|
199
|
-
log.debug(`The file does not contain any controller
|
|
248
|
+
log.debug(`The file ${filePath} does not contain any controller`);
|
|
200
249
|
}
|
|
201
250
|
};
|
|
202
251
|
#requestSchema = debounceWithArgs(async (segmentName) => {
|
|
203
|
-
const {
|
|
252
|
+
const { apiRoot, log, port, config } = this.#projectInfo;
|
|
204
253
|
const { devHttps } = config;
|
|
205
|
-
const endpoint = `${
|
|
254
|
+
const endpoint = `${apiRoot.startsWith(`http${devHttps ? 's' : ''}://`) ? apiRoot : `http${devHttps ? 's' : ''}://localhost:${port}${apiRoot}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
|
|
206
255
|
log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
|
|
207
|
-
const resp = await fetch(endpoint);
|
|
208
|
-
if (resp.status !== 200) {
|
|
209
|
-
const probableCause = {
|
|
210
|
-
404: 'The segment is not compiled.',
|
|
211
|
-
500: 'Syntax error in one of controllers.',
|
|
212
|
-
}[resp.status];
|
|
213
|
-
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
let schema = null;
|
|
217
256
|
try {
|
|
218
|
-
|
|
257
|
+
const resp = await fetch(endpoint);
|
|
258
|
+
if (resp.status !== 200) {
|
|
259
|
+
const probableCause = {
|
|
260
|
+
404: 'The segment did not compile or config.origin is wrong.',
|
|
261
|
+
}[resp.status];
|
|
262
|
+
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
|
|
263
|
+
return { isError: true };
|
|
264
|
+
}
|
|
265
|
+
let segmentSchema = null;
|
|
266
|
+
try {
|
|
267
|
+
({ schema: segmentSchema } = (await resp.json()));
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
271
|
+
}
|
|
272
|
+
await this.#handleSegmentSchema(segmentName, segmentSchema);
|
|
219
273
|
}
|
|
220
274
|
catch (error) {
|
|
221
|
-
log.error(`Error
|
|
275
|
+
log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}: ${error.message}`);
|
|
276
|
+
return { isError: true };
|
|
222
277
|
}
|
|
223
|
-
|
|
278
|
+
return { isError: false };
|
|
224
279
|
}, 500);
|
|
225
|
-
|
|
280
|
+
#generate = debounce(() => generate({ projectInfo: this.#projectInfo, fullSchema: this.#fullSchema, locatedSegments: this.#segments }).then(this.#onFirstTimeGenerate), 1000);
|
|
281
|
+
async #handleSegmentSchema(segmentName, segmentSchema) {
|
|
226
282
|
const { log, config, cwd } = this.#projectInfo;
|
|
227
|
-
if (!
|
|
228
|
-
log.warn(
|
|
283
|
+
if (!segmentSchema) {
|
|
284
|
+
log.warn(`${formatLoggedSegmentName(segmentName)} schema is null`);
|
|
229
285
|
return;
|
|
230
286
|
}
|
|
231
|
-
log.debug(`Handling received schema from ${formatLoggedSegmentName(
|
|
232
|
-
const schemaOutAbsolutePath = path.
|
|
233
|
-
const segment = this.#segments.find((s) => s.segmentName ===
|
|
287
|
+
log.debug(`Handling received schema from ${formatLoggedSegmentName(segmentName)}`);
|
|
288
|
+
const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
|
|
289
|
+
const segment = this.#segments.find((s) => s.segmentName === segmentName);
|
|
234
290
|
if (!segment) {
|
|
235
|
-
log.warn(
|
|
291
|
+
log.warn(`${formatLoggedSegmentName(segmentName)} not found`);
|
|
236
292
|
return;
|
|
237
293
|
}
|
|
238
|
-
this.#
|
|
239
|
-
if (
|
|
294
|
+
this.#fullSchema.segments[segmentName] = segmentSchema;
|
|
295
|
+
if (segmentSchema.emitSchema) {
|
|
240
296
|
const now = Date.now();
|
|
241
|
-
const { diffResult } = await
|
|
297
|
+
const { diffResult } = await writeOneSegmentSchemaFile({
|
|
242
298
|
schemaOutAbsolutePath,
|
|
243
|
-
|
|
299
|
+
segmentSchema,
|
|
244
300
|
skipIfExists: false,
|
|
245
301
|
});
|
|
246
302
|
const timeTook = Date.now() - now;
|
|
@@ -249,17 +305,25 @@ export class VovkDev {
|
|
|
249
305
|
log.info(`Schema for ${formatLoggedSegmentName(segment.segmentName)} has been updated in ${timeTook}ms`);
|
|
250
306
|
}
|
|
251
307
|
}
|
|
252
|
-
else if (
|
|
253
|
-
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but emitSchema is false`);
|
|
308
|
+
else if (segmentSchema && !isEmpty(segmentSchema.controllers)) {
|
|
309
|
+
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but "emitSchema" is false`);
|
|
254
310
|
}
|
|
255
|
-
if (this.#segments.every((s) => this.#
|
|
311
|
+
if (this.#segments.every((s) => this.#fullSchema.segments[s.segmentName])) {
|
|
256
312
|
log.debug(`All segments with "emitSchema" have schema.`);
|
|
257
|
-
|
|
313
|
+
this.#generate();
|
|
258
314
|
}
|
|
259
315
|
}
|
|
260
|
-
async start({
|
|
261
|
-
|
|
316
|
+
async start({ exit }) {
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
this.#projectInfo = await getProjectInfo();
|
|
262
319
|
const { log, config, cwd, apiDir } = this.#projectInfo;
|
|
320
|
+
this.#segments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
|
|
321
|
+
log.info('Starting...');
|
|
322
|
+
if (exit) {
|
|
323
|
+
this.#onFirstTimeGenerate = once(() => {
|
|
324
|
+
log.info('The schemas and the RPC client have been generated. Exiting...');
|
|
325
|
+
});
|
|
326
|
+
}
|
|
263
327
|
if (config.devHttps) {
|
|
264
328
|
const agent = new Agent({
|
|
265
329
|
connect: {
|
|
@@ -274,20 +338,49 @@ export class VovkDev {
|
|
|
274
338
|
process.on('unhandledRejection', (reason) => {
|
|
275
339
|
log.error(`Unhandled Rejection: ${String(reason)}`);
|
|
276
340
|
});
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
this.#
|
|
280
|
-
await
|
|
281
|
-
|
|
341
|
+
const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
|
|
342
|
+
const segmentNames = this.#segments.map((s) => s.segmentName);
|
|
343
|
+
await ensureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, segmentNames);
|
|
344
|
+
await ensureClient(this.#projectInfo, this.#segments);
|
|
345
|
+
const MAX_ATTEMPTS = 5;
|
|
346
|
+
const DELAY = 5000;
|
|
347
|
+
// Request schema every segment in 5 seconds in order to update schema on start
|
|
282
348
|
setTimeout(() => {
|
|
283
349
|
for (const { segmentName } of this.#segments) {
|
|
284
|
-
|
|
350
|
+
let attempts = 0;
|
|
351
|
+
void this.#requestSchema(segmentName).then(({ isError }) => {
|
|
352
|
+
if (isError) {
|
|
353
|
+
const interval = setInterval(() => {
|
|
354
|
+
attempts++;
|
|
355
|
+
if (attempts >= MAX_ATTEMPTS) {
|
|
356
|
+
clearInterval(interval);
|
|
357
|
+
log.error(`Failed to request schema for ${formatLoggedSegmentName(segmentName)} after ${MAX_ATTEMPTS} attempts`);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
|
|
361
|
+
if (!isError2) {
|
|
362
|
+
clearInterval(interval);
|
|
363
|
+
log.info(`Requested schema for ${formatLoggedSegmentName(segmentName)} after ${attempts} attempts`);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}, DELAY);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
285
369
|
}
|
|
286
|
-
|
|
287
|
-
|
|
370
|
+
}, DELAY);
|
|
371
|
+
if (!exit) {
|
|
372
|
+
this.#watch(() => {
|
|
373
|
+
log.info(`Ready in ${Date.now() - now}ms. Making initial requests for schemas in a moment...`);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
log.info(`Ready in ${Date.now() - now}ms. Making requests for schemas in a moment...`);
|
|
378
|
+
}
|
|
288
379
|
}
|
|
289
380
|
}
|
|
290
381
|
const env = process.env;
|
|
291
382
|
if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
|
|
292
|
-
void new VovkDev().start(
|
|
383
|
+
void new VovkDev({ schemaOut: env.__VOVK_SCHEMA_OUT_FLAG__ || undefined }).start({
|
|
384
|
+
exit: env.__VOVK_EXIT__ === 'true',
|
|
385
|
+
});
|
|
293
386
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { DiffResult } from './
|
|
1
|
+
import type { DiffResult } from './diffSegmentSchema.mjs';
|
|
2
2
|
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
3
|
export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
|
|
@@ -3,23 +3,6 @@ import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
|
3
3
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
4
4
|
export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
5
5
|
const diffNormalized = [];
|
|
6
|
-
diffResult.workers.added.forEach((name) => {
|
|
7
|
-
diffNormalized.push({ what: 'worker', type: 'added', name });
|
|
8
|
-
});
|
|
9
|
-
diffResult.workers.removed.forEach((name) => {
|
|
10
|
-
diffNormalized.push({ what: 'worker', type: 'removed', name });
|
|
11
|
-
});
|
|
12
|
-
diffResult.workers.handlers.forEach((handler) => {
|
|
13
|
-
handler.added.forEach((name) => {
|
|
14
|
-
diffNormalized.push({ what: 'workerHandler', type: 'added', name: `${handler.nameOfClass}.${name}` });
|
|
15
|
-
});
|
|
16
|
-
handler.removed.forEach((name) => {
|
|
17
|
-
diffNormalized.push({ what: 'workerHandler', type: 'removed', name: `${handler.nameOfClass}.${name}` });
|
|
18
|
-
});
|
|
19
|
-
handler.changed.forEach((name) => {
|
|
20
|
-
diffNormalized.push({ what: 'workerHandler', type: 'changed', name: `${handler.nameOfClass}.${name}` });
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
6
|
diffResult.controllers.added.forEach((name) => {
|
|
24
7
|
diffNormalized.push({ what: 'controller', type: 'added', name });
|
|
25
8
|
});
|
|
@@ -37,52 +20,32 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
|
37
20
|
diffNormalized.push({ what: 'controllerHandler', type: 'changed', name: `${handler.nameOfClass}.${name}` });
|
|
38
21
|
});
|
|
39
22
|
});
|
|
40
|
-
const LIMIT = diffNormalized.length <
|
|
23
|
+
const LIMIT = diffNormalized.length < 17 ? diffNormalized.length : 15;
|
|
41
24
|
const addedText = chalk.green('added');
|
|
42
25
|
const removedText = chalk.red('removed');
|
|
43
26
|
const changedText = chalk.cyan('changed');
|
|
44
27
|
for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
|
|
45
28
|
switch (diffNormalizedItem.what) {
|
|
46
|
-
case 'worker':
|
|
47
|
-
switch (diffNormalizedItem.type) {
|
|
48
|
-
case 'added':
|
|
49
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
50
|
-
break;
|
|
51
|
-
case 'removed':
|
|
52
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
break;
|
|
56
29
|
case 'controller':
|
|
57
30
|
switch (diffNormalizedItem.type) {
|
|
58
31
|
case 'added':
|
|
59
|
-
projectInfo.log.info(`Schema for
|
|
60
|
-
break;
|
|
61
|
-
case 'removed':
|
|
62
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
case 'workerHandler':
|
|
67
|
-
switch (diffNormalizedItem.type) {
|
|
68
|
-
case 'added':
|
|
69
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
32
|
+
projectInfo.log.info(`Schema for RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
70
33
|
break;
|
|
71
34
|
case 'removed':
|
|
72
|
-
projectInfo.log.info(`Schema for
|
|
35
|
+
projectInfo.log.info(`Schema for RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
73
36
|
break;
|
|
74
37
|
}
|
|
75
38
|
break;
|
|
76
39
|
case 'controllerHandler':
|
|
77
40
|
switch (diffNormalizedItem.type) {
|
|
78
41
|
case 'added':
|
|
79
|
-
projectInfo.log.info(`Schema for
|
|
42
|
+
projectInfo.log.info(`Schema for RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
80
43
|
break;
|
|
81
44
|
case 'removed':
|
|
82
|
-
projectInfo.log.info(`Schema for
|
|
45
|
+
projectInfo.log.info(`Schema for RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
83
46
|
break;
|
|
84
47
|
case 'changed':
|
|
85
|
-
projectInfo.log.info(`Schema for
|
|
48
|
+
projectInfo.log.info(`Schema for RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
86
49
|
break;
|
|
87
50
|
}
|
|
88
51
|
break;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import pick from 'lodash/pick.js';
|
|
4
|
+
import { META_FILE_NAME } from './writeOneSegmentSchemaFile.mjs';
|
|
5
|
+
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
6
|
+
export default async function writeMetaJson(schemaOutAbsolutePath, projectInfo) {
|
|
7
|
+
const metaJsonPath = path.join(schemaOutAbsolutePath, META_FILE_NAME + '.json');
|
|
8
|
+
const metaStr = JSON.stringify({ config: projectInfo ? pick(projectInfo.config, projectInfo.config.emitConfig) : {} }, null, 2);
|
|
9
|
+
const existingStr = await fs.readFile(metaJsonPath, 'utf-8').catch(() => null);
|
|
10
|
+
if (existingStr !== metaStr) {
|
|
11
|
+
await fs.writeFile(metaJsonPath, metaStr);
|
|
12
|
+
projectInfo?.log.info(`Meta JSON is written to ${chalkHighlightThing(metaJsonPath)}`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
projectInfo?.log.debug(`Meta JSON is up to date at ${chalkHighlightThing(metaJsonPath)}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VovkSegmentSchema } from 'vovk';
|
|
2
|
+
import { type DiffResult } from './diffSegmentSchema.mjs';
|
|
3
|
+
export declare const ROOT_SEGMENT_FILE_NAME = "root";
|
|
4
|
+
export declare const META_FILE_NAME = "_meta";
|
|
5
|
+
export default function writeOneSegmentSchemaFile({ schemaOutAbsolutePath, segmentSchema, skipIfExists, }: {
|
|
6
|
+
schemaOutAbsolutePath: string;
|
|
7
|
+
segmentSchema: VovkSegmentSchema;
|
|
8
|
+
skipIfExists?: boolean;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
isCreated: boolean;
|
|
11
|
+
diffResult: DiffResult | null;
|
|
12
|
+
}>;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
-
import
|
|
3
|
+
import diffSegmentSchema from './diffSegmentSchema.mjs';
|
|
4
4
|
import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
5
|
-
export const
|
|
6
|
-
export
|
|
7
|
-
|
|
5
|
+
export const ROOT_SEGMENT_FILE_NAME = 'root';
|
|
6
|
+
export const META_FILE_NAME = '_meta';
|
|
7
|
+
export default async function writeOneSegmentSchemaFile({ schemaOutAbsolutePath, segmentSchema, skipIfExists = false, }) {
|
|
8
|
+
const segmentPath = path.join(schemaOutAbsolutePath, `${segmentSchema.segmentName || ROOT_SEGMENT_FILE_NAME}.json`);
|
|
8
9
|
if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
|
|
9
10
|
try {
|
|
10
11
|
await fs.stat(segmentPath);
|
|
@@ -15,14 +16,17 @@ export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema
|
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
await fs.mkdir(path.dirname(segmentPath), { recursive: true });
|
|
18
|
-
const schemaStr = JSON.stringify(
|
|
19
|
+
const schemaStr = JSON.stringify(segmentSchema, null, 2);
|
|
19
20
|
const existing = await fs.readFile(segmentPath, 'utf-8').catch(() => null);
|
|
20
21
|
if (existing === schemaStr) {
|
|
21
22
|
return { isCreated: false, diffResult: null };
|
|
22
23
|
}
|
|
23
24
|
await fs.writeFile(segmentPath, schemaStr);
|
|
24
25
|
if (existing) {
|
|
25
|
-
return {
|
|
26
|
+
return {
|
|
27
|
+
isCreated: false,
|
|
28
|
+
diffResult: diffSegmentSchema(JSON.parse(existing), segmentSchema),
|
|
29
|
+
};
|
|
26
30
|
}
|
|
27
31
|
return { isCreated: true, diffResult: null };
|
|
28
32
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { VovkSchemaIdEnum } from 'vovk';
|
|
2
|
+
import { generate } from './generate.mjs';
|
|
3
|
+
const getEmptySegmentRecordSchema = (segmentNames) => {
|
|
4
|
+
const result = {};
|
|
5
|
+
for (const segmentName of segmentNames) {
|
|
6
|
+
result[segmentName] = {
|
|
7
|
+
$schema: VovkSchemaIdEnum.SEGMENT,
|
|
8
|
+
segmentName,
|
|
9
|
+
emitSchema: false,
|
|
10
|
+
controllers: {},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
export default async function ensureClient(projectInfo, locatedSegments) {
|
|
16
|
+
return generate({
|
|
17
|
+
isEnsuringClient: true,
|
|
18
|
+
projectInfo,
|
|
19
|
+
fullSchema: {
|
|
20
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
21
|
+
segments: getEmptySegmentRecordSchema(locatedSegments.map(({ segmentName }) => segmentName)),
|
|
22
|
+
meta: {
|
|
23
|
+
$schema: VovkSchemaIdEnum.META,
|
|
24
|
+
config: {
|
|
25
|
+
$schema: VovkSchemaIdEnum.CONFIG,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
locatedSegments,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VovkSchema } from 'vovk';
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
+
import type { GenerateOptions } from '../types.mjs';
|
|
4
|
+
import type { Segment } from '../locateSegments.mjs';
|
|
5
|
+
export declare function generate({ isEnsuringClient, projectInfo, forceNothingWrittenLog, fullSchema, locatedSegments, cliGenerateOptions, }: {
|
|
6
|
+
isEnsuringClient?: boolean;
|
|
7
|
+
projectInfo: ProjectInfo;
|
|
8
|
+
forceNothingWrittenLog?: boolean;
|
|
9
|
+
fullSchema: VovkSchema;
|
|
10
|
+
locatedSegments: Segment[];
|
|
11
|
+
cliGenerateOptions?: GenerateOptions;
|
|
12
|
+
}): Promise<void>;
|