vovk-cli 0.0.1-draft.42 → 0.0.1-draft.44
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/client-templates/compiled/{client.d.ts.ejs → compiled.d.ts.ejs} +8 -6
- package/client-templates/compiled/{client.js.ejs → compiled.js.ejs} +4 -5
- package/client-templates/ts/index.ts.ejs +6 -6
- package/dist/dev/diffSchema.d.mts +2 -3
- package/dist/dev/ensureClient.d.mts +1 -1
- package/dist/dev/ensureClient.mjs +3 -4
- package/dist/dev/ensureSchemaFiles.d.mts +3 -0
- package/dist/dev/ensureSchemaFiles.mjs +4 -1
- package/dist/dev/index.d.mts +3 -1
- package/dist/dev/index.mjs +43 -18
- package/dist/dev/logDiffResult.mjs +9 -9
- package/dist/generate/getClientTemplates.d.mts +11 -0
- package/dist/generate/getClientTemplates.mjs +28 -0
- package/dist/generate/index.d.mts +12 -0
- package/dist/generate/index.mjs +78 -0
- package/dist/getProjectInfo/getConfig.mjs +2 -1
- package/dist/getProjectInfo/index.d.mts +1 -1
- package/dist/getProjectInfo/index.mjs +2 -2
- package/dist/index.mjs +18 -12
- package/dist/init/updateDependenciesWithoutInstalling.mjs +1 -1
- package/dist/locateSegments.d.mts +1 -1
- package/dist/locateSegments.mjs +1 -1
- package/dist/postinstall.mjs +14 -17
- package/dist/types.d.mts +3 -1
- package/package.json +15 -15
- package/dist/generateClient.d.mts +0 -12
- package/dist/generateClient.mjs +0 -91
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
|
|
1
2
|
<%- '// auto-generated\n/* eslint-disable */' %>
|
|
2
|
-
import type {
|
|
3
|
-
import type { promisifyWorker } from 'vovk/worker';
|
|
3
|
+
import type { createRPC, createWPC, VovkClientFetcher } from 'vovk';
|
|
4
4
|
import type fetcher from '<%= fetcherClientImportPath %>';
|
|
5
|
-
<% segments.forEach((segment, i) => {
|
|
6
|
-
|
|
5
|
+
<% segments.forEach((segment, i) => {
|
|
6
|
+
const hasWorkers = !!Object.keys(segmentsSchema[segment.segmentName].workers).length;
|
|
7
|
+
%>
|
|
8
|
+
import type { Controllers as Controllers<%= i %><% if(hasWorkers) { %>, Workers as Workers<%= i %> <% } %> } from "<%= segment.segmentImportPath %>";
|
|
7
9
|
<% }) %>
|
|
8
10
|
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
9
11
|
|
|
@@ -14,9 +16,9 @@ type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
|
14
16
|
const workers = Object.keys(segSchema.workers);
|
|
15
17
|
%>
|
|
16
18
|
<% controllers.forEach((key) => { %>
|
|
17
|
-
export const <%= key %>: ReturnType<typeof
|
|
19
|
+
export const <%= key %>: ReturnType<typeof createRPC<Controllers<%= i %>["<%= key %>"], Options>>;
|
|
18
20
|
<% }) %>
|
|
19
21
|
<% workers.forEach((key) => { %>
|
|
20
|
-
export const <%= key %>: ReturnType<typeof
|
|
22
|
+
export const <%= key %>: ReturnType<typeof createWPC<Workers<%= i %>["<%= key %>"]>>;
|
|
21
23
|
<% }) %>
|
|
22
24
|
<% }) %>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<%- '// auto-generated\n/* eslint-disable */' %>
|
|
2
|
-
const {
|
|
3
|
-
const { promisifyWorker } = require('vovk/worker');
|
|
2
|
+
const { createRPC, createWPC } = require('vovk');
|
|
4
3
|
const { default: fetcher } = require('<%= fetcherClientImportPath %>');
|
|
5
4
|
const schema = require('<%= schemaOutImportPath %>');
|
|
6
5
|
|
|
7
6
|
const { default: validateOnClient = null } = <%- validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'%>;
|
|
8
|
-
const apiRoot = '<%=
|
|
7
|
+
const apiRoot = '<%= apiRoot %>';
|
|
9
8
|
|
|
10
9
|
<% segments.forEach((segment) => {
|
|
11
10
|
const segSchema = segmentsSchema[segment.segmentName];
|
|
@@ -14,13 +13,13 @@ const apiRoot = '<%= apiEntryPoint %>';
|
|
|
14
13
|
const workers = Object.keys(segSchema.workers);
|
|
15
14
|
%>
|
|
16
15
|
<% controllers.forEach((key) => { %>
|
|
17
|
-
exports.<%= key %> =
|
|
16
|
+
exports.<%= key %> = createRPC(
|
|
18
17
|
schema['<%= segment.segmentName %>'].controllers.<%= key %>,
|
|
19
18
|
'<%= segment.segmentName %>',
|
|
20
19
|
{ fetcher, validateOnClient, defaultOptions: { apiRoot } }
|
|
21
20
|
);
|
|
22
21
|
<% }) %>
|
|
23
22
|
<% workers.forEach((key) => { %>
|
|
24
|
-
exports.<%= key %> =
|
|
23
|
+
exports.<%= key %> = createWPC(null, schema['<%= segment.segmentName %>'].workers.<%= key %>);
|
|
25
24
|
<% }) %>
|
|
26
25
|
<% }) %>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<%- '// auto-generated\n/* eslint-disable */' %>
|
|
2
|
-
import {
|
|
3
|
-
import { promisifyWorker } from 'vovk/worker';
|
|
2
|
+
import { createRPC, createWPC, type VovkClientFetcher } from 'vovk';
|
|
4
3
|
import fetcher from '<%= fetcherClientImportPath %>';
|
|
5
4
|
import schema from '<%= schemaOutImportPath %>';
|
|
6
5
|
<% if (validateOnClientImportPath) { %>
|
|
@@ -9,16 +8,17 @@ import validateOnClient from '<%= validateOnClientImportPath %>';
|
|
|
9
8
|
const validateOnClient = undefined;
|
|
10
9
|
<% } %>
|
|
11
10
|
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
12
|
-
const apiRoot = '<%=
|
|
11
|
+
const apiRoot = '<%= apiRoot %>';
|
|
13
12
|
|
|
14
13
|
<% segments.forEach((segment, i) => {
|
|
15
14
|
const segSchema = segmentsSchema[segment.segmentName];
|
|
16
15
|
if (!segSchema || !segSchema.emitSchema) return;
|
|
16
|
+
const hasWorkers = !!Object.keys(segmentsSchema[segment.segmentName].workers).length;
|
|
17
17
|
%>
|
|
18
|
-
import type { Controllers as Controllers<%= i %>, Workers as Workers<%= i %> } from "<%= segment.segmentImportPath %>";
|
|
18
|
+
import type { Controllers as Controllers<%= i %><% if(hasWorkers) { %>, Workers as Workers<%= i %> <% } %>} from "<%= segment.segmentImportPath %>";
|
|
19
19
|
|
|
20
20
|
<% Object.keys(segSchema.controllers).forEach((key) => { %>
|
|
21
|
-
export const <%= key %> =
|
|
21
|
+
export const <%= key %> = createRPC<Controllers<%= i %>["<%= key %>"], Options>(
|
|
22
22
|
schema['<%= segment.segmentName %>'].controllers.<%= key %>,
|
|
23
23
|
'<%= segment.segmentName %>',
|
|
24
24
|
{ fetcher, validateOnClient, defaultOptions: { apiRoot } }
|
|
@@ -26,7 +26,7 @@ export const <%= key %> = clientizeController<Controllers<%= i %>["<%= key %>"],
|
|
|
26
26
|
<% }) %>
|
|
27
27
|
|
|
28
28
|
<% Object.keys(segSchema.workers).forEach((key) => { %>
|
|
29
|
-
export const <%= key %> =
|
|
29
|
+
export const <%= key %> = createWPC<Workers<%= i %>["<%= key %>"]>(
|
|
30
30
|
null,
|
|
31
31
|
schema['<%= segment.segmentName %>'].workers.<%= key %>
|
|
32
32
|
);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import type { _VovkControllerSchema, _VovkWorkerSchema } from 'vovk/types';
|
|
1
|
+
import type { VovkControllerSchema, VovkWorkerSchema, VovkSchema } from 'vovk';
|
|
3
2
|
interface HandlersDiff {
|
|
4
3
|
nameOfClass: string;
|
|
5
4
|
added: string[];
|
|
@@ -15,7 +14,7 @@ export interface DiffResult {
|
|
|
15
14
|
workers: WorkersOrControllersDiff;
|
|
16
15
|
controllers: WorkersOrControllersDiff;
|
|
17
16
|
}
|
|
18
|
-
export declare function diffHandlers<T extends
|
|
17
|
+
export declare function diffHandlers<T extends VovkWorkerSchema['handlers'] | VovkControllerSchema['handlers']>(oldHandlers: T, newHandlers: T, nameOfClass: string): HandlersDiff;
|
|
19
18
|
export declare function diffWorkersOrControllers<T extends VovkSchema['controllers'] | VovkSchema['workers']>(oldItems: T, newItems: T): WorkersOrControllersDiff;
|
|
20
19
|
/**
|
|
21
20
|
example output:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
-
export default async function ensureClient(
|
|
4
|
-
const { config, cwd, log } = projectInfo;
|
|
3
|
+
export default async function ensureClient({ config, cwd, log }) {
|
|
5
4
|
const now = Date.now();
|
|
6
5
|
const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
|
|
7
6
|
const dts = `// auto-generated
|
|
@@ -10,8 +9,8 @@ export default async function ensureClient(projectInfo) {
|
|
|
10
9
|
// Feel free to report an issue at https://github.com/finom/vovk/issues`;
|
|
11
10
|
const js = dts;
|
|
12
11
|
const ts = dts;
|
|
13
|
-
const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, '
|
|
14
|
-
const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, '
|
|
12
|
+
const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'compiled.js');
|
|
13
|
+
const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'compiled.d.ts');
|
|
15
14
|
const localTsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'index.ts');
|
|
16
15
|
const existingJs = await fs.readFile(localJsAbsolutePath, 'utf-8').catch(() => null);
|
|
17
16
|
const existingDts = await fs.readFile(localDtsAbsolutePath, 'utf-8').catch(() => null);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Ensure that the schema files are created to avoid any import errors.
|
|
4
|
+
*/
|
|
2
5
|
export default function ensureSchemaFiles(projectInfo: ProjectInfo | null, schemaOutAbsolutePath: string, segmentNames: string[]): Promise<void>;
|
|
3
6
|
export declare const debouncedEnsureSchemaFiles: import("lodash").DebouncedFunc<typeof ensureSchemaFiles>;
|
|
@@ -3,6 +3,9 @@ import path from 'node:path';
|
|
|
3
3
|
import debounce from 'lodash/debounce.js';
|
|
4
4
|
import writeOneSchemaFile, { ROOT_SEGMENT_SCHEMA_NAME } from './writeOneSchemaFile.mjs';
|
|
5
5
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
6
|
+
/**
|
|
7
|
+
* Ensure that the schema files are created to avoid any import errors.
|
|
8
|
+
*/
|
|
6
9
|
export default async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePath, segmentNames) {
|
|
7
10
|
const now = Date.now();
|
|
8
11
|
let hasChanged = false;
|
|
@@ -78,6 +81,6 @@ export default segmentSchema;`;
|
|
|
78
81
|
// Start the recursive deletion from the root directory
|
|
79
82
|
await deleteUnnecessaryJsonFiles(schemaOutAbsolutePath);
|
|
80
83
|
if (hasChanged)
|
|
81
|
-
projectInfo?.log.info(`
|
|
84
|
+
projectInfo?.log.info(`Created empty schema files in ${Date.now() - now}ms`);
|
|
82
85
|
}
|
|
83
86
|
export const debouncedEnsureSchemaFiles = debounce(ensureSchemaFiles, 1000);
|
package/dist/dev/index.d.mts
CHANGED
package/dist/dev/index.mjs
CHANGED
|
@@ -6,12 +6,13 @@ import keyBy from 'lodash/keyBy.js';
|
|
|
6
6
|
import capitalize from 'lodash/capitalize.js';
|
|
7
7
|
import debounce from 'lodash/debounce.js';
|
|
8
8
|
import isEmpty from 'lodash/isEmpty.js';
|
|
9
|
+
import once from 'lodash/once.js';
|
|
9
10
|
import { debouncedEnsureSchemaFiles } from './ensureSchemaFiles.mjs';
|
|
10
11
|
import writeOneSchemaFile from './writeOneSchemaFile.mjs';
|
|
11
12
|
import logDiffResult from './logDiffResult.mjs';
|
|
12
13
|
import ensureClient from './ensureClient.mjs';
|
|
13
14
|
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
14
|
-
import
|
|
15
|
+
import generate from '../generate/index.mjs';
|
|
15
16
|
import locateSegments from '../locateSegments.mjs';
|
|
16
17
|
import debounceWithArgs from '../utils/debounceWithArgs.mjs';
|
|
17
18
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
@@ -22,6 +23,7 @@ export class VovkDev {
|
|
|
22
23
|
#isWatching = false;
|
|
23
24
|
#modulesWatcher = null;
|
|
24
25
|
#segmentWatcher = null;
|
|
26
|
+
#onFirstTimeGenerate = null;
|
|
25
27
|
#watchSegments = (callback) => {
|
|
26
28
|
const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
|
|
27
29
|
const { cwd, log, config, apiDir } = this.#projectInfo;
|
|
@@ -40,11 +42,14 @@ export class VovkDev {
|
|
|
40
42
|
const segmentName = getSegmentName(filePath);
|
|
41
43
|
this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
|
|
42
44
|
? this.#segments
|
|
43
|
-
: [
|
|
45
|
+
: [
|
|
46
|
+
...this.#segments,
|
|
47
|
+
{
|
|
44
48
|
routeFilePath: filePath,
|
|
45
49
|
segmentName,
|
|
46
|
-
segmentImportPath: path.relative(config.clientOutDir, filePath) // TODO DRY locateSegments
|
|
47
|
-
}
|
|
50
|
+
segmentImportPath: path.relative(config.clientOutDir, filePath), // TODO DRY locateSegments
|
|
51
|
+
},
|
|
52
|
+
];
|
|
48
53
|
log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
|
|
49
54
|
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
50
55
|
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
@@ -138,7 +143,7 @@ export class VovkDev {
|
|
|
138
143
|
await this.#segmentWatcher?.close();
|
|
139
144
|
await Promise.all([
|
|
140
145
|
new Promise((resolve) => this.#watchModules(() => resolve(0))),
|
|
141
|
-
new Promise((resolve) => this.#watchSegments(() => resolve(0)))
|
|
146
|
+
new Promise((resolve) => this.#watchSegments(() => resolve(0))),
|
|
142
147
|
]);
|
|
143
148
|
if (isInitial) {
|
|
144
149
|
callback();
|
|
@@ -150,7 +155,14 @@ export class VovkDev {
|
|
|
150
155
|
}, 1000);
|
|
151
156
|
chokidar
|
|
152
157
|
// .watch(['vovk.config.{js,mjs,cjs}', '.config/vovk.config.{js,mjs,cjs}'], {
|
|
153
|
-
.watch([
|
|
158
|
+
.watch([
|
|
159
|
+
'vovk.config.js',
|
|
160
|
+
'vovk.config.mjs',
|
|
161
|
+
'vovk.config.cjs',
|
|
162
|
+
'.config/vovk.config.js',
|
|
163
|
+
'.config/vovk.config.mjs',
|
|
164
|
+
'.config/vovk.config.cjs',
|
|
165
|
+
], {
|
|
154
166
|
persistent: true,
|
|
155
167
|
cwd,
|
|
156
168
|
ignoreInitial: false,
|
|
@@ -215,9 +227,9 @@ export class VovkDev {
|
|
|
215
227
|
}
|
|
216
228
|
};
|
|
217
229
|
#requestSchema = debounceWithArgs(async (segmentName) => {
|
|
218
|
-
const {
|
|
230
|
+
const { apiRoot, log, port, config } = this.#projectInfo;
|
|
219
231
|
const { devHttps } = config;
|
|
220
|
-
const endpoint = `${
|
|
232
|
+
const endpoint = `${apiRoot.startsWith(`http${devHttps ? 's' : ''}://`) ? apiRoot : `http${devHttps ? 's' : ''}://localhost:${port}${apiRoot}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
|
|
221
233
|
log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
|
|
222
234
|
try {
|
|
223
235
|
const resp = await fetch(endpoint);
|
|
@@ -238,11 +250,12 @@ export class VovkDev {
|
|
|
238
250
|
await this.#handleSchema(schema);
|
|
239
251
|
}
|
|
240
252
|
catch (error) {
|
|
241
|
-
log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
253
|
+
log.error(`Error requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}: ${error.message}`);
|
|
242
254
|
return { isError: true };
|
|
243
255
|
}
|
|
244
256
|
return { isError: false };
|
|
245
257
|
}, 500);
|
|
258
|
+
#generate = debounce(() => generate({ projectInfo: this.#projectInfo, segments: this.#segments, segmentsSchema: this.#schemas }).then(this.#onFirstTimeGenerate), 1000);
|
|
246
259
|
async #handleSchema(schema) {
|
|
247
260
|
const { log, config, cwd } = this.#projectInfo;
|
|
248
261
|
if (!schema) {
|
|
@@ -275,14 +288,19 @@ export class VovkDev {
|
|
|
275
288
|
}
|
|
276
289
|
if (this.#segments.every((s) => this.#schemas[s.segmentName])) {
|
|
277
290
|
log.debug(`All segments with "emitSchema" have schema.`);
|
|
278
|
-
|
|
291
|
+
this.#generate();
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
|
-
async start() {
|
|
294
|
+
async start({ exit }) {
|
|
282
295
|
const now = Date.now();
|
|
283
296
|
this.#projectInfo = await getProjectInfo();
|
|
284
297
|
const { log, config, cwd, apiDir } = this.#projectInfo;
|
|
285
298
|
log.info('Starting...');
|
|
299
|
+
if (exit) {
|
|
300
|
+
this.#onFirstTimeGenerate = once(() => {
|
|
301
|
+
log.info('The schemas and the RPC client have been generated. Exiting...');
|
|
302
|
+
});
|
|
303
|
+
}
|
|
286
304
|
if (config.devHttps) {
|
|
287
305
|
const agent = new Agent({
|
|
288
306
|
connect: {
|
|
@@ -301,10 +319,11 @@ export class VovkDev {
|
|
|
301
319
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
302
320
|
this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
303
321
|
await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
322
|
+
const MAX_ATTEMPTS = 5;
|
|
323
|
+
const DELAY = 5000;
|
|
304
324
|
// Request schema every segment in 5 seconds in order to update schema on start
|
|
305
325
|
setTimeout(() => {
|
|
306
326
|
for (const { segmentName } of this.#segments) {
|
|
307
|
-
const MAX_ATTEMPTS = 3;
|
|
308
327
|
let attempts = 0;
|
|
309
328
|
void this.#requestSchema(segmentName).then(({ isError }) => {
|
|
310
329
|
if (isError) {
|
|
@@ -318,19 +337,25 @@ export class VovkDev {
|
|
|
318
337
|
void this.#requestSchema(segmentName).then(({ isError: isError2 }) => {
|
|
319
338
|
if (!isError2) {
|
|
320
339
|
clearInterval(interval);
|
|
340
|
+
log.info(`Requested schema for ${formatLoggedSegmentName(segmentName)} after ${attempts} attempts`);
|
|
321
341
|
}
|
|
322
342
|
});
|
|
323
|
-
},
|
|
343
|
+
}, DELAY);
|
|
324
344
|
}
|
|
325
345
|
});
|
|
326
346
|
}
|
|
327
|
-
},
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
347
|
+
}, DELAY);
|
|
348
|
+
if (!exit) {
|
|
349
|
+
this.#watch(() => {
|
|
350
|
+
log.info(`Ready in ${Date.now() - now}ms. Making initial requests for schemas in a moment...`);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
log.info(`Ready in ${Date.now() - now}ms. Making initial requests for schemas in a moment...`);
|
|
355
|
+
}
|
|
331
356
|
}
|
|
332
357
|
}
|
|
333
358
|
const env = process.env;
|
|
334
359
|
if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
|
|
335
|
-
void new VovkDev().start();
|
|
360
|
+
void new VovkDev().start({ exit: env.__VOVK_EXIT__ === 'true' });
|
|
336
361
|
}
|
|
@@ -46,43 +46,43 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
|
46
46
|
case 'worker':
|
|
47
47
|
switch (diffNormalizedItem.type) {
|
|
48
48
|
case 'added':
|
|
49
|
-
projectInfo.log.info(`Schema for
|
|
49
|
+
projectInfo.log.info(`Schema for WPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
50
50
|
break;
|
|
51
51
|
case 'removed':
|
|
52
|
-
projectInfo.log.info(`Schema for
|
|
52
|
+
projectInfo.log.info(`Schema for WPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
53
53
|
break;
|
|
54
54
|
}
|
|
55
55
|
break;
|
|
56
56
|
case 'controller':
|
|
57
57
|
switch (diffNormalizedItem.type) {
|
|
58
58
|
case 'added':
|
|
59
|
-
projectInfo.log.info(`Schema
|
|
59
|
+
projectInfo.log.info(`Schema forn RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
60
60
|
break;
|
|
61
61
|
case 'removed':
|
|
62
|
-
projectInfo.log.info(`Schema
|
|
62
|
+
projectInfo.log.info(`Schema forn RPC ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
63
63
|
break;
|
|
64
64
|
}
|
|
65
65
|
break;
|
|
66
66
|
case 'workerHandler':
|
|
67
67
|
switch (diffNormalizedItem.type) {
|
|
68
68
|
case 'added':
|
|
69
|
-
projectInfo.log.info(`Schema for
|
|
69
|
+
projectInfo.log.info(`Schema for WPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
70
70
|
break;
|
|
71
71
|
case 'removed':
|
|
72
|
-
projectInfo.log.info(`Schema for
|
|
72
|
+
projectInfo.log.info(`Schema for WPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
break;
|
|
76
76
|
case 'controllerHandler':
|
|
77
77
|
switch (diffNormalizedItem.type) {
|
|
78
78
|
case 'added':
|
|
79
|
-
projectInfo.log.info(`Schema
|
|
79
|
+
projectInfo.log.info(`Schema forn RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${addedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
80
80
|
break;
|
|
81
81
|
case 'removed':
|
|
82
|
-
projectInfo.log.info(`Schema
|
|
82
|
+
projectInfo.log.info(`Schema forn RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${removedText} from ${formatLoggedSegmentName(segmentName)}`);
|
|
83
83
|
break;
|
|
84
84
|
case 'changed':
|
|
85
|
-
projectInfo.log.info(`Schema
|
|
85
|
+
projectInfo.log.info(`Schema forn RPC method ${chalkHighlightThing(diffNormalizedItem.name)} has been ${changedText} at ${formatLoggedSegmentName(segmentName)}`);
|
|
86
86
|
break;
|
|
87
87
|
}
|
|
88
88
|
break;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { VovkConfig } from '../types.mjs';
|
|
2
|
+
interface ClientTemplate {
|
|
3
|
+
templatePath: string;
|
|
4
|
+
outPath: string;
|
|
5
|
+
}
|
|
6
|
+
export default function getClientTemplates({ config, cwd, templateNames, }: {
|
|
7
|
+
config: Required<VovkConfig>;
|
|
8
|
+
cwd: string;
|
|
9
|
+
templateNames?: string[];
|
|
10
|
+
}): ClientTemplate[];
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export default function getClientTemplates({ config, cwd, templateNames = [], }) {
|
|
3
|
+
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
4
|
+
const templatesDir = path.join(__dirname, '../..', 'client-templates');
|
|
5
|
+
const clientOutDirAbsolutePath = path.resolve(cwd, config.clientOutDir);
|
|
6
|
+
const mapper = (dir) => (name) => ({
|
|
7
|
+
templatePath: path.resolve(templatesDir, dir, name),
|
|
8
|
+
outPath: path.join(clientOutDirAbsolutePath, name.replace('.ejs', '')),
|
|
9
|
+
});
|
|
10
|
+
const builtInTemplatesMap = {
|
|
11
|
+
ts: ['index.ts.ejs'].map(mapper('ts')),
|
|
12
|
+
compiled: ['compiled.js.ejs', 'compiled.d.ts.ejs'].map(mapper('compiled')),
|
|
13
|
+
python: ['__init__.py'].map(mapper('python')),
|
|
14
|
+
};
|
|
15
|
+
const templateFiles = (templateNames ?? config.experimental_clientGenerateTemplateNames).reduce((acc, template) => {
|
|
16
|
+
if (template in builtInTemplatesMap) {
|
|
17
|
+
return [...acc, ...builtInTemplatesMap[template]];
|
|
18
|
+
}
|
|
19
|
+
return [
|
|
20
|
+
...acc,
|
|
21
|
+
{
|
|
22
|
+
templatePath: path.resolve(cwd, template),
|
|
23
|
+
outPath: path.join(clientOutDirAbsolutePath, path.basename(template).replace('.ejs', '')),
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
}, []);
|
|
27
|
+
return templateFiles;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VovkSchema } from 'vovk';
|
|
2
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
+
import type { Segment } from '../locateSegments.mjs';
|
|
4
|
+
import { GenerateOptions } from '../types.mjs';
|
|
5
|
+
export default function generate({ projectInfo, segments, segmentsSchema, templates, prettify: prettifyClient, fullSchema, }: {
|
|
6
|
+
projectInfo: ProjectInfo;
|
|
7
|
+
segments: Segment[];
|
|
8
|
+
segmentsSchema: Record<string, VovkSchema>;
|
|
9
|
+
} & Pick<GenerateOptions, 'templates' | 'prettify' | 'fullSchema'>): Promise<{
|
|
10
|
+
written: boolean;
|
|
11
|
+
path: string;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import ejs from 'ejs';
|
|
4
|
+
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
5
|
+
import prettify from '../utils/prettify.mjs';
|
|
6
|
+
import getClientTemplates from './getClientTemplates.mjs';
|
|
7
|
+
export default async function generate({ projectInfo, segments, segmentsSchema, templates, prettify: prettifyClient, fullSchema, }) {
|
|
8
|
+
templates = templates ?? projectInfo.config.experimental_clientGenerateTemplateNames;
|
|
9
|
+
const noClient = templates?.[0] === 'none';
|
|
10
|
+
const { config, cwd, log, validateOnClientImportPath, apiRoot, fetcherClientImportPath, schemaOutImportPath } = projectInfo;
|
|
11
|
+
const clientOutDirAbsolutePath = path.resolve(cwd, config.clientOutDir);
|
|
12
|
+
const templateFiles = getClientTemplates({ config, cwd, templateNames: templates });
|
|
13
|
+
// Ensure that each segment has a matching schema if it needs to be emitted:
|
|
14
|
+
for (let i = 0; i < segments.length; i++) {
|
|
15
|
+
const { segmentName } = segments[i];
|
|
16
|
+
const schema = segmentsSchema[segmentName];
|
|
17
|
+
if (!schema) {
|
|
18
|
+
throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
|
|
19
|
+
}
|
|
20
|
+
if (!schema.emitSchema)
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
// Data for the EJS templates:
|
|
25
|
+
const ejsData = {
|
|
26
|
+
apiRoot,
|
|
27
|
+
fetcherClientImportPath,
|
|
28
|
+
schemaOutImportPath,
|
|
29
|
+
validateOnClientImportPath,
|
|
30
|
+
segments,
|
|
31
|
+
segmentsSchema,
|
|
32
|
+
};
|
|
33
|
+
// Process each template in parallel
|
|
34
|
+
const processedTemplates = noClient
|
|
35
|
+
? []
|
|
36
|
+
: await Promise.all(templateFiles.map(async ({ templatePath, outPath }) => {
|
|
37
|
+
// Read the EJS template
|
|
38
|
+
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
39
|
+
// Render the template
|
|
40
|
+
let rendered = templatePath.endsWith('.ejs') ? ejs.render(templateContent, ejsData) : templateContent;
|
|
41
|
+
// Optionally prettify
|
|
42
|
+
if (prettifyClient || config.prettifyClient) {
|
|
43
|
+
rendered = await prettify(rendered, outPath);
|
|
44
|
+
}
|
|
45
|
+
// Read existing file content to compare
|
|
46
|
+
const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
|
|
47
|
+
// Determine if we need to rewrite the file
|
|
48
|
+
const needsWriting = existingContent !== rendered;
|
|
49
|
+
return {
|
|
50
|
+
outPath,
|
|
51
|
+
rendered,
|
|
52
|
+
needsWriting,
|
|
53
|
+
};
|
|
54
|
+
}));
|
|
55
|
+
const anyNeedsWriting = processedTemplates.some(({ needsWriting }) => needsWriting);
|
|
56
|
+
if (fullSchema || anyNeedsWriting) {
|
|
57
|
+
// Make sure the output directory exists
|
|
58
|
+
await fs.mkdir(clientOutDirAbsolutePath, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
if (fullSchema) {
|
|
61
|
+
const fullSchemaOutAbsolutePath = path.resolve(clientOutDirAbsolutePath, typeof fullSchema === 'string' ? fullSchema : 'full-schema.json');
|
|
62
|
+
await fs.writeFile(fullSchemaOutAbsolutePath, JSON.stringify(segmentsSchema, null, 2));
|
|
63
|
+
log.info(`Full schema has ben written to ${fullSchemaOutAbsolutePath}`);
|
|
64
|
+
}
|
|
65
|
+
if (!anyNeedsWriting) {
|
|
66
|
+
log.debug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
|
|
67
|
+
return { written: false, path: clientOutDirAbsolutePath };
|
|
68
|
+
}
|
|
69
|
+
// Write updated files where needed
|
|
70
|
+
await Promise.all(processedTemplates.map(({ outPath, rendered, needsWriting }) => {
|
|
71
|
+
if (needsWriting) {
|
|
72
|
+
return fs.writeFile(outPath, rendered);
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}));
|
|
76
|
+
log.info(`Client generated in ${Date.now() - now}ms`);
|
|
77
|
+
return { written: true, path: clientOutDirAbsolutePath };
|
|
78
|
+
}
|
|
@@ -9,7 +9,7 @@ export default async function getConfig({ clientOutDir, cwd }) {
|
|
|
9
9
|
modulesDir: env.VOVK_MODULES_DIR ?? conf.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/'),
|
|
10
10
|
validateOnClient: env.VOVK_VALIDATE_ON_CLIENT ?? conf.validateOnClient ?? null,
|
|
11
11
|
validationLibrary: env.VOVK_VALIDATION_LIBRARY ?? conf.validationLibrary ?? null,
|
|
12
|
-
fetcher: env.VOVK_FETCHER ?? conf.fetcher ?? 'vovk/client/defaultFetcher',
|
|
12
|
+
fetcher: env.VOVK_FETCHER ?? conf.fetcher ?? 'vovk/dist/client/defaultFetcher',
|
|
13
13
|
schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? conf.schemaOutDir ?? './.vovk-schema',
|
|
14
14
|
clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? conf.clientOutDir ?? './node_modules/.vovk-client',
|
|
15
15
|
origin: (env.VOVK_ORIGIN ?? conf.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
@@ -18,6 +18,7 @@ export default async function getConfig({ clientOutDir, cwd }) {
|
|
|
18
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
|
+
experimental_clientGenerateTemplateNames: conf.experimental_clientGenerateTemplateNames ?? ['ts', 'compiled'],
|
|
21
22
|
templates: {
|
|
22
23
|
service: 'vovk-cli/templates/service.ejs',
|
|
23
24
|
controller: 'vovk-cli/templates/controller.ejs',
|
|
@@ -6,7 +6,7 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, cw
|
|
|
6
6
|
// Make PORT available to the config file at getConfig
|
|
7
7
|
process.env.PORT = port;
|
|
8
8
|
const { config, srcRoot, configAbsolutePaths, userConfig, error } = await getConfig({ clientOutDir, cwd });
|
|
9
|
-
const
|
|
9
|
+
const apiRoot = `${config.origin ?? ''}/${config.rootEntry}`;
|
|
10
10
|
const apiDir = path.join(srcRoot, 'app', config.rootEntry);
|
|
11
11
|
const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir).replace(/\\/g, '/'); // windows fix
|
|
12
12
|
const fetcherClientImportPath = config.fetcher.startsWith('.')
|
|
@@ -25,7 +25,7 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, cw
|
|
|
25
25
|
return {
|
|
26
26
|
cwd,
|
|
27
27
|
port,
|
|
28
|
-
|
|
28
|
+
apiRoot,
|
|
29
29
|
apiDir,
|
|
30
30
|
srcRoot,
|
|
31
31
|
schemaOutImportPath,
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { Command } from 'commander';
|
|
|
7
7
|
import concurrently from 'concurrently';
|
|
8
8
|
import getAvailablePort from './utils/getAvailablePort.mjs';
|
|
9
9
|
import getProjectInfo from './getProjectInfo/index.mjs';
|
|
10
|
-
import
|
|
10
|
+
import generate from './generate/index.mjs';
|
|
11
11
|
import locateSegments from './locateSegments.mjs';
|
|
12
12
|
import { VovkDev } from './dev/index.mjs';
|
|
13
13
|
import newComponents from './new/index.mjs';
|
|
@@ -19,11 +19,13 @@ initProgram(program.command('init'));
|
|
|
19
19
|
program
|
|
20
20
|
.command('dev')
|
|
21
21
|
.description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
|
|
22
|
-
.option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation'
|
|
22
|
+
.option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation')
|
|
23
|
+
.option('--exit', 'Kill the processe when schema and client is generated')
|
|
23
24
|
.allowUnknownOption(true)
|
|
24
25
|
.action(async (options, command) => {
|
|
26
|
+
const { nextDev, exit = false } = options;
|
|
25
27
|
const portAttempts = 30;
|
|
26
|
-
const PORT = !
|
|
28
|
+
const PORT = !nextDev
|
|
27
29
|
? process.env.PORT
|
|
28
30
|
: process.env.PORT ||
|
|
29
31
|
(await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
|
|
@@ -34,7 +36,7 @@ program
|
|
|
34
36
|
if (!PORT) {
|
|
35
37
|
throw new Error('🐺 ❌ PORT env variable is required');
|
|
36
38
|
}
|
|
37
|
-
if (
|
|
39
|
+
if (nextDev) {
|
|
38
40
|
const { result } = concurrently([
|
|
39
41
|
{
|
|
40
42
|
command: `npx next dev ${command.args.join(' ')}`,
|
|
@@ -43,12 +45,17 @@ program
|
|
|
43
45
|
},
|
|
44
46
|
{
|
|
45
47
|
command: `node ${import.meta.dirname}/dev/index.mjs`,
|
|
46
|
-
name: 'Vovk Dev
|
|
47
|
-
env: {
|
|
48
|
+
name: 'Vovk Dev Watcher',
|
|
49
|
+
env: {
|
|
50
|
+
PORT,
|
|
51
|
+
__VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
|
|
52
|
+
__VOVK_EXIT__: exit ? 'true' : 'false',
|
|
53
|
+
},
|
|
48
54
|
},
|
|
49
55
|
], {
|
|
50
56
|
killOthers: ['failure', 'success'],
|
|
51
57
|
prefix: 'none',
|
|
58
|
+
successCondition: 'first',
|
|
52
59
|
});
|
|
53
60
|
try {
|
|
54
61
|
await result;
|
|
@@ -58,7 +65,7 @@ program
|
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
else {
|
|
61
|
-
void new VovkDev().start();
|
|
68
|
+
void new VovkDev().start({ exit });
|
|
62
69
|
}
|
|
63
70
|
});
|
|
64
71
|
program
|
|
@@ -66,19 +73,18 @@ program
|
|
|
66
73
|
.alias('g')
|
|
67
74
|
.description('Generate client')
|
|
68
75
|
.option('--out, --client-out-dir <path>', 'Path to output directory')
|
|
69
|
-
.option('--template, --templates <templates...>', 'Client code templates')
|
|
70
|
-
.option('--no-client', 'Do not generate client')
|
|
76
|
+
.option('--template, --templates <templates...>', 'Client code templates ("ts", "compiled", "python", "none", a custom path)')
|
|
71
77
|
.option('--full-schema [fileName]', 'Generate client with full schema')
|
|
72
78
|
.option('--prettify', 'Prettify output files')
|
|
73
79
|
.action(async (options) => {
|
|
74
|
-
const { clientOutDir, templates, prettify,
|
|
80
|
+
const { clientOutDir, templates, prettify, fullSchema } = options;
|
|
75
81
|
const projectInfo = await getProjectInfo({ clientOutDir });
|
|
76
82
|
const { cwd, config, apiDir } = projectInfo;
|
|
77
83
|
const segments = await locateSegments({ dir: apiDir, config });
|
|
78
84
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
79
85
|
const schemaImportUrl = pathToFileURL(path.join(schemaOutAbsolutePath, 'index.js')).href;
|
|
80
|
-
const { default: segmentsSchema } = await import(schemaImportUrl);
|
|
81
|
-
await
|
|
86
|
+
const { default: segmentsSchema } = (await import(schemaImportUrl));
|
|
87
|
+
await generate({ projectInfo, segments, segmentsSchema, templates, prettify, fullSchema });
|
|
82
88
|
});
|
|
83
89
|
program
|
|
84
90
|
.command('new [components...]')
|
|
@@ -5,7 +5,7 @@ import getNPMPackageMetadata from '../utils/getNPMPackageMetadata.mjs';
|
|
|
5
5
|
async function updateDeps({ packageJson, packageNames, channel, key, }) {
|
|
6
6
|
return Promise.all(packageNames.map(async (packageName) => {
|
|
7
7
|
const metadata = await getNPMPackageMetadata(packageName);
|
|
8
|
-
const isVovk = packageName.startsWith('vovk');
|
|
8
|
+
const isVovk = packageName.startsWith('vovk') && packageName !== 'vovk-mapped-types';
|
|
9
9
|
const latestVersion = metadata['dist-tags'][isVovk ? (channel ?? 'latest') : 'latest'];
|
|
10
10
|
if (!packageJson[key]) {
|
|
11
11
|
packageJson[key] = {};
|
|
@@ -4,7 +4,7 @@ export type Segment = {
|
|
|
4
4
|
segmentName: string;
|
|
5
5
|
segmentImportPath: string;
|
|
6
6
|
};
|
|
7
|
-
export default function locateSegments({ dir, rootDir, config }: {
|
|
7
|
+
export default function locateSegments({ dir, rootDir, config, }: {
|
|
8
8
|
dir: string;
|
|
9
9
|
rootDir?: string;
|
|
10
10
|
config: Required<VovkConfig> | null;
|
package/dist/locateSegments.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
|
|
4
4
|
// config: null is used for testing
|
|
5
|
-
export default async function locateSegments({ dir, rootDir, config }) {
|
|
5
|
+
export default async function locateSegments({ dir, rootDir, config, }) {
|
|
6
6
|
let results = [];
|
|
7
7
|
rootDir = rootDir ?? dir;
|
|
8
8
|
// Read the contents of the directory
|
package/dist/postinstall.mjs
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
* Checks if a file exists at the given path.
|
|
5
|
-
* @param {string} filePath - The path to the file.
|
|
6
|
-
* @returns {Promise<boolean>} - A promise that resolves to true if the file exists, false otherwise.
|
|
7
|
-
*/
|
|
8
|
-
const getFileSystemEntryType = async (filePath) => !!(await fs.stat(filePath).catch(() => false));
|
|
3
|
+
import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
|
|
9
4
|
async function postinstall() {
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
5
|
+
// TODO: The function doesn't consider client templates, how to do that?
|
|
6
|
+
const vovk = path.join(import.meta.dirname, '../../.vovk-client');
|
|
7
|
+
const js = path.join(vovk, 'compiled.js');
|
|
8
|
+
const ts = path.join(vovk, 'compiled.d.ts');
|
|
13
9
|
const index = path.join(vovk, 'index.ts');
|
|
14
|
-
if ((await getFileSystemEntryType(js)) ||
|
|
15
|
-
(await getFileSystemEntryType(ts)) ||
|
|
16
|
-
(await getFileSystemEntryType(index))) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
10
|
await fs.mkdir(vovk, { recursive: true });
|
|
20
|
-
await
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
if (!(await getFileSystemEntryType(js))) {
|
|
12
|
+
await fs.writeFile(js, '/* postinstall */');
|
|
13
|
+
}
|
|
14
|
+
if (!(await getFileSystemEntryType(ts))) {
|
|
15
|
+
await fs.writeFile(ts, '/* postinstall */');
|
|
16
|
+
}
|
|
17
|
+
if (!(await getFileSystemEntryType(index))) {
|
|
18
|
+
await fs.writeFile(index, '/* postinstall */');
|
|
19
|
+
}
|
|
23
20
|
}
|
|
24
21
|
void postinstall();
|
package/dist/types.d.mts
CHANGED
|
@@ -16,6 +16,7 @@ export type VovkEnv = {
|
|
|
16
16
|
VOVK_PRETTIFY_CLIENT?: string;
|
|
17
17
|
VOVK_DEV_HTTPS?: string;
|
|
18
18
|
__VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
|
|
19
|
+
__VOVK_EXIT__?: 'true' | 'false';
|
|
19
20
|
};
|
|
20
21
|
export type VovkConfig = {
|
|
21
22
|
clientOutDir?: string;
|
|
@@ -30,6 +31,7 @@ export type VovkConfig = {
|
|
|
30
31
|
logLevel?: LogLevelNames;
|
|
31
32
|
prettifyClient?: boolean;
|
|
32
33
|
devHttps?: boolean;
|
|
34
|
+
experimental_clientGenerateTemplateNames?: string[];
|
|
33
35
|
templates?: {
|
|
34
36
|
service?: string;
|
|
35
37
|
controller?: string;
|
|
@@ -46,13 +48,13 @@ export type VovkModuleRenderResult = {
|
|
|
46
48
|
};
|
|
47
49
|
export interface DevOptions {
|
|
48
50
|
nextDev?: boolean;
|
|
51
|
+
exit?: boolean;
|
|
49
52
|
}
|
|
50
53
|
export interface GenerateOptions {
|
|
51
54
|
clientOutDir?: string;
|
|
52
55
|
templates?: string[];
|
|
53
56
|
prettify?: boolean;
|
|
54
57
|
fullSchema?: string | boolean;
|
|
55
|
-
noClient?: boolean;
|
|
56
58
|
}
|
|
57
59
|
export interface InitOptions {
|
|
58
60
|
yes?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vovk-cli",
|
|
3
|
-
"version": "0.0.1-draft.
|
|
3
|
+
"version": "0.0.1-draft.44",
|
|
4
4
|
"bin": {
|
|
5
5
|
"vovk": "./dist/index.mjs"
|
|
6
6
|
},
|
|
@@ -36,27 +36,27 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://vovk.dev",
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"vovk": "^3.0.0-draft.
|
|
39
|
+
"vovk": "^3.0.0-draft.50"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@inquirer/prompts": "^7.
|
|
43
|
-
"@npmcli/package-json": "^6.1.
|
|
44
|
-
"chalk": "^5.
|
|
45
|
-
"chokidar": "^4.0.
|
|
46
|
-
"commander": "^
|
|
47
|
-
"concurrently": "^9.1.
|
|
48
|
-
"dotenv": "^16.4.
|
|
42
|
+
"@inquirer/prompts": "^7.2.3",
|
|
43
|
+
"@npmcli/package-json": "^6.1.1",
|
|
44
|
+
"chalk": "^5.4.1",
|
|
45
|
+
"chokidar": "^4.0.3",
|
|
46
|
+
"commander": "^13.1.0",
|
|
47
|
+
"concurrently": "^9.1.2",
|
|
48
|
+
"dotenv": "^16.4.7",
|
|
49
49
|
"ejs": "^3.1.10",
|
|
50
50
|
"gray-matter": "^4.0.3",
|
|
51
|
-
"inflection": "^3.0.
|
|
51
|
+
"inflection": "^3.0.2",
|
|
52
52
|
"jsonc-parser": "^3.3.1",
|
|
53
53
|
"lodash": "^4.17.21",
|
|
54
54
|
"loglevel": "^1.9.2",
|
|
55
55
|
"pluralize": "^8.0.0",
|
|
56
|
-
"prettier": "^3.4.
|
|
56
|
+
"prettier": "^3.4.2",
|
|
57
57
|
"tar-stream": "^3.1.7",
|
|
58
|
-
"ts-morph": "^
|
|
59
|
-
"undici": "^7.
|
|
58
|
+
"ts-morph": "^25.0.0",
|
|
59
|
+
"undici": "^7.3.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/concat-stream": "^2.0.3",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"@types/pluralize": "^0.0.33",
|
|
66
66
|
"@types/tar-stream": "^3.1.3",
|
|
67
67
|
"concat-stream": "^2.0.0",
|
|
68
|
-
"create-next-app": "^15.
|
|
68
|
+
"create-next-app": "^15.1.6",
|
|
69
69
|
"node-pty": "^1.0.0",
|
|
70
|
-
"type-fest": "^4.
|
|
70
|
+
"type-fest": "^4.33.0"
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import type { ProjectInfo } from './getProjectInfo/index.mjs';
|
|
3
|
-
import type { Segment } from './locateSegments.mjs';
|
|
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<{
|
|
10
|
-
written: boolean;
|
|
11
|
-
path: string;
|
|
12
|
-
}>;
|
package/dist/generateClient.mjs
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import ejs from 'ejs';
|
|
4
|
-
import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
|
|
5
|
-
import prettify from './utils/prettify.mjs';
|
|
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]];
|
|
23
|
-
}
|
|
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:
|
|
30
|
-
for (let i = 0; i < segments.length; i++) {
|
|
31
|
-
const { segmentName } = segments[i];
|
|
32
|
-
const schema = segmentsSchema[segmentName];
|
|
33
|
-
if (!schema) {
|
|
34
|
-
throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
|
|
35
|
-
}
|
|
36
|
-
if (!schema.emitSchema)
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
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}`);
|
|
73
|
-
}
|
|
74
|
-
// 2. Check if any file needs rewriting
|
|
75
|
-
const anyNeedsWriting = processedTemplates.some(({ needsWriting }) => needsWriting);
|
|
76
|
-
if (!anyNeedsWriting) {
|
|
77
|
-
log.debug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
|
|
78
|
-
return { written: false, path: clientOutDirAbsolutePath };
|
|
79
|
-
}
|
|
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
|
-
}));
|
|
89
|
-
log.info(`Client generated in ${Date.now() - now}ms`);
|
|
90
|
-
return { written: true, path: clientOutDirAbsolutePath };
|
|
91
|
-
}
|