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