vovk-cli 0.0.1-draft.130 → 0.0.1-draft.131
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/dist/dev/index.mjs +22 -25
- package/dist/generate/ensureClient.d.mts +1 -1
- package/dist/generate/ensureClient.mjs +50 -25
- package/dist/generate/getClientTemplates.d.mts +3 -4
- package/dist/generate/getClientTemplates.mjs +12 -11
- package/dist/generate/index.d.mts +7 -7
- package/dist/generate/index.mjs +142 -75
- package/dist/getProjectInfo/getConfig.mjs +2 -0
- package/dist/getProjectInfo/index.d.mts +7 -7
- package/dist/getProjectInfo/index.mjs +35 -10
- package/dist/index.mjs +1 -4
- package/dist/locateSegments.mjs +1 -2
- package/dist/utils/pickSegmentFullSchema.d.mts +2 -0
- package/dist/utils/pickSegmentFullSchema.mjs +8 -0
- package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
- package/dist/utils/removeUnlistedDirectories.mjs +54 -0
- package/package.json +2 -2
package/dist/dev/index.mjs
CHANGED
|
@@ -19,7 +19,6 @@ import isSegmentSchemaEmpty from './isSegmentSchemaEmpty.mjs';
|
|
|
19
19
|
import writeConfigJson from './writeConfigJson.mjs';
|
|
20
20
|
export class VovkDev {
|
|
21
21
|
#projectInfo;
|
|
22
|
-
#segments = [];
|
|
23
22
|
#fullSchema = {
|
|
24
23
|
segments: {},
|
|
25
24
|
config: {},
|
|
@@ -44,10 +43,10 @@ export class VovkDev {
|
|
|
44
43
|
log.debug(`File ${filePath} has been added to segments folder`);
|
|
45
44
|
if (segmentReg.test(filePath)) {
|
|
46
45
|
const segmentName = getSegmentName(filePath);
|
|
47
|
-
this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
|
|
48
|
-
? this.#segments
|
|
46
|
+
this.#projectInfo.segments = this.#projectInfo.segments.find((s) => s.segmentName === segmentName)
|
|
47
|
+
? this.#projectInfo.segments
|
|
49
48
|
: [
|
|
50
|
-
...this.#segments,
|
|
49
|
+
...this.#projectInfo.segments,
|
|
51
50
|
{
|
|
52
51
|
routeFilePath: filePath,
|
|
53
52
|
segmentName,
|
|
@@ -55,8 +54,8 @@ export class VovkDev {
|
|
|
55
54
|
},
|
|
56
55
|
];
|
|
57
56
|
log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
|
|
58
|
-
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
59
|
-
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
57
|
+
log.debug(`Full list of segments: ${this.#projectInfo.segments.map((s) => s.segmentName).join(', ')}`);
|
|
58
|
+
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#projectInfo.segments.map((s) => s.segmentName));
|
|
60
59
|
}
|
|
61
60
|
})
|
|
62
61
|
.on('change', (filePath) => {
|
|
@@ -67,15 +66,15 @@ export class VovkDev {
|
|
|
67
66
|
})
|
|
68
67
|
.on('addDir', async (dirPath) => {
|
|
69
68
|
log.debug(`Directory ${dirPath} has been added to segments folder`);
|
|
70
|
-
this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
71
|
-
for (const { segmentName } of this.#segments) {
|
|
69
|
+
this.#projectInfo.segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
70
|
+
for (const { segmentName } of this.#projectInfo.segments) {
|
|
72
71
|
void this.#requestSchema(segmentName);
|
|
73
72
|
}
|
|
74
73
|
})
|
|
75
74
|
.on('unlinkDir', async (dirPath) => {
|
|
76
75
|
log.debug(`Directory ${dirPath} has been removed from segments folder`);
|
|
77
|
-
this.#segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
78
|
-
for (const { segmentName } of this.#segments) {
|
|
76
|
+
this.#projectInfo.segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
77
|
+
for (const { segmentName } of this.#projectInfo.segments) {
|
|
79
78
|
void this.#requestSchema(segmentName);
|
|
80
79
|
}
|
|
81
80
|
})
|
|
@@ -83,10 +82,10 @@ export class VovkDev {
|
|
|
83
82
|
log.debug(`File ${filePath} has been removed from segments folder`);
|
|
84
83
|
if (segmentReg.test(filePath)) {
|
|
85
84
|
const segmentName = getSegmentName(filePath);
|
|
86
|
-
this.#segments = this.#segments.filter((s) => s.segmentName !== segmentName);
|
|
85
|
+
this.#projectInfo.segments = this.#projectInfo.segments.filter((s) => s.segmentName !== segmentName);
|
|
87
86
|
log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} has been removed`);
|
|
88
|
-
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
89
|
-
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
87
|
+
log.debug(`Full list of segments: ${this.#projectInfo.segments.map((s) => s.segmentName).join(', ')}`);
|
|
88
|
+
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#projectInfo.segments.map((s) => s.segmentName));
|
|
90
89
|
}
|
|
91
90
|
})
|
|
92
91
|
.on('ready', () => {
|
|
@@ -119,12 +118,12 @@ export class VovkDev {
|
|
|
119
118
|
log.debug(`File ${filePath} has been removed from modules folder`);
|
|
120
119
|
})
|
|
121
120
|
.on('addDir', () => {
|
|
122
|
-
for (const { segmentName } of this.#segments) {
|
|
121
|
+
for (const { segmentName } of this.#projectInfo.segments) {
|
|
123
122
|
void this.#requestSchema(segmentName);
|
|
124
123
|
}
|
|
125
124
|
})
|
|
126
125
|
.on('unlinkDir', () => {
|
|
127
|
-
for (const { segmentName } of this.#segments) {
|
|
126
|
+
for (const { segmentName } of this.#projectInfo.segments) {
|
|
128
127
|
void this.#requestSchema(segmentName);
|
|
129
128
|
}
|
|
130
129
|
})
|
|
@@ -192,7 +191,7 @@ export class VovkDev {
|
|
|
192
191
|
if (this.#isWatching)
|
|
193
192
|
throw new Error('Already watching');
|
|
194
193
|
const { log } = this.#projectInfo;
|
|
195
|
-
log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#segments.map((s) => s.segmentName))}.`);
|
|
194
|
+
log.debug(`Starting segments and modules watcher. Detected initial segments: ${JSON.stringify(this.#projectInfo.segments.map((s) => s.segmentName))}.`);
|
|
196
195
|
// automatically watches segments and modules
|
|
197
196
|
this.#watchConfig(callback);
|
|
198
197
|
}
|
|
@@ -207,7 +206,7 @@ export class VovkDev {
|
|
|
207
206
|
const namesOfClasses = [...code.matchAll(nameOfClasReg)].map((match) => match[1]);
|
|
208
207
|
const importRegex = /import\s*{[^}]*\b(get|post|put|del|head|options)\b[^}]*}\s*from\s*['"]vovk['"]/;
|
|
209
208
|
if (importRegex.test(code) && namesOfClasses.length) {
|
|
210
|
-
const affectedSegments = this.#segments.filter((s) => {
|
|
209
|
+
const affectedSegments = this.#projectInfo.segments.filter((s) => {
|
|
211
210
|
const segmentSchema = this.#fullSchema.segments[s.segmentName];
|
|
212
211
|
if (!segmentSchema)
|
|
213
212
|
return false;
|
|
@@ -257,7 +256,7 @@ export class VovkDev {
|
|
|
257
256
|
}
|
|
258
257
|
return { isError: false };
|
|
259
258
|
}, 500);
|
|
260
|
-
#generate = debounce(() => generate({ projectInfo: this.#projectInfo,
|
|
259
|
+
#generate = debounce(() => generate({ projectInfo: this.#projectInfo, fullSchema: this.#fullSchema }).then(this.#onFirstTimeGenerate), 1000);
|
|
261
260
|
async #handleSegmentSchema(segmentName, segmentSchema) {
|
|
262
261
|
const { log, config, cwd } = this.#projectInfo;
|
|
263
262
|
if (!segmentSchema) {
|
|
@@ -266,7 +265,7 @@ export class VovkDev {
|
|
|
266
265
|
}
|
|
267
266
|
log.debug(`Handling received schema from ${formatLoggedSegmentName(segmentName)}`);
|
|
268
267
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
269
|
-
const segment = this.#segments.find((s) => s.segmentName === segmentName);
|
|
268
|
+
const segment = this.#projectInfo.segments.find((s) => s.segmentName === segmentName);
|
|
270
269
|
if (!segment) {
|
|
271
270
|
log.warn(`${formatLoggedSegmentName(segmentName)} not found`);
|
|
272
271
|
return;
|
|
@@ -288,7 +287,7 @@ export class VovkDev {
|
|
|
288
287
|
else if (segmentSchema && !isSegmentSchemaEmpty(segmentSchema)) {
|
|
289
288
|
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but "emitSchema" is false`);
|
|
290
289
|
}
|
|
291
|
-
if (this.#segments.every((s) => this.#fullSchema.segments[s.segmentName])) {
|
|
290
|
+
if (this.#projectInfo.segments.every((s) => this.#fullSchema.segments[s.segmentName])) {
|
|
292
291
|
log.debug(`All segments with "emitSchema" have schema.`);
|
|
293
292
|
this.#generate();
|
|
294
293
|
}
|
|
@@ -296,7 +295,7 @@ export class VovkDev {
|
|
|
296
295
|
async start({ exit }) {
|
|
297
296
|
const now = Date.now();
|
|
298
297
|
this.#projectInfo = await getProjectInfo();
|
|
299
|
-
const { log, config, cwd
|
|
298
|
+
const { log, config, cwd } = this.#projectInfo;
|
|
300
299
|
log.info('Starting...');
|
|
301
300
|
if (exit) {
|
|
302
301
|
this.#onFirstTimeGenerate = once(() => {
|
|
@@ -317,16 +316,14 @@ export class VovkDev {
|
|
|
317
316
|
process.on('unhandledRejection', (reason) => {
|
|
318
317
|
log.error(`Unhandled Rejection: ${String(reason)}`);
|
|
319
318
|
});
|
|
320
|
-
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
321
319
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
322
|
-
this.#
|
|
323
|
-
await ensureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
320
|
+
await ensureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#projectInfo.segments.map((s) => s.segmentName));
|
|
324
321
|
await ensureClient(this.#projectInfo);
|
|
325
322
|
const MAX_ATTEMPTS = 5;
|
|
326
323
|
const DELAY = 5000;
|
|
327
324
|
// Request schema every segment in 5 seconds in order to update schema on start
|
|
328
325
|
setTimeout(() => {
|
|
329
|
-
for (const { segmentName } of this.#segments) {
|
|
326
|
+
for (const { segmentName } of this.#projectInfo.segments) {
|
|
330
327
|
let attempts = 0;
|
|
331
328
|
void this.#requestSchema(segmentName).then(({ isError }) => {
|
|
332
329
|
if (isError) {
|
|
@@ -1,41 +1,66 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import getClientTemplates, { BuiltInTemplateName } from './getClientTemplates.mjs';
|
|
3
|
-
import uniq from 'lodash/uniq.js';
|
|
4
4
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import { ROOT_SEGMENT_SCHEMA_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
6
|
+
async function writeOnePlaceholder({ outPath, defaultText, templateName, usedTemplateNames, }) {
|
|
7
|
+
const existing = await fs.readFile(outPath, 'utf-8').catch(() => null);
|
|
8
|
+
if (!existing) {
|
|
9
|
+
let text = defaultText;
|
|
10
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
11
|
+
// a workaround that prevents compilation error when client is not yet generated but back-end imports fullSchema
|
|
12
|
+
if (Object.keys(BuiltInTemplateName).includes(templateName)) {
|
|
13
|
+
if (outPath.endsWith('.cjs')) {
|
|
14
|
+
text += '\nmodule.exports.fullSchema = {};';
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
text += '\nexport const fullSchema = {};';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
await fs.writeFile(outPath, outPath.endsWith('.py') ? text.replace(/\/\//g, '#') : text);
|
|
21
|
+
usedTemplateNames.add(templateName);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export default async function ensureClient({ config, cwd, log, segments }) {
|
|
7
25
|
const now = Date.now();
|
|
8
26
|
const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({
|
|
9
27
|
config,
|
|
10
28
|
cwd,
|
|
11
29
|
generateFrom: config.generateFrom,
|
|
12
30
|
});
|
|
13
|
-
|
|
31
|
+
const usedTemplateNames = new Set();
|
|
14
32
|
const defaultText = `// auto-generated ${new Date().toISOString()}
|
|
15
33
|
// This is a temporary placeholder to avoid compilation errors if client is imported before it's generated.
|
|
16
34
|
// If you still see this text, the client is not generated yet because of an unknown problem.
|
|
17
35
|
// Feel free to report an issue at https://github.com/finom/vovk/issues`;
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
if (
|
|
22
|
-
await
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else {
|
|
29
|
-
text += '\nexport const fullSchema = {};';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
await fs.writeFile(outPath, outPath.endsWith('.py') ? text.replace(/\/\//g, '#') : text);
|
|
33
|
-
usedTemplateNames.push(templateName);
|
|
36
|
+
await Promise.all(templateFiles.map(async (clientTemplate) => {
|
|
37
|
+
const { templatePath, templateName, outDir } = clientTemplate;
|
|
38
|
+
const outPath = path.join(outDir, path.basename(templatePath).replace('.ejs', ''));
|
|
39
|
+
if (config.emitFullClient) {
|
|
40
|
+
await writeOnePlaceholder({
|
|
41
|
+
outPath,
|
|
42
|
+
defaultText,
|
|
43
|
+
templateName,
|
|
44
|
+
usedTemplateNames,
|
|
45
|
+
});
|
|
34
46
|
}
|
|
47
|
+
if (config.emitSegmentClient) {
|
|
48
|
+
// Generate client files for each segment
|
|
49
|
+
await Promise.all(segments.map(async ({ segmentName }) => {
|
|
50
|
+
const outPath = path.join(outDir, segmentName || ROOT_SEGMENT_SCHEMA_NAME, path.basename(templatePath).replace('.ejs', ''));
|
|
51
|
+
return writeOnePlaceholder({
|
|
52
|
+
outPath,
|
|
53
|
+
defaultText,
|
|
54
|
+
templateName,
|
|
55
|
+
usedTemplateNames,
|
|
56
|
+
});
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
}));
|
|
60
|
+
if (usedTemplateNames.size) {
|
|
61
|
+
log.info(`Placeholder client files from template${usedTemplateNames.size !== 1 ? 's' : ''} ${chalkHighlightThing(Array.from(usedTemplateNames)
|
|
62
|
+
.map((s) => `"${s}"`)
|
|
63
|
+
.join(', '))} are generated at ${clientOutDirAbsolutePath} in ${Date.now() - now}ms`);
|
|
35
64
|
}
|
|
36
|
-
|
|
37
|
-
if (usedTemplateNames.length) {
|
|
38
|
-
log.info(`Placeholder client files from template${usedTemplateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(usedTemplateNames.map((s) => `"${s}"`).join(', '))} are generated at ${clientOutDirAbsolutePath} in ${Date.now() - now}ms`);
|
|
39
|
-
}
|
|
40
|
-
return { written: !!usedTemplateNames.length };
|
|
65
|
+
return { written: !!usedTemplateNames.size };
|
|
41
66
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { VovkStrictConfig } from 'vovk';
|
|
2
2
|
export declare const DEFAULT_FULL_SCHEMA_FILE_NAME = "full-schema.json";
|
|
3
|
-
interface ClientTemplate {
|
|
3
|
+
export interface ClientTemplate {
|
|
4
4
|
templateName: string;
|
|
5
5
|
templatePath: string;
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
outDir: string;
|
|
7
|
+
fullSchemaJSONFileName: string | null;
|
|
8
8
|
origin?: string | null;
|
|
9
9
|
}
|
|
10
10
|
export declare enum BuiltInTemplateName {
|
|
@@ -21,4 +21,3 @@ export default function getClientTemplates({ config, cwd, generateFrom, }: {
|
|
|
21
21
|
clientOutDirAbsolutePath: string;
|
|
22
22
|
templateFiles: ClientTemplate[];
|
|
23
23
|
}>;
|
|
24
|
-
export {};
|
|
@@ -17,28 +17,28 @@ export default async function getClientTemplates({ config, cwd, generateFrom = [
|
|
|
17
17
|
templateName: BuiltInTemplateName.ts,
|
|
18
18
|
templatePath: path.resolve(templatesDir, 'ts/*'),
|
|
19
19
|
outDir: clientOutDirAbsolutePath,
|
|
20
|
-
|
|
20
|
+
fullSchemaJSON: false,
|
|
21
21
|
origin: null,
|
|
22
22
|
},
|
|
23
23
|
main: {
|
|
24
24
|
templateName: BuiltInTemplateName.main,
|
|
25
25
|
templatePath: path.resolve(templatesDir, 'main/*'),
|
|
26
26
|
outDir: clientOutDirAbsolutePath,
|
|
27
|
-
|
|
27
|
+
fullSchemaJSON: false,
|
|
28
28
|
origin: null,
|
|
29
29
|
},
|
|
30
30
|
module: {
|
|
31
31
|
templateName: BuiltInTemplateName.module,
|
|
32
32
|
templatePath: path.resolve(templatesDir, 'module/*'),
|
|
33
33
|
outDir: clientOutDirAbsolutePath,
|
|
34
|
-
|
|
34
|
+
fullSchemaJSON: false,
|
|
35
35
|
origin: null,
|
|
36
36
|
},
|
|
37
37
|
fullSchema: {
|
|
38
38
|
templateName: BuiltInTemplateName.fullSchema,
|
|
39
39
|
templatePath: path.resolve(templatesDir, 'fullSchema/*'),
|
|
40
40
|
outDir: clientOutDirAbsolutePath,
|
|
41
|
-
|
|
41
|
+
fullSchemaJSON: false,
|
|
42
42
|
origin: null,
|
|
43
43
|
},
|
|
44
44
|
};
|
|
@@ -51,7 +51,7 @@ export default async function getClientTemplates({ config, cwd, generateFrom = [
|
|
|
51
51
|
templateName: template,
|
|
52
52
|
templatePath: resolveAbsoluteModulePath(template, cwd),
|
|
53
53
|
outDir: clientOutDirAbsolutePath,
|
|
54
|
-
|
|
54
|
+
fullSchemaJSON: false,
|
|
55
55
|
origin: null,
|
|
56
56
|
};
|
|
57
57
|
}
|
|
@@ -59,7 +59,7 @@ export default async function getClientTemplates({ config, cwd, generateFrom = [
|
|
|
59
59
|
templateName: template.templateName ?? template.templatePath,
|
|
60
60
|
templatePath: resolveAbsoluteModulePath(template.templatePath, cwd),
|
|
61
61
|
outDir: template.outDir ? path.resolve(cwd, template.outDir) : clientOutDirAbsolutePath,
|
|
62
|
-
|
|
62
|
+
fullSchemaJSON: template.fullSchemaJSON ?? false,
|
|
63
63
|
origin: template.origin ?? null,
|
|
64
64
|
};
|
|
65
65
|
});
|
|
@@ -70,14 +70,15 @@ export default async function getClientTemplates({ config, cwd, generateFrom = [
|
|
|
70
70
|
for (const generateFromItem of generateFromStrict) {
|
|
71
71
|
const files = await glob(generateFromItem.templatePath);
|
|
72
72
|
for await (const templatePath of files) {
|
|
73
|
-
const fullSchemaOutAbsolutePath = generateFromItem.fullSchema
|
|
74
|
-
? path.resolve(generateFromItem.outDir, generateFromItem.fullSchema === 'string' ? generateFromItem.fullSchema : DEFAULT_FULL_SCHEMA_FILE_NAME)
|
|
75
|
-
: null;
|
|
76
73
|
templateFiles.push({
|
|
77
74
|
templateName: generateFromItem.templateName,
|
|
78
75
|
templatePath,
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
outDir: generateFromItem.outDir,
|
|
77
|
+
fullSchemaJSONFileName: generateFromItem.fullSchemaJSON
|
|
78
|
+
? generateFromItem.fullSchemaJSON === 'string'
|
|
79
|
+
? generateFromItem.fullSchemaJSON
|
|
80
|
+
: DEFAULT_FULL_SCHEMA_FILE_NAME
|
|
81
|
+
: null,
|
|
81
82
|
origin: generateFromItem.origin,
|
|
82
83
|
});
|
|
83
84
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { VovkFullSchema } from 'vovk';
|
|
2
2
|
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
-
import type { Segment } from '../locateSegments.mjs';
|
|
4
3
|
import type { GenerateOptions } from '../types.mjs';
|
|
5
|
-
export
|
|
4
|
+
export type ClientImports = {
|
|
5
|
+
fetcher: string;
|
|
6
|
+
validateOnClient: string | null;
|
|
7
|
+
createRPC: string;
|
|
8
|
+
};
|
|
9
|
+
export default function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient, fullSchema, emitFullSchema, }: {
|
|
6
10
|
projectInfo: ProjectInfo;
|
|
7
|
-
segments: Segment[];
|
|
8
11
|
forceNothingWrittenLog?: boolean;
|
|
9
12
|
fullSchema: VovkFullSchema;
|
|
10
|
-
} & Pick<GenerateOptions, 'templates' | 'prettify' | 'emitFullSchema'>): Promise<
|
|
11
|
-
written: boolean;
|
|
12
|
-
path: string;
|
|
13
|
-
}>;
|
|
13
|
+
} & Pick<GenerateOptions, 'templates' | 'prettify' | 'emitFullSchema'>): Promise<void>;
|
package/dist/generate/index.mjs
CHANGED
|
@@ -2,114 +2,181 @@ import path from 'node:path';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import ejs from 'ejs';
|
|
4
4
|
import matter from 'gray-matter';
|
|
5
|
-
import
|
|
5
|
+
import _ from 'lodash';
|
|
6
6
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
7
7
|
import prettify from '../utils/prettify.mjs';
|
|
8
8
|
import getClientTemplates, { DEFAULT_FULL_SCHEMA_FILE_NAME } from './getClientTemplates.mjs';
|
|
9
9
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
10
|
-
import _ from 'lodash';
|
|
11
10
|
import { ROOT_SEGMENT_SCHEMA_NAME, SEGMENTS_SCHEMA_DIR_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import pickSegmentFullSchema from '../utils/pickSegmentFullSchema.mjs';
|
|
12
|
+
import removeUnlistedDirectories from '../utils/removeUnlistedDirectories.mjs';
|
|
13
|
+
async function writeOneClientFile({ projectInfo, clientTemplate, fullSchema, prettifyClient, segmentName, imports, templateContent, matterResult: { data, content }, }) {
|
|
14
|
+
const { config, apiRoot, segments } = projectInfo;
|
|
15
|
+
const { templatePath, outDir, origin } = clientTemplate;
|
|
16
|
+
const outPath = path.join(outDir, typeof segmentName === 'string' ? segmentName || ROOT_SEGMENT_SCHEMA_NAME : '', path.basename(templatePath).replace('.ejs', ''));
|
|
17
|
+
const schemaOutDir = path.relative(config.clientOutDir, config.schemaOutDir).replace(/\\/g, '/'); // windows fix
|
|
18
|
+
// Data for the EJS templates:
|
|
19
|
+
const t = {
|
|
20
|
+
_, // lodash
|
|
21
|
+
ROOT_SEGMENT_SCHEMA_NAME,
|
|
22
|
+
SEGMENTS_SCHEMA_DIR_NAME,
|
|
23
|
+
apiRoot: origin ? `${origin}/${config.rootEntry}` : apiRoot,
|
|
24
|
+
imports,
|
|
25
|
+
fullSchema,
|
|
26
|
+
schemaOutDir,
|
|
27
|
+
segmentMeta: Object.fromEntries(segments.map(({ segmentName, routeFilePath, segmentImportPath }) => [
|
|
28
|
+
segmentName,
|
|
29
|
+
{
|
|
30
|
+
routeFilePath,
|
|
31
|
+
segmentImportPath: typeof segmentName === 'string'
|
|
32
|
+
? `${_.times((segmentName.match(/\//g)?.length ?? 0) + 1)
|
|
33
|
+
.map(() => '..')
|
|
34
|
+
.join('/')}/${segmentImportPath}`
|
|
35
|
+
: segmentImportPath,
|
|
36
|
+
},
|
|
37
|
+
])),
|
|
38
|
+
};
|
|
39
|
+
if (data.imports instanceof Array) {
|
|
40
|
+
for (const imp of data.imports) {
|
|
41
|
+
t.imports = {
|
|
42
|
+
...t.imports,
|
|
43
|
+
[imp]: await import(imp),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Render the template
|
|
48
|
+
let rendered = templatePath.endsWith('.ejs')
|
|
49
|
+
? ejs.render(content, { t }, {
|
|
50
|
+
filename: templatePath,
|
|
51
|
+
})
|
|
52
|
+
: templateContent;
|
|
53
|
+
// Optionally prettify
|
|
54
|
+
if (prettifyClient || config.prettifyClient) {
|
|
55
|
+
rendered = await prettify(rendered, outPath);
|
|
56
|
+
}
|
|
57
|
+
// Read existing file content to compare
|
|
58
|
+
const existingContent = await fs.readFile(outPath, 'utf-8').catch(() => '');
|
|
59
|
+
// Determine if we need to rewrite the file, ignore 1st line
|
|
60
|
+
const needsWriting = existingContent.trim().split('\n').slice(1).join('\n') !== rendered.trim().split('\n').slice(1).join('\n');
|
|
61
|
+
if (needsWriting) {
|
|
62
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
63
|
+
await fs.writeFile(outPath, rendered);
|
|
64
|
+
}
|
|
65
|
+
return { written: needsWriting };
|
|
66
|
+
}
|
|
67
|
+
export default async function generate({ projectInfo, forceNothingWrittenLog, templates, prettify: prettifyClient = false, fullSchema, emitFullSchema, }) {
|
|
68
|
+
const now = Date.now();
|
|
14
69
|
const generateFrom = templates ?? projectInfo.config.generateFrom;
|
|
15
70
|
const noClient = templates?.[0] === 'none';
|
|
16
|
-
const { config, cwd, log, clientImports,
|
|
71
|
+
const { config, cwd, log, clientImports, segments } = projectInfo;
|
|
17
72
|
const { clientOutDirAbsolutePath, templateFiles } = await getClientTemplates({ config, cwd, generateFrom });
|
|
18
73
|
// Ensure that each segment has a matching schema if it needs to be emitted:
|
|
19
74
|
for (let i = 0; i < segments.length; i++) {
|
|
20
75
|
const { segmentName } = segments[i];
|
|
21
|
-
const schema =
|
|
76
|
+
const schema = fullSchema.segments[segmentName];
|
|
22
77
|
if (!schema) {
|
|
23
78
|
throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
|
|
24
79
|
}
|
|
25
80
|
if (!schema.emitSchema)
|
|
26
81
|
continue;
|
|
27
82
|
}
|
|
28
|
-
|
|
29
|
-
const
|
|
83
|
+
let written = false;
|
|
84
|
+
const fullSchemaOutAbsolutePaths = new Map();
|
|
85
|
+
const usedTemplateNames = new Set();
|
|
30
86
|
// Process each template in parallel
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/*const parsed = matter((await ejs.render(codeTemplate, { t }, { async: true, filename: templateFileName })).trim());
|
|
35
|
-
const { dir, fileName, sourceName, compiledName } = parsed.data as VovkModuleRenderResult;
|
|
36
|
-
const code = empty ? (sourceName ? `export default class ${sourceName} {}` : '') : parsed.content;*/
|
|
87
|
+
await Promise.all(templateFiles.map(async (clientTemplate) => {
|
|
88
|
+
const { templatePath, templateName, outDir, fullSchemaJSONFileName } = clientTemplate;
|
|
89
|
+
if (!noClient) {
|
|
37
90
|
// Read the EJS template
|
|
38
91
|
const templateContent = await fs.readFile(templatePath, 'utf-8');
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
t.imports = {
|
|
54
|
-
...t.imports,
|
|
55
|
-
[imp]: await import(imp),
|
|
56
|
-
};
|
|
92
|
+
const matterResult = matter(templateContent);
|
|
93
|
+
if (config.emitFullClient) {
|
|
94
|
+
const { written: isWritten } = await writeOneClientFile({
|
|
95
|
+
projectInfo,
|
|
96
|
+
clientTemplate,
|
|
97
|
+
fullSchema,
|
|
98
|
+
prettifyClient,
|
|
99
|
+
segmentName: null,
|
|
100
|
+
imports: clientImports.fullClient,
|
|
101
|
+
templateContent,
|
|
102
|
+
matterResult,
|
|
103
|
+
});
|
|
104
|
+
if (isWritten) {
|
|
105
|
+
usedTemplateNames.add(templateName);
|
|
57
106
|
}
|
|
107
|
+
written ||= isWritten;
|
|
58
108
|
}
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
109
|
+
// TODO Remove files if emitFullClient is false ???
|
|
110
|
+
if (config.emitSegmentClient) {
|
|
111
|
+
// Generate client files for each segment
|
|
112
|
+
await Promise.all(segments.map(async ({ segmentName }) => {
|
|
113
|
+
const segmentFullSchema = {
|
|
114
|
+
config: fullSchema.config,
|
|
115
|
+
segments: { [segmentName]: fullSchema.segments[segmentName] },
|
|
116
|
+
};
|
|
117
|
+
const { written: isWritten } = await writeOneClientFile({
|
|
118
|
+
projectInfo,
|
|
119
|
+
clientTemplate,
|
|
120
|
+
fullSchema: segmentFullSchema,
|
|
121
|
+
prettifyClient,
|
|
122
|
+
segmentName,
|
|
123
|
+
imports: clientImports.schemaClient[segmentName],
|
|
124
|
+
templateContent,
|
|
125
|
+
matterResult,
|
|
126
|
+
});
|
|
127
|
+
if (isWritten) {
|
|
128
|
+
usedTemplateNames.add(templateName);
|
|
129
|
+
}
|
|
130
|
+
written ||= isWritten;
|
|
131
|
+
}));
|
|
132
|
+
await removeUnlistedDirectories(outDir, segments.map(({ segmentName }) => segmentName || ROOT_SEGMENT_SCHEMA_NAME));
|
|
68
133
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
134
|
+
else {
|
|
135
|
+
await removeUnlistedDirectories(outDir, []);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (config.emitFullClient) {
|
|
139
|
+
const fullSchemaOutAbsolutePath = fullSchemaJSONFileName ? path.resolve(outDir, fullSchemaJSONFileName) : null;
|
|
140
|
+
if (fullSchemaOutAbsolutePath) {
|
|
141
|
+
fullSchemaOutAbsolutePaths.set(null, fullSchemaOutAbsolutePath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (config.emitSegmentClient) {
|
|
145
|
+
segments.forEach(({ segmentName }) => {
|
|
146
|
+
const fullSchemaOutAbsolutePath = fullSchemaJSONFileName
|
|
147
|
+
? path.resolve(outDir, segmentName || ROOT_SEGMENT_SCHEMA_NAME, fullSchemaJSONFileName)
|
|
148
|
+
: null;
|
|
149
|
+
if (fullSchemaOutAbsolutePath) {
|
|
150
|
+
fullSchemaOutAbsolutePaths.set(segmentName, fullSchemaOutAbsolutePath);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}));
|
|
84
155
|
if (emitFullSchema) {
|
|
85
156
|
const fullSchemaOutAbsolutePath = emitFullSchema
|
|
86
157
|
? path.resolve(clientOutDirAbsolutePath, emitFullSchema === 'string' ? emitFullSchema : DEFAULT_FULL_SCHEMA_FILE_NAME)
|
|
87
158
|
: null;
|
|
88
159
|
if (fullSchemaOutAbsolutePath) {
|
|
89
|
-
|
|
160
|
+
fullSchemaOutAbsolutePaths.set(null, fullSchemaOutAbsolutePath);
|
|
90
161
|
}
|
|
91
162
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
fs.mkdir(path.dirname(
|
|
96
|
-
|
|
163
|
+
if (fullSchemaOutAbsolutePaths.size) {
|
|
164
|
+
await Promise.all(Array.from(fullSchemaOutAbsolutePaths.entries()).map(async ([segmentName, outPath]) => {
|
|
165
|
+
const schema = segmentName ? pickSegmentFullSchema(fullSchema, segmentName) : fullSchema;
|
|
166
|
+
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
167
|
+
await fs.writeFile(outPath, JSON.stringify(schema, null, 2));
|
|
97
168
|
}));
|
|
98
|
-
log.info(`Full schema has been written to ${
|
|
169
|
+
log.info(`Full schema has been written to ${Array.from(fullSchemaOutAbsolutePaths)
|
|
170
|
+
.map((s) => `"${s}"`)
|
|
171
|
+
.join(', ')}`);
|
|
172
|
+
}
|
|
173
|
+
if (written) {
|
|
174
|
+
log.info(`Client generated from template${usedTemplateNames.size !== 1 ? 's' : ''} ${chalkHighlightThing(Array.from(usedTemplateNames)
|
|
175
|
+
.map((s) => `"${s}"`)
|
|
176
|
+
.join(', '))} in ${Date.now() - now}ms`);
|
|
99
177
|
}
|
|
100
|
-
|
|
178
|
+
else {
|
|
101
179
|
const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
|
|
102
180
|
logOrDebug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
|
|
103
|
-
return { written: false, path: clientOutDirAbsolutePath };
|
|
104
181
|
}
|
|
105
|
-
// Write updated files where needed
|
|
106
|
-
await Promise.all(processedTemplates.map(async ({ outPath, rendered, needsWriting }) => {
|
|
107
|
-
if (needsWriting) {
|
|
108
|
-
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
|
109
|
-
return fs.writeFile(outPath, rendered);
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}));
|
|
113
|
-
log.info(`Client generated from template${usedTemplateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(usedTemplateNames.map((s) => `"${s}"`).join(', '))} in ${Date.now() - now}ms`);
|
|
114
|
-
return { written: true, path: clientOutDirAbsolutePath };
|
|
115
182
|
}
|
|
@@ -11,6 +11,8 @@ export default async function getConfig({ clientOutDir, configPath, cwd, }) {
|
|
|
11
11
|
const defaultClientTemplates = ['module', 'main'];
|
|
12
12
|
const config = {
|
|
13
13
|
emitConfig: [],
|
|
14
|
+
emitFullClient: 'VOVK_EMIT_FULL_CLIENT' in env ? !!env.VOVK_EMIT_FULL_CLIENT : (conf.emitFullClient ?? true),
|
|
15
|
+
emitSegmentClient: 'VOVK_EMIT_SEGMENT_CLIENT' in env ? !!env.VOVK_EMIT_SEGMENT_CLIENT : (conf.emitSegmentClient ?? false),
|
|
14
16
|
modulesDir: env.VOVK_MODULES_DIR ?? conf.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/'),
|
|
15
17
|
imports: {
|
|
16
18
|
fetcher: typeof fetcherImport === 'string' ? [fetcherImport] : fetcherImport,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ClientImports } from '../generate/index.mjs';
|
|
1
2
|
export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
|
|
2
3
|
export default function getProjectInfo({ port: givenPort, clientOutDir, configPath, cwd, }?: {
|
|
3
4
|
port?: number;
|
|
@@ -12,14 +13,12 @@ export default function getProjectInfo({ port: givenPort, clientOutDir, configPa
|
|
|
12
13
|
srcRoot: string;
|
|
13
14
|
config: import("vovk").VovkStrictConfig;
|
|
14
15
|
clientImports: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
validateOnClient: string | null;
|
|
18
|
-
module: {
|
|
19
|
-
fetcher: string;
|
|
20
|
-
createRPC: string;
|
|
21
|
-
validateOnClient: string | null;
|
|
16
|
+
fullClient: ClientImports & {
|
|
17
|
+
module: ClientImports;
|
|
22
18
|
};
|
|
19
|
+
schemaClient: Record<string, ClientImports & {
|
|
20
|
+
module: ClientImports;
|
|
21
|
+
}>;
|
|
23
22
|
};
|
|
24
23
|
log: {
|
|
25
24
|
info: (msg: string) => void;
|
|
@@ -28,4 +27,5 @@ export default function getProjectInfo({ port: givenPort, clientOutDir, configPa
|
|
|
28
27
|
debug: (msg: string) => void;
|
|
29
28
|
raw: import("loglevel").RootLogger;
|
|
30
29
|
};
|
|
30
|
+
segments: import("../locateSegments.mjs").Segment[];
|
|
31
31
|
}>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import getConfig from './getConfig.mjs';
|
|
3
3
|
import getLogger from '../utils/getLogger.mjs';
|
|
4
|
+
import locateSegments from '../locateSegments.mjs';
|
|
5
|
+
import { ROOT_SEGMENT_SCHEMA_NAME } from '../dev/writeOneSegmentSchemaFile.mjs';
|
|
4
6
|
export default async function getProjectInfo({ port: givenPort, clientOutDir, configPath, cwd = process.cwd(), } = {}) {
|
|
5
7
|
const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
|
|
6
8
|
// Make PORT available to the config file at getConfig
|
|
@@ -19,18 +21,40 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, co
|
|
|
19
21
|
if (!userConfig && configAbsolutePaths.length > 0) {
|
|
20
22
|
log.error(`Error reading config file at ${configAbsolutePaths[0]}: ${error?.message ?? 'Unknown Error'}`);
|
|
21
23
|
}
|
|
22
|
-
const getImportPath = (p) =>
|
|
24
|
+
const getImportPath = (p, s = '') => p.startsWith('.') ? path.relative(path.join(config.clientOutDir, s), p) : p;
|
|
25
|
+
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
26
|
+
const segments = await locateSegments({ dir: apiDirAbsolutePath, config });
|
|
27
|
+
// TODO Refactor
|
|
23
28
|
const clientImports = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
fullClient: {
|
|
30
|
+
fetcher: getImportPath(config.imports.fetcher[0]),
|
|
31
|
+
createRPC: getImportPath(config.imports.createRPC[0]),
|
|
32
|
+
validateOnClient: config.imports.validateOnClient ? getImportPath(config.imports.validateOnClient[0]) : null,
|
|
33
|
+
module: {
|
|
34
|
+
fetcher: getImportPath(config.imports.fetcher[1] ?? config.imports.fetcher[0]),
|
|
35
|
+
createRPC: getImportPath(config.imports.createRPC[1] ?? config.imports.createRPC[0]),
|
|
36
|
+
validateOnClient: config.imports.validateOnClient
|
|
37
|
+
? getImportPath(config.imports.validateOnClient[1] ?? config.imports.validateOnClient[0])
|
|
38
|
+
: null,
|
|
39
|
+
},
|
|
33
40
|
},
|
|
41
|
+
schemaClient: Object.fromEntries(segments.map((segment) => [
|
|
42
|
+
segment.segmentName,
|
|
43
|
+
{
|
|
44
|
+
fetcher: getImportPath(config.imports.fetcher[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
|
|
45
|
+
createRPC: getImportPath(config.imports.createRPC[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
|
|
46
|
+
validateOnClient: config.imports.validateOnClient
|
|
47
|
+
? getImportPath(config.imports.validateOnClient[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME)
|
|
48
|
+
: null,
|
|
49
|
+
module: {
|
|
50
|
+
fetcher: getImportPath(config.imports.fetcher[1] ?? config.imports.fetcher[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
|
|
51
|
+
createRPC: getImportPath(config.imports.createRPC[1] ?? config.imports.createRPC[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME),
|
|
52
|
+
validateOnClient: config.imports.validateOnClient
|
|
53
|
+
? getImportPath(config.imports.validateOnClient[1] ?? config.imports.validateOnClient[0], segment.segmentName || ROOT_SEGMENT_SCHEMA_NAME)
|
|
54
|
+
: null,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
])),
|
|
34
58
|
};
|
|
35
59
|
return {
|
|
36
60
|
cwd,
|
|
@@ -41,5 +65,6 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, co
|
|
|
41
65
|
config,
|
|
42
66
|
clientImports,
|
|
43
67
|
log,
|
|
68
|
+
segments,
|
|
44
69
|
};
|
|
45
70
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import concurrently from 'concurrently';
|
|
|
7
7
|
import getAvailablePort from './utils/getAvailablePort.mjs';
|
|
8
8
|
import getProjectInfo from './getProjectInfo/index.mjs';
|
|
9
9
|
import generate from './generate/index.mjs';
|
|
10
|
-
import locateSegments from './locateSegments.mjs';
|
|
11
10
|
import { VovkDev } from './dev/index.mjs';
|
|
12
11
|
import newComponents from './new/index.mjs';
|
|
13
12
|
import initProgram from './initProgram.mjs';
|
|
@@ -81,13 +80,11 @@ program
|
|
|
81
80
|
.action(async (options) => {
|
|
82
81
|
const { clientOutDir, templates, prettify, emitFullSchema, config: configPath } = options;
|
|
83
82
|
const projectInfo = await getProjectInfo({ clientOutDir, configPath });
|
|
84
|
-
const { cwd, config
|
|
85
|
-
const segments = await locateSegments({ dir: apiDir, config });
|
|
83
|
+
const { cwd, config } = projectInfo;
|
|
86
84
|
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
87
85
|
const fullSchema = await getFullSchemaFromJSON(schemaOutAbsolutePath, projectInfo);
|
|
88
86
|
await generate({
|
|
89
87
|
projectInfo,
|
|
90
|
-
segments,
|
|
91
88
|
fullSchema,
|
|
92
89
|
templates,
|
|
93
90
|
prettify,
|
package/dist/locateSegments.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
|
|
4
|
-
// config: null is used for testing
|
|
5
4
|
export default async function locateSegments({ dir, rootDir, config, }) {
|
|
6
5
|
let results = [];
|
|
7
6
|
rootDir = rootDir ?? dir;
|
|
@@ -19,7 +18,7 @@ export default async function locateSegments({ dir, rootDir, config, }) {
|
|
|
19
18
|
if (await getFileSystemEntryType(routeFilePath)) {
|
|
20
19
|
// Calculate the basePath relative to the root directory
|
|
21
20
|
const segmentName = path.relative(rootDir, dir).replace(/\\/g, '/'); // windows fix
|
|
22
|
-
const segmentImportPath = path.relative(config?.clientOutDir ?? '.
|
|
21
|
+
const segmentImportPath = path.relative(config?.clientOutDir ?? '.__error', routeFilePath);
|
|
23
22
|
results.push({ routeFilePath, segmentName, segmentImportPath });
|
|
24
23
|
}
|
|
25
24
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes all directories in a folder that aren't in the provided allowlist
|
|
3
|
+
* Supports nested directory paths like 'foo/bar/baz'
|
|
4
|
+
*
|
|
5
|
+
* @param folderPath - The path to the folder to process
|
|
6
|
+
* @param allowedDirs - Array of relative directory paths to keep
|
|
7
|
+
* @returns Promise that resolves when all operations are complete
|
|
8
|
+
*/
|
|
9
|
+
declare function removeUnlistedDirectories(folderPath: string, allowedDirs: string[]): Promise<void>;
|
|
10
|
+
export default removeUnlistedDirectories;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Removes all directories in a folder that aren't in the provided allowlist
|
|
5
|
+
* Supports nested directory paths like 'foo/bar/baz'
|
|
6
|
+
*
|
|
7
|
+
* @param folderPath - The path to the folder to process
|
|
8
|
+
* @param allowedDirs - Array of relative directory paths to keep
|
|
9
|
+
* @returns Promise that resolves when all operations are complete
|
|
10
|
+
*/
|
|
11
|
+
async function removeUnlistedDirectories(folderPath, allowedDirs) {
|
|
12
|
+
// Normalize all allowed paths to use the system-specific separator
|
|
13
|
+
const normalizedAllowedDirs = allowedDirs.map((dir) => dir.split('/').join(path.sep));
|
|
14
|
+
// Process the directory tree recursively
|
|
15
|
+
await processDirectory(folderPath, '', normalizedAllowedDirs);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recursively processes directories to determine which should be kept or removed
|
|
19
|
+
*
|
|
20
|
+
* @param basePath - The absolute base path being processed
|
|
21
|
+
* @param relativePath - The current relative path from the base
|
|
22
|
+
* @param allowedDirs - Normalized list of allowed directory paths
|
|
23
|
+
*/
|
|
24
|
+
async function processDirectory(basePath, relativePath, allowedDirs) {
|
|
25
|
+
const currentDirPath = path.join(basePath, relativePath);
|
|
26
|
+
// Read all entries in the current directory
|
|
27
|
+
const entries = await fs.readdir(currentDirPath, { withFileTypes: true });
|
|
28
|
+
// Process only directories
|
|
29
|
+
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
30
|
+
// Check each directory
|
|
31
|
+
for (const dir of dirEntries) {
|
|
32
|
+
// Calculate the new relative path
|
|
33
|
+
const newRelativePath = relativePath ? path.join(relativePath, dir.name) : dir.name;
|
|
34
|
+
// Check if this directory or any of its subdirectories should be kept
|
|
35
|
+
const shouldKeep = allowedDirs.some((allowedDir) => {
|
|
36
|
+
// Direct match
|
|
37
|
+
if (allowedDir === newRelativePath)
|
|
38
|
+
return true;
|
|
39
|
+
// Check if it's a parent path of an allowed directory
|
|
40
|
+
// e.g. "foo" is a parent of "foo/bar/baz"
|
|
41
|
+
return allowedDir.startsWith(newRelativePath + path.sep);
|
|
42
|
+
});
|
|
43
|
+
if (shouldKeep) {
|
|
44
|
+
// Recursively process this directory's contents
|
|
45
|
+
await processDirectory(basePath, newRelativePath, allowedDirs);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Remove this directory since it's not in the allowed list
|
|
49
|
+
const fullPath = path.join(basePath, newRelativePath);
|
|
50
|
+
await fs.rm(fullPath, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export default removeUnlistedDirectories;
|
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.131",
|
|
4
4
|
"bin": {
|
|
5
5
|
"vovk": "./dist/index.mjs"
|
|
6
6
|
},
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://vovk.dev",
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"vovk": "^3.0.0-draft.
|
|
38
|
+
"vovk": "^3.0.0-draft.110"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
41
|
"vovk-python-client": "*"
|