vovk-cli 0.0.1-draft.4 → 0.0.1-draft.41
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/README.md +1 -1
- package/client-templates/compiled/client.d.ts.ejs +24 -0
- package/client-templates/compiled/client.js.ejs +26 -0
- package/client-templates/python/__init__.py +276 -0
- package/client-templates/ts/index.ts.ejs +36 -0
- package/dist/{watcher → dev}/diffSchema.d.mts +2 -2
- package/dist/{watcher → dev}/diffSchema.mjs +1 -1
- package/dist/dev/ensureClient.d.mts +5 -0
- package/dist/dev/ensureClient.mjs +31 -0
- package/dist/{watcher → dev}/ensureSchemaFiles.mjs +20 -8
- package/dist/dev/index.d.mts +4 -0
- package/dist/{watcher → dev}/index.mjs +102 -61
- package/dist/{watcher → dev}/logDiffResult.d.mts +2 -2
- package/dist/{watcher → dev}/logDiffResult.mjs +13 -9
- package/dist/{watcher → dev}/writeOneSchemaFile.d.mts +1 -1
- package/dist/{watcher → dev}/writeOneSchemaFile.mjs +4 -3
- package/dist/generateClient.d.mts +7 -2
- package/dist/generateClient.mjs +74 -80
- package/dist/getProjectInfo/getConfig.mjs +1 -1
- package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +2 -2
- package/dist/getProjectInfo/getRelativeSrcRoot.mjs +4 -4
- package/dist/getProjectInfo/getUserConfig.mjs +4 -2
- package/dist/getProjectInfo/importUncachedModule.mjs +0 -1
- package/dist/getProjectInfo/importUncachedModuleWorker.mjs +0 -1
- package/dist/getProjectInfo/index.mjs +2 -2
- package/dist/index.d.mts +1 -23
- package/dist/index.mjs +29 -47
- package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
- package/dist/init/createConfig.d.mts +3 -4
- package/dist/init/createConfig.mjs +5 -5
- package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
- package/dist/init/getTemplateFilesFromPackage.mjs +4 -5
- package/dist/init/index.d.mts +1 -2
- package/dist/init/index.mjs +46 -93
- package/dist/init/installDependencies.d.mts +4 -1
- package/dist/init/installDependencies.mjs +2 -2
- package/dist/init/logUpdateDependenciesError.d.mts +11 -0
- package/dist/init/logUpdateDependenciesError.mjs +45 -0
- package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
- package/dist/init/updateDependenciesWithoutInstalling.mjs +12 -7
- package/dist/init/updateNPMScripts.d.mts +3 -1
- package/dist/init/updateNPMScripts.mjs +10 -6
- package/dist/init/updateTypeScriptConfig.mjs +2 -2
- package/dist/initProgram.d.mts +2 -0
- package/dist/initProgram.mjs +21 -0
- package/dist/locateSegments.d.mts +7 -1
- package/dist/locateSegments.mjs +9 -6
- package/dist/new/addClassToSegmentCode.mjs +6 -2
- package/dist/new/addCommonTerms.mjs +1 -0
- package/dist/new/index.d.mts +2 -2
- package/dist/new/index.mjs +13 -3
- package/dist/new/newModule.d.mts +6 -2
- package/dist/new/newModule.mjs +51 -27
- package/dist/new/newSegment.d.mts +3 -2
- package/dist/new/newSegment.mjs +7 -5
- package/dist/new/render.d.mts +3 -7
- package/dist/new/render.mjs +11 -7
- package/dist/postinstall.mjs +5 -3
- package/dist/types.d.mts +40 -1
- package/dist/utils/debounceWithArgs.d.mts +1 -1
- package/dist/utils/debounceWithArgs.mjs +24 -9
- package/dist/utils/formatLoggedSegmentName.mjs +1 -1
- package/dist/utils/getAvailablePort.mjs +3 -2
- package/dist/utils/getFileSystemEntryType.mjs +1 -1
- package/package.json +14 -11
- package/templates/controller.ejs +20 -18
- package/templates/service.ejs +24 -4
- package/templates/worker.ejs +24 -1
- package/dist/getProjectInfo/directoryExists.d.mts +0 -1
- package/dist/getProjectInfo/directoryExists.mjs +0 -10
- package/dist/watcher/index.d.mts +0 -6
- package/templates_old/MyThingController.c.only.template.ts +0 -32
- package/templates_old/MyThingController.c.template.ts +0 -34
- package/templates_old/MyThingService.s.template.ts +0 -18
- package/templates_old/controller.ejs +0 -85
- package/templates_old/service.ejs +0 -9
- package/templates_old/worker.ejs +0 -9
- package/templates_old/zod/MyThingController.c.only.template.ts +0 -32
- package/templates_old/zod/MyThingController.c.template.ts +0 -39
- package/templates_old/zod/MyThingService.s.template.ts +0 -18
- /package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +0 -0
- /package/dist/{watcher → dev}/isMetadataEmpty.d.mts +0 -0
- /package/dist/{watcher → dev}/isMetadataEmpty.mjs +0 -0
|
@@ -1,33 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import * as chokidar from 'chokidar';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
4
|
+
import { Agent, setGlobalDispatcher } from 'undici';
|
|
5
|
+
import keyBy from 'lodash/keyBy.js';
|
|
6
|
+
import capitalize from 'lodash/capitalize.js';
|
|
7
|
+
import debounce from 'lodash/debounce.js';
|
|
8
|
+
import isEmpty from 'lodash/isEmpty.js';
|
|
5
9
|
import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
|
|
6
10
|
import writeOneSchemaFile from './writeOneSchemaFile.mjs';
|
|
7
11
|
import logDiffResult from './logDiffResult.mjs';
|
|
12
|
+
import ensureClient from './ensureClient.mjs';
|
|
13
|
+
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
8
14
|
import generateClient from '../generateClient.mjs';
|
|
9
15
|
import locateSegments from '../locateSegments.mjs';
|
|
10
16
|
import debounceWithArgs from '../utils/debounceWithArgs.mjs';
|
|
11
|
-
import debounce from 'lodash/debounce.js';
|
|
12
|
-
import isEmpty from 'lodash/isEmpty.js';
|
|
13
17
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
14
|
-
|
|
15
|
-
import capitalize from 'lodash/capitalize.js';
|
|
16
|
-
import { Agent, setGlobalDispatcher } from 'undici';
|
|
17
|
-
export class VovkCLIWatcher {
|
|
18
|
+
export class VovkDev {
|
|
18
19
|
#projectInfo;
|
|
19
20
|
#segments = [];
|
|
20
21
|
#schemas = {};
|
|
21
22
|
#isWatching = false;
|
|
22
23
|
#modulesWatcher = null;
|
|
23
24
|
#segmentWatcher = null;
|
|
24
|
-
#watchSegments = () => {
|
|
25
|
+
#watchSegments = (callback) => {
|
|
25
26
|
const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
|
|
26
27
|
const { cwd, log, config, apiDir } = this.#projectInfo;
|
|
27
28
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
28
29
|
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
29
30
|
const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
|
|
30
|
-
log.debug(`Watching segments
|
|
31
|
+
log.debug(`Watching segments at ${apiDirAbsolutePath}`);
|
|
31
32
|
this.#segmentWatcher = chokidar
|
|
32
33
|
.watch(apiDirAbsolutePath, {
|
|
33
34
|
persistent: true,
|
|
@@ -39,7 +40,11 @@ export class VovkCLIWatcher {
|
|
|
39
40
|
const segmentName = getSegmentName(filePath);
|
|
40
41
|
this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
|
|
41
42
|
? this.#segments
|
|
42
|
-
: [...this.#segments, {
|
|
43
|
+
: [...this.#segments, {
|
|
44
|
+
routeFilePath: filePath,
|
|
45
|
+
segmentName,
|
|
46
|
+
segmentImportPath: path.relative(config.clientOutDir, filePath) // TODO DRY locateSegments
|
|
47
|
+
}];
|
|
43
48
|
log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
|
|
44
49
|
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
45
50
|
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
@@ -51,18 +56,16 @@ export class VovkCLIWatcher {
|
|
|
51
56
|
void this.#requestSchema(getSegmentName(filePath));
|
|
52
57
|
}
|
|
53
58
|
})
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
55
59
|
.on('addDir', async (dirPath) => {
|
|
56
60
|
log.debug(`Directory ${dirPath} has been added to segments folder`);
|
|
57
|
-
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
61
|
+
this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
58
62
|
for (const { segmentName } of this.#segments) {
|
|
59
63
|
void this.#requestSchema(segmentName);
|
|
60
64
|
}
|
|
61
65
|
})
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
63
66
|
.on('unlinkDir', async (dirPath) => {
|
|
64
67
|
log.debug(`Directory ${dirPath} has been removed from segments folder`);
|
|
65
|
-
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
68
|
+
this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
66
69
|
for (const { segmentName } of this.#segments) {
|
|
67
70
|
void this.#requestSchema(segmentName);
|
|
68
71
|
}
|
|
@@ -78,16 +81,17 @@ export class VovkCLIWatcher {
|
|
|
78
81
|
}
|
|
79
82
|
})
|
|
80
83
|
.on('ready', () => {
|
|
84
|
+
callback();
|
|
81
85
|
log.debug('Segments watcher is ready');
|
|
82
86
|
})
|
|
83
87
|
.on('error', (error) => {
|
|
84
|
-
log.error(`Error watching segments folder: ${error
|
|
88
|
+
log.error(`Error watching segments folder: ${error?.message ?? 'Unknown error'}`);
|
|
85
89
|
});
|
|
86
90
|
};
|
|
87
|
-
#watchModules = () => {
|
|
91
|
+
#watchModules = (callback) => {
|
|
88
92
|
const { config, cwd, log } = this.#projectInfo;
|
|
89
93
|
const modulesDirAbsolutePath = path.join(cwd, config.modulesDir);
|
|
90
|
-
log.debug(`Watching modules
|
|
94
|
+
log.debug(`Watching modules at ${modulesDirAbsolutePath}`);
|
|
91
95
|
const processControllerChange = debounceWithArgs(this.#processControllerChange, 500);
|
|
92
96
|
this.#modulesWatcher = chokidar
|
|
93
97
|
.watch(modulesDirAbsolutePath, {
|
|
@@ -116,30 +120,37 @@ export class VovkCLIWatcher {
|
|
|
116
120
|
}
|
|
117
121
|
})
|
|
118
122
|
.on('ready', () => {
|
|
123
|
+
callback();
|
|
119
124
|
log.debug('Modules watcher is ready');
|
|
120
125
|
})
|
|
121
126
|
.on('error', (error) => {
|
|
122
|
-
log.error(`Error watching modules folder: ${error
|
|
127
|
+
log.error(`Error watching modules folder: ${error?.message ?? 'Unknown error'}`);
|
|
123
128
|
});
|
|
124
129
|
};
|
|
125
|
-
#watchConfig = () => {
|
|
130
|
+
#watchConfig = (callback) => {
|
|
126
131
|
const { log, cwd } = this.#projectInfo;
|
|
127
132
|
log.debug(`Watching config files`);
|
|
128
133
|
let isInitial = true;
|
|
134
|
+
let isReady = false;
|
|
129
135
|
const handle = debounce(async () => {
|
|
130
136
|
this.#projectInfo = await getProjectInfo();
|
|
131
|
-
if (!isInitial) {
|
|
132
|
-
log.info('Config file has been updated');
|
|
133
|
-
isInitial = false;
|
|
134
|
-
}
|
|
135
137
|
await this.#modulesWatcher?.close();
|
|
136
138
|
await this.#segmentWatcher?.close();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
await Promise.all([
|
|
140
|
+
new Promise((resolve) => this.#watchModules(() => resolve(0))),
|
|
141
|
+
new Promise((resolve) => this.#watchSegments(() => resolve(0)))
|
|
142
|
+
]);
|
|
143
|
+
if (isInitial) {
|
|
144
|
+
callback();
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
log.info('Config file has been updated');
|
|
148
|
+
}
|
|
149
|
+
isInitial = false;
|
|
139
150
|
}, 1000);
|
|
140
|
-
let ready = false;
|
|
141
151
|
chokidar
|
|
142
|
-
.watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
152
|
+
// .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
153
|
+
.watch(['vovk.config.js', 'vovk.config.mjs', 'vovk.config.cjs', '.config/vovk.config.js', '.config/vovk.config.mjs', '.config/vovk.config.cjs'], {
|
|
143
154
|
persistent: true,
|
|
144
155
|
cwd,
|
|
145
156
|
ignoreInitial: false,
|
|
@@ -149,24 +160,23 @@ export class VovkCLIWatcher {
|
|
|
149
160
|
.on('change', () => void handle())
|
|
150
161
|
.on('unlink', () => void handle())
|
|
151
162
|
.on('ready', () => {
|
|
152
|
-
if (
|
|
163
|
+
if (isReady)
|
|
153
164
|
return;
|
|
154
165
|
// for some reason this watcher triggers ready event twice
|
|
155
166
|
log.debug('Config files watcher is ready');
|
|
156
|
-
|
|
167
|
+
isReady = true;
|
|
157
168
|
})
|
|
158
|
-
.on('error', (error) => {
|
|
159
|
-
log.error(`Error watching config files: ${error.message}`);
|
|
160
|
-
});
|
|
169
|
+
.on('error', (error) => log.error(`Error watching config files: ${error?.message ?? 'Unknown error'}`));
|
|
161
170
|
void handle();
|
|
162
171
|
};
|
|
163
|
-
#watch() {
|
|
172
|
+
async #watch(callback) {
|
|
164
173
|
if (this.#isWatching)
|
|
165
174
|
throw new Error('Already watching');
|
|
166
175
|
const { log } = this.#projectInfo;
|
|
167
176
|
log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
|
|
177
|
+
await ensureClient(this.#projectInfo);
|
|
168
178
|
// automatically watches segments and modules
|
|
169
|
-
this.#watchConfig();
|
|
179
|
+
this.#watchConfig(callback);
|
|
170
180
|
}
|
|
171
181
|
#processControllerChange = async (filePath) => {
|
|
172
182
|
const { log } = this.#projectInfo;
|
|
@@ -183,8 +193,8 @@ export class VovkCLIWatcher {
|
|
|
183
193
|
const schema = this.#schemas[s.segmentName];
|
|
184
194
|
if (!schema)
|
|
185
195
|
return false;
|
|
186
|
-
const controllersByOriginalName = keyBy(schema.controllers, '
|
|
187
|
-
const workersByOriginalName = keyBy(schema.workers, '
|
|
196
|
+
const controllersByOriginalName = keyBy(schema.controllers, 'originalControllerName');
|
|
197
|
+
const workersByOriginalName = keyBy(schema.workers, 'originalWorkerName');
|
|
188
198
|
return namesOfClasses.some((name) => schema.controllers[name] ||
|
|
189
199
|
schema.workers[name] ||
|
|
190
200
|
controllersByOriginalName[name] ||
|
|
@@ -196,6 +206,9 @@ export class VovkCLIWatcher {
|
|
|
196
206
|
await this.#requestSchema(segment.segmentName);
|
|
197
207
|
}
|
|
198
208
|
}
|
|
209
|
+
else {
|
|
210
|
+
log.debug(`The class ${namesOfClasses.join(', ')} does not belong to any segment`);
|
|
211
|
+
}
|
|
199
212
|
}
|
|
200
213
|
else {
|
|
201
214
|
log.debug(`The file does not contain any controller or worker`);
|
|
@@ -206,23 +219,29 @@ export class VovkCLIWatcher {
|
|
|
206
219
|
const { devHttps } = config;
|
|
207
220
|
const endpoint = `${apiEntryPoint.startsWith(`http${devHttps ? 's' : ''}://`) ? apiEntryPoint : `http${devHttps ? 's' : ''}://localhost:${port}${apiEntryPoint}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
|
|
208
221
|
log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
|
|
209
|
-
const resp = await fetch(endpoint);
|
|
210
|
-
if (resp.status !== 200) {
|
|
211
|
-
const probableCause = {
|
|
212
|
-
404: 'The segment is not compiled.',
|
|
213
|
-
500: 'Syntax error in one of controllers.',
|
|
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
222
|
try {
|
|
220
|
-
|
|
223
|
+
const resp = await fetch(endpoint);
|
|
224
|
+
if (resp.status !== 200) {
|
|
225
|
+
const probableCause = {
|
|
226
|
+
404: 'The segment did not compile or config.origin is wrong.',
|
|
227
|
+
}[resp.status];
|
|
228
|
+
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status} but expected 200.${probableCause ? ` Probable cause: ${probableCause}` : ''}`);
|
|
229
|
+
return { isError: true };
|
|
230
|
+
}
|
|
231
|
+
let schema = null;
|
|
232
|
+
try {
|
|
233
|
+
({ schema } = (await resp.json()));
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
237
|
+
}
|
|
238
|
+
await this.#handleSchema(schema);
|
|
221
239
|
}
|
|
222
240
|
catch (error) {
|
|
223
|
-
log.error(`Error
|
|
241
|
+
log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
242
|
+
return { isError: true };
|
|
224
243
|
}
|
|
225
|
-
|
|
244
|
+
return { isError: false };
|
|
226
245
|
}, 500);
|
|
227
246
|
async #handleSchema(schema) {
|
|
228
247
|
const { log, config, cwd } = this.#projectInfo;
|
|
@@ -252,16 +271,18 @@ export class VovkCLIWatcher {
|
|
|
252
271
|
}
|
|
253
272
|
}
|
|
254
273
|
else if (schema && (!isEmpty(schema.controllers) || !isEmpty(schema.workers))) {
|
|
255
|
-
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but emitSchema is false`);
|
|
274
|
+
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but "emitSchema" is false`);
|
|
256
275
|
}
|
|
257
276
|
if (this.#segments.every((s) => this.#schemas[s.segmentName])) {
|
|
258
277
|
log.debug(`All segments with "emitSchema" have schema.`);
|
|
259
|
-
await generateClient(this.#projectInfo, this.#segments, this.#schemas);
|
|
278
|
+
await generateClient({ projectInfo: this.#projectInfo, segments: this.#segments, segmentsSchema: this.#schemas });
|
|
260
279
|
}
|
|
261
280
|
}
|
|
262
|
-
async start(
|
|
263
|
-
|
|
281
|
+
async start() {
|
|
282
|
+
const now = Date.now();
|
|
283
|
+
this.#projectInfo = await getProjectInfo();
|
|
264
284
|
const { log, config, cwd, apiDir } = this.#projectInfo;
|
|
285
|
+
log.info('Starting...');
|
|
265
286
|
if (config.devHttps) {
|
|
266
287
|
const agent = new Agent({
|
|
267
288
|
connect: {
|
|
@@ -278,18 +299,38 @@ export class VovkCLIWatcher {
|
|
|
278
299
|
});
|
|
279
300
|
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
280
301
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
281
|
-
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
302
|
+
this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
282
303
|
await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
283
|
-
// Request schema every segment in
|
|
304
|
+
// Request schema every segment in 5 seconds in order to update schema on start
|
|
284
305
|
setTimeout(() => {
|
|
285
306
|
for (const { segmentName } of this.#segments) {
|
|
286
|
-
|
|
307
|
+
const MAX_ATTEMPTS = 3;
|
|
308
|
+
let attempts = 0;
|
|
309
|
+
void this.#requestSchema(segmentName).then(({ isError }) => {
|
|
310
|
+
if (isError) {
|
|
311
|
+
const interval = setInterval(() => {
|
|
312
|
+
attempts++;
|
|
313
|
+
if (attempts >= MAX_ATTEMPTS) {
|
|
314
|
+
clearInterval(interval);
|
|
315
|
+
log.error(`Failed to request schema for ${formatLoggedSegmentName(segmentName)} after ${MAX_ATTEMPTS} attempts`);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
|
|
319
|
+
if (!isError2) {
|
|
320
|
+
clearInterval(interval);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}, 5000);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
287
326
|
}
|
|
288
|
-
|
|
289
|
-
|
|
327
|
+
}, 5000);
|
|
328
|
+
this.#watch(() => {
|
|
329
|
+
log.info(`Ready in ${Date.now() - now}ms`);
|
|
330
|
+
});
|
|
290
331
|
}
|
|
291
332
|
}
|
|
292
333
|
const env = process.env;
|
|
293
334
|
if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
|
|
294
|
-
void new
|
|
335
|
+
void new VovkDev().start();
|
|
295
336
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { DiffResult } from './diffSchema.mjs';
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
3
|
export default function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
2
3
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
3
4
|
export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
@@ -37,48 +38,51 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
|
37
38
|
});
|
|
38
39
|
});
|
|
39
40
|
const LIMIT = diffNormalized.length < 12 ? diffNormalized.length : 10;
|
|
41
|
+
const addedText = chalk.green('added');
|
|
42
|
+
const removedText = chalk.red('removed');
|
|
43
|
+
const changedText = chalk.cyan('changed');
|
|
40
44
|
for (const diffNormalizedItem of diffNormalized.slice(0, LIMIT)) {
|
|
41
45
|
switch (diffNormalizedItem.what) {
|
|
42
46
|
case 'worker':
|
|
43
47
|
switch (diffNormalizedItem.type) {
|
|
44
48
|
case 'added':
|
|
45
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
49
|
+
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
46
50
|
break;
|
|
47
51
|
case 'removed':
|
|
48
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
52
|
+
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
49
53
|
break;
|
|
50
54
|
}
|
|
51
55
|
break;
|
|
52
56
|
case 'controller':
|
|
53
57
|
switch (diffNormalizedItem.type) {
|
|
54
58
|
case 'added':
|
|
55
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
59
|
+
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
56
60
|
break;
|
|
57
61
|
case 'removed':
|
|
58
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
62
|
+
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
59
63
|
break;
|
|
60
64
|
}
|
|
61
65
|
break;
|
|
62
66
|
case 'workerHandler':
|
|
63
67
|
switch (diffNormalizedItem.type) {
|
|
64
68
|
case 'added':
|
|
65
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
69
|
+
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
66
70
|
break;
|
|
67
71
|
case 'removed':
|
|
68
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
72
|
+
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
69
73
|
break;
|
|
70
74
|
}
|
|
71
75
|
break;
|
|
72
76
|
case 'controllerHandler':
|
|
73
77
|
switch (diffNormalizedItem.type) {
|
|
74
78
|
case 'added':
|
|
75
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
79
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
76
80
|
break;
|
|
77
81
|
case 'removed':
|
|
78
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
82
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
79
83
|
break;
|
|
80
84
|
case 'changed':
|
|
81
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been
|
|
85
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
82
86
|
break;
|
|
83
87
|
}
|
|
84
88
|
break;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import { DiffResult } from './diffSchema.mjs';
|
|
2
|
+
import { type DiffResult } from './diffSchema.mjs';
|
|
3
3
|
export declare const ROOT_SEGMENT_SCHEMA_NAME = "_root";
|
|
4
4
|
export default function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists, }: {
|
|
5
5
|
schemaOutAbsolutePath: string;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs/promises';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
3
|
import diffSchema from './diffSchema.mjs';
|
|
4
|
+
import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
4
5
|
export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
|
|
5
6
|
export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists = false, }) {
|
|
6
7
|
const segmentPath = path.join(schemaOutAbsolutePath, `${schema.segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json`);
|
|
7
|
-
if (skipIfExists) {
|
|
8
|
+
if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
|
|
8
9
|
try {
|
|
9
10
|
await fs.stat(segmentPath);
|
|
10
11
|
return { isCreated: false, diffResult: null };
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import type { VovkSchema } from 'vovk';
|
|
1
2
|
import type { ProjectInfo } from './getProjectInfo/index.mjs';
|
|
2
3
|
import type { Segment } from './locateSegments.mjs';
|
|
3
|
-
import
|
|
4
|
-
export default function generateClient(projectInfo
|
|
4
|
+
import { GenerateOptions } from './types.mjs';
|
|
5
|
+
export default function generateClient({ projectInfo, segments, segmentsSchema, templates, prettify: prettifyClient, fullSchema, noClient, }: {
|
|
6
|
+
projectInfo: ProjectInfo;
|
|
7
|
+
segments: Segment[];
|
|
8
|
+
segmentsSchema: Record<string, VovkSchema>;
|
|
9
|
+
} & Pick<GenerateOptions, 'templates' | 'prettify' | 'fullSchema' | 'noClient'>): Promise<{
|
|
5
10
|
written: boolean;
|
|
6
11
|
path: string;
|
|
7
12
|
}>;
|
package/dist/generateClient.mjs
CHANGED
|
@@ -1,59 +1,32 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import fs from 'fs/promises';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import ejs from 'ejs';
|
|
3
4
|
import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
|
|
4
5
|
import prettify from './utils/prettify.mjs';
|
|
5
|
-
export default async function generateClient(projectInfo, segments, segmentsSchema) {
|
|
6
|
-
const { config, cwd, log, validateOnClientImportPath, apiEntryPoint, fetcherClientImportPath, schemaOutImportPath } = projectInfo;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const schema = require('${schemaOutImportPath}');
|
|
23
|
-
`;
|
|
24
|
-
let ts = `// auto-generated
|
|
25
|
-
/* eslint-disable */
|
|
26
|
-
import { clientizeController } from 'vovk/client';
|
|
27
|
-
import { promisifyWorker } from 'vovk/worker';
|
|
28
|
-
import type { VovkClientFetcher } from 'vovk/client';
|
|
29
|
-
import fetcher from '${fetcherClientImportPath}';
|
|
30
|
-
import schema from '${schemaOutImportPath}';
|
|
31
|
-
|
|
32
|
-
`;
|
|
33
|
-
for (let i = 0; i < segments.length; i++) {
|
|
34
|
-
const { routeFilePath, segmentName } = segments[i];
|
|
35
|
-
const schema = segmentsSchema[segmentName];
|
|
36
|
-
if (!schema) {
|
|
37
|
-
throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
|
|
6
|
+
export default async function generateClient({ projectInfo, segments, segmentsSchema, templates = ['ts', 'compiled'], prettify: prettifyClient, fullSchema, noClient, }) {
|
|
7
|
+
const { config, cwd, log, validateOnClientImportPath, apiEntryPoint, fetcherClientImportPath, schemaOutImportPath, } = projectInfo;
|
|
8
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
9
|
+
const templatesDir = path.join(__dirname, '..', 'client-templates');
|
|
10
|
+
const clientOutDirAbsolutePath = path.resolve(cwd, config.clientOutDir);
|
|
11
|
+
const mapper = (dir) => (name) => ({
|
|
12
|
+
templatePath: path.resolve(templatesDir, dir, name),
|
|
13
|
+
outPath: path.join(clientOutDirAbsolutePath, name.replace('.ejs', '')),
|
|
14
|
+
});
|
|
15
|
+
const builtInTemplatesMap = {
|
|
16
|
+
ts: ['index.ts.ejs'].map(mapper('ts')),
|
|
17
|
+
compiled: ['client.js.ejs', 'client.d.ts.ejs'].map(mapper('compiled')),
|
|
18
|
+
python: ['__init__.py'].map(mapper('python')),
|
|
19
|
+
};
|
|
20
|
+
const templateFiles = templates.reduce((acc, template) => {
|
|
21
|
+
if (template in builtInTemplatesMap) {
|
|
22
|
+
return [...acc, ...builtInTemplatesMap[template]];
|
|
38
23
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
dts += `
|
|
46
|
-
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
47
|
-
`;
|
|
48
|
-
ts += `
|
|
49
|
-
${validateOnClientImportPath ? `import validateOnClient from '${validateOnClientImportPath}';\n` : '\nconst validateOnClient = undefined;'}
|
|
50
|
-
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
51
|
-
const prefix = '${apiEntryPoint}';
|
|
52
|
-
`;
|
|
53
|
-
js += `
|
|
54
|
-
const { default: validateOnClient = null } = ${validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'};
|
|
55
|
-
const prefix = '${apiEntryPoint}';
|
|
56
|
-
`;
|
|
24
|
+
return [...acc, {
|
|
25
|
+
templatePath: path.resolve(cwd, template),
|
|
26
|
+
outPath: path.join(clientOutDirAbsolutePath, path.basename(template).replace('.ejs', ''))
|
|
27
|
+
}];
|
|
28
|
+
}, []);
|
|
29
|
+
// Ensure that each segment has a matching schema if it needs to be emitted:
|
|
57
30
|
for (let i = 0; i < segments.length; i++) {
|
|
58
31
|
const { segmentName } = segments[i];
|
|
59
32
|
const schema = segmentsSchema[segmentName];
|
|
@@ -62,36 +35,57 @@ const prefix = '${apiEntryPoint}';
|
|
|
62
35
|
}
|
|
63
36
|
if (!schema.emitSchema)
|
|
64
37
|
continue;
|
|
65
|
-
for (const key of Object.keys(schema.controllers)) {
|
|
66
|
-
dts += `export const ${key}: ReturnType<typeof clientizeController<Controllers${i}["${key}"], Options>>;\n`;
|
|
67
|
-
js += `exports.${key} = clientizeController(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { prefix } });\n`;
|
|
68
|
-
ts += `export const ${key} = clientizeController<Controllers${i}["${key}"], Options>(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { prefix } });\n`;
|
|
69
|
-
}
|
|
70
|
-
for (const key of Object.keys(schema.workers)) {
|
|
71
|
-
dts += `export const ${key}: ReturnType<typeof promisifyWorker<Workers${i}["${key}"]>>;\n`;
|
|
72
|
-
js += `exports.${key} = promisifyWorker(null, schema['${segmentName}'].workers.${key});\n`;
|
|
73
|
-
ts += `export const ${key} = promisifyWorker<Workers${i}["${key}"]>(null, schema['${segmentName}'].workers.${key});\n`;
|
|
74
|
-
}
|
|
75
38
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
// Data for the EJS templates:
|
|
41
|
+
const ejsData = {
|
|
42
|
+
apiEntryPoint,
|
|
43
|
+
fetcherClientImportPath,
|
|
44
|
+
schemaOutImportPath,
|
|
45
|
+
validateOnClientImportPath,
|
|
46
|
+
segments,
|
|
47
|
+
segmentsSchema,
|
|
48
|
+
};
|
|
49
|
+
// 1. Process each template in parallel
|
|
50
|
+
const processedTemplates = noClient ? [] : await Promise.all(templateFiles.map(async ({ templatePath, outPath }) => {
|
|
51
|
+
// Read the EJS template
|
|
52
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
53
|
+
// Render the template
|
|
54
|
+
let rendered = templatePath.endsWith('.ejs') ? ejs.render(templateContent, ejsData) : templateContent;
|
|
55
|
+
// Optionally prettify
|
|
56
|
+
if (prettifyClient || config.prettifyClient) {
|
|
57
|
+
rendered = await prettify(rendered, outPath);
|
|
58
|
+
}
|
|
59
|
+
// Read existing file content to compare
|
|
60
|
+
const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
|
|
61
|
+
// Determine if we need to rewrite the file
|
|
62
|
+
const needsWriting = existingContent !== rendered;
|
|
63
|
+
return {
|
|
64
|
+
outPath,
|
|
65
|
+
rendered,
|
|
66
|
+
needsWriting,
|
|
67
|
+
};
|
|
68
|
+
}));
|
|
69
|
+
if (fullSchema) {
|
|
70
|
+
const fullSchemaOutAbsolutePath = path.resolve(clientOutDirAbsolutePath, typeof fullSchema === 'string' ? fullSchema : 'full-schema.json');
|
|
71
|
+
await fs.writeFile(fullSchemaOutAbsolutePath, JSON.stringify(segmentsSchema, null, 2));
|
|
72
|
+
log.info(`Full schema written to ${fullSchemaOutAbsolutePath}`);
|
|
86
73
|
}
|
|
87
|
-
|
|
74
|
+
// 2. Check if any file needs rewriting
|
|
75
|
+
const anyNeedsWriting = processedTemplates.some(({ needsWriting }) => needsWriting);
|
|
76
|
+
if (!anyNeedsWriting) {
|
|
88
77
|
log.debug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
|
|
89
|
-
return { written: false, path:
|
|
78
|
+
return { written: false, path: clientOutDirAbsolutePath };
|
|
90
79
|
}
|
|
91
|
-
|
|
92
|
-
await fs.
|
|
93
|
-
|
|
94
|
-
await
|
|
80
|
+
// 3. Make sure the output directory exists
|
|
81
|
+
await fs.mkdir(clientOutDirAbsolutePath, { recursive: true });
|
|
82
|
+
// 4. Write updated files where needed
|
|
83
|
+
await Promise.all(processedTemplates.map(({ outPath, rendered, needsWriting }) => {
|
|
84
|
+
if (needsWriting) {
|
|
85
|
+
return fs.writeFile(outPath, rendered);
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}));
|
|
95
89
|
log.info(`Client generated in ${Date.now() - now}ms`);
|
|
96
|
-
return { written: true, path:
|
|
90
|
+
return { written: true, path: clientOutDirAbsolutePath };
|
|
97
91
|
}
|
|
@@ -15,7 +15,7 @@ export default async function getConfig({ clientOutDir, cwd }) {
|
|
|
15
15
|
origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
16
16
|
rootEntry: env.VOVK_ROOT_ENTRY ?? conf.rootEntry ?? 'api',
|
|
17
17
|
rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? conf.rootSegmentModulesDirName ?? '',
|
|
18
|
-
logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? '
|
|
18
|
+
logLevel: env.VOVK_LOG_LEVEL ?? conf.logLevel ?? 'info',
|
|
19
19
|
prettifyClient: (env.VOVK_PRETTIFY_CLIENT ? !!env.VOVK_PRETTIFY_CLIENT : null) ?? conf.prettifyClient ?? false,
|
|
20
20
|
devHttps: (env.VOVK_DEV_HTTPS ? !!env.VOVK_DEV_HTTPS : null) ?? conf.devHttps ?? false,
|
|
21
21
|
templates: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
|
|
4
4
|
const rootDir = path.resolve(cwd, relativePath || '');
|
|
5
5
|
const baseName = 'vovk.config';
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
|
|
3
3
|
export default async function getRelativeSrcRoot({ cwd }) {
|
|
4
4
|
// Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
|
|
5
|
-
if (await
|
|
5
|
+
if ((await getFileSystemEntryType(path.join(cwd, 'app'))) === FileSystemEntryType.DIRECTORY) {
|
|
6
6
|
return '.';
|
|
7
7
|
}
|
|
8
|
-
else if (await
|
|
8
|
+
else if ((await getFileSystemEntryType(path.join(cwd, 'src/app'))) === FileSystemEntryType.DIRECTORY) {
|
|
9
9
|
return './src';
|
|
10
10
|
}
|
|
11
11
|
throw new Error(`${cwd} Could not find app router directory. Check Next.js docs for more info.`);
|