vovk-cli 0.0.1-draft.26 → 0.0.1-draft.261
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +29 -1
- package/client-templates/cjs/index.cjs.ejs +14 -0
- package/client-templates/cjs/index.d.cts.ejs +22 -0
- package/client-templates/mixins/mixins.d.ts.ejs +64 -0
- package/client-templates/mixins/mixins.json.ejs +1 -0
- package/client-templates/mjs/index.d.mts.ejs +22 -0
- package/client-templates/mjs/index.mjs.ejs +20 -0
- package/client-templates/packageJson/package.json.ejs +1 -0
- package/client-templates/readme/README.md.ejs +35 -0
- package/client-templates/schemaCjs/schema.cjs.ejs +26 -0
- package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
- package/client-templates/schemaJson/schema.json.ejs +1 -0
- package/client-templates/schemaTs/schema.ts.ejs +35 -0
- package/client-templates/ts/index.ts.ejs +30 -0
- package/dist/bundle/index.d.mts +8 -0
- package/dist/bundle/index.mjs +86 -0
- package/dist/dev/diffSegmentSchema.d.mts +36 -0
- package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +4 -12
- package/dist/dev/ensureSchemaFiles.d.mts +3 -0
- package/dist/dev/ensureSchemaFiles.mjs +15 -31
- package/dist/dev/index.d.mts +5 -2
- package/dist/dev/index.mjs +173 -78
- package/dist/dev/logDiffResult.d.mts +1 -1
- package/dist/dev/logDiffResult.mjs +6 -43
- package/dist/dev/writeMetaJson.d.mts +2 -0
- package/dist/dev/writeMetaJson.mjs +17 -0
- package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
- package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
- package/dist/generate/ensureClient.d.mts +3 -0
- package/dist/generate/ensureClient.mjs +32 -0
- package/dist/generate/generate.d.mts +12 -0
- package/dist/generate/generate.mjs +266 -0
- package/dist/generate/getClientTemplateFiles.d.mts +22 -0
- package/dist/generate/getClientTemplateFiles.mjs +89 -0
- package/dist/generate/getProjectFullSchema.d.mts +3 -0
- package/dist/generate/getProjectFullSchema.mjs +64 -0
- package/dist/generate/getTemplateClientImports.d.mts +18 -0
- package/dist/generate/getTemplateClientImports.mjs +38 -0
- package/dist/generate/index.d.mts +33 -0
- package/dist/generate/index.mjs +185 -0
- package/dist/generate/mergePackages.d.mts +9 -0
- package/dist/generate/mergePackages.mjs +78 -0
- package/dist/generate/writeOneClientFile.d.mts +33 -0
- package/dist/generate/writeOneClientFile.mjs +101 -0
- package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
- package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
- package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +16 -0
- package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +98 -0
- package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
- package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +6 -4
- package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -5
- package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -1
- package/dist/getProjectInfo/getConfig/index.d.mts +102 -0
- package/dist/getProjectInfo/getConfig/index.mjs +90 -0
- package/dist/getProjectInfo/index.d.mts +7 -9
- package/dist/getProjectInfo/index.mjs +13 -22
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +95 -37
- package/dist/init/createConfig.d.mts +2 -2
- package/dist/init/createConfig.mjs +18 -12
- package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
- package/dist/init/index.d.mts +2 -2
- package/dist/init/index.mjs +90 -54
- package/dist/init/installDependencies.mjs +4 -2
- package/dist/init/logUpdateDependenciesError.d.mts +3 -1
- package/dist/init/logUpdateDependenciesError.mjs +7 -1
- package/dist/init/updateDependenciesWithoutInstalling.mjs +41 -11
- package/dist/init/updateNPMScripts.d.mts +3 -1
- package/dist/init/updateNPMScripts.mjs +10 -7
- package/dist/init/updateTypeScriptConfig.d.mts +4 -1
- package/dist/init/updateTypeScriptConfig.mjs +11 -7
- package/dist/initProgram.d.mts +1 -1
- package/dist/initProgram.mjs +17 -16
- package/dist/locateSegments.d.mts +8 -1
- package/dist/locateSegments.mjs +14 -4
- package/dist/new/addClassToSegmentCode.d.mts +1 -2
- package/dist/new/addClassToSegmentCode.mjs +3 -3
- package/dist/new/addCommonTerms.mjs +1 -0
- package/dist/new/index.d.mts +1 -1
- package/dist/new/index.mjs +3 -2
- package/dist/new/newModule.d.mts +2 -1
- package/dist/new/newModule.mjs +18 -16
- package/dist/new/newSegment.d.mts +2 -1
- package/dist/new/newSegment.mjs +19 -10
- package/dist/new/render.d.mts +7 -3
- package/dist/new/render.mjs +29 -8
- package/dist/types.d.mts +50 -41
- package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +3 -0
- package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +53 -0
- package/dist/utils/debounceWithArgs.d.mts +2 -2
- package/dist/utils/debounceWithArgs.mjs +24 -9
- package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
- package/dist/utils/formatLoggedSegmentName.mjs +3 -2
- package/dist/utils/getAvailablePort.mjs +1 -1
- package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
- package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
- package/dist/utils/normalizeOpenAPIMixins.d.mts +5 -0
- package/dist/utils/normalizeOpenAPIMixins.mjs +49 -0
- package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
- package/dist/utils/pickSegmentFullSchema.mjs +15 -0
- package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
- package/dist/utils/removeUnlistedDirectories.mjs +61 -0
- package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
- package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
- package/module-templates/controller.ts.ejs +56 -0
- package/module-templates/service.ts.ejs +28 -0
- package/package.json +42 -22
- package/dist/dev/diffSchema.d.mts +0 -43
- package/dist/dev/ensureClient.d.mts +0 -5
- package/dist/dev/ensureClient.mjs +0 -31
- package/dist/dev/isMetadataEmpty.d.mts +0 -2
- package/dist/dev/isMetadataEmpty.mjs +0 -4
- package/dist/dev/writeOneSchemaFile.d.mts +0 -11
- package/dist/generateClient.d.mts +0 -7
- package/dist/generateClient.mjs +0 -97
- package/dist/getProjectInfo/getConfig.d.mts +0 -11
- package/dist/getProjectInfo/getConfig.mjs +0 -29
- package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
- package/dist/postinstall.d.mts +0 -1
- package/dist/postinstall.mjs +0 -24
- package/templates/controller.ejs +0 -51
- package/templates/service.ejs +0 -27
- package/templates/worker.ejs +0 -24
- /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
- /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
package/dist/new/newModule.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
3
4
|
import render from './render.mjs';
|
|
4
5
|
import addClassToSegmentCode from './addClassToSegmentCode.mjs';
|
|
5
6
|
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
6
|
-
import locateSegments from '../locateSegments.mjs';
|
|
7
7
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
8
8
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
9
9
|
import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
10
10
|
import prettify from '../utils/prettify.mjs';
|
|
11
|
+
import resolveAbsoluteModulePath from '../utils/resolveAbsoluteModulePath.mjs';
|
|
12
|
+
import { locateSegments } from '../locateSegments.mjs';
|
|
11
13
|
function splitByLast(str, delimiter = '/') {
|
|
12
14
|
const index = str.lastIndexOf(delimiter);
|
|
13
15
|
if (index === -1) {
|
|
@@ -18,19 +20,19 @@ function splitByLast(str, delimiter = '/') {
|
|
|
18
20
|
const after = str.substring(index + delimiter.length);
|
|
19
21
|
return [before, after];
|
|
20
22
|
}
|
|
21
|
-
export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, }) {
|
|
22
|
-
const { config, log,
|
|
23
|
-
|
|
23
|
+
export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
|
|
24
|
+
const { config, log, cwd, apiDirAbsolutePath } = await getProjectInfo();
|
|
25
|
+
const segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
|
|
26
|
+
const isNodeNextResolution = ['node16', 'nodenext'].includes((await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase()) ?? '');
|
|
27
|
+
let templates = config.moduleTemplates;
|
|
24
28
|
const [segmentName, moduleName] = splitByLast(moduleNameWithOptionalSegment);
|
|
25
|
-
// replace c by controller, s by service,
|
|
29
|
+
// replace c by controller, s by service, everything else keeps the same
|
|
26
30
|
what = what.map((s) => {
|
|
27
31
|
switch (s) {
|
|
28
32
|
case 'c':
|
|
29
33
|
return 'controller';
|
|
30
34
|
case 's':
|
|
31
35
|
return 'service';
|
|
32
|
-
case 'w':
|
|
33
|
-
return 'worker';
|
|
34
36
|
default:
|
|
35
37
|
return s;
|
|
36
38
|
}
|
|
@@ -49,16 +51,13 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
|
|
|
49
51
|
throw new Error(`Template for "${type}" not found`);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
|
-
const segments = await locateSegments(apiDir);
|
|
53
54
|
const segment = segments.find((s) => s.segmentName === segmentName);
|
|
54
55
|
if (!segment) {
|
|
55
56
|
throw new Error(`Unable to create module. Segment "${segmentName}" not found. Run "vovk new segment ${segmentName}" to create it`);
|
|
56
57
|
}
|
|
57
58
|
for (const type of what) {
|
|
58
59
|
const templatePath = templates[type];
|
|
59
|
-
const templateAbsolutePath = templatePath
|
|
60
|
-
? path.resolve(cwd, templatePath)
|
|
61
|
-
: path.resolve(cwd, './node_modules', templatePath);
|
|
60
|
+
const templateAbsolutePath = resolveAbsoluteModulePath(templatePath, cwd);
|
|
62
61
|
const templateCode = await fs.readFile(templateAbsolutePath, 'utf-8');
|
|
63
62
|
const { dir: renderedDir, fileName, sourceName, compiledName, code, } = await render(templateCode, {
|
|
64
63
|
cwd,
|
|
@@ -66,6 +65,9 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
|
|
|
66
65
|
withService: what.includes('service'),
|
|
67
66
|
segmentName,
|
|
68
67
|
moduleName,
|
|
68
|
+
empty,
|
|
69
|
+
templateFileName: templateAbsolutePath,
|
|
70
|
+
isNodeNextResolution,
|
|
69
71
|
});
|
|
70
72
|
const dir = dirFlag || renderedDir;
|
|
71
73
|
if (!dir) {
|
|
@@ -84,24 +86,24 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
|
|
|
84
86
|
else {
|
|
85
87
|
await fs.mkdir(absoluteModuleDir, { recursive: true });
|
|
86
88
|
await fs.writeFile(absoluteModulePath, prettiedCode);
|
|
87
|
-
log.info(`Created ${chalkHighlightThing(
|
|
89
|
+
log.info(`Created${empty ? ' empty' : ''} ${chalkHighlightThing(absoluteModulePath)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
|
-
if (type === 'controller'
|
|
92
|
+
if (type === 'controller') {
|
|
91
93
|
if (!sourceName) {
|
|
92
94
|
throw new Error(`The template for "${type}" does not provide a sourceName`);
|
|
93
95
|
}
|
|
94
96
|
if (!compiledName) {
|
|
95
|
-
throw new Error(
|
|
97
|
+
throw new Error(`The template for "${type}" does not provide a compiledName`);
|
|
96
98
|
}
|
|
97
99
|
const { routeFilePath } = segment;
|
|
98
100
|
const segmentSourceCode = await fs.readFile(routeFilePath, 'utf-8');
|
|
99
|
-
|
|
101
|
+
let importPath = path.relative(path.dirname(routeFilePath) + '/', absoluteModulePath).replace(/\.(ts|tsx)$/, '');
|
|
102
|
+
importPath += isNodeNextResolution ? '.ts' : '';
|
|
100
103
|
if (!noSegmentUpdate) {
|
|
101
104
|
const newSegmentCode = await prettify(addClassToSegmentCode(segmentSourceCode, {
|
|
102
105
|
sourceName,
|
|
103
106
|
compiledName,
|
|
104
|
-
type,
|
|
105
107
|
importPath,
|
|
106
108
|
}), routeFilePath);
|
|
107
109
|
if (!dryRun) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export default function newSegment({ segmentName, overwrite, dryRun, }: {
|
|
1
|
+
export default function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }: {
|
|
2
2
|
segmentName: string;
|
|
3
|
+
isStaticSegment?: boolean;
|
|
3
4
|
overwrite?: boolean;
|
|
4
5
|
dryRun?: boolean;
|
|
5
6
|
}): Promise<void>;
|
package/dist/new/newSegment.mjs
CHANGED
|
@@ -5,23 +5,30 @@ import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
|
5
5
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
6
6
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
7
7
|
import prettify from '../utils/prettify.mjs';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
export default async function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }) {
|
|
10
|
+
const { apiDirAbsolutePath, log, config } = await getProjectInfo();
|
|
11
|
+
if (!apiDirAbsolutePath) {
|
|
12
|
+
throw new Error('No API directory found. Please ensure you are in a Nest.js project.');
|
|
13
|
+
}
|
|
14
|
+
const absoluteSegmentRoutePath = path.join(apiDirAbsolutePath, segmentName, '[[...vovk]]/route.ts');
|
|
11
15
|
if (!overwrite && (await getFileSystemEntryType(absoluteSegmentRoutePath))) {
|
|
12
16
|
throw new Error(`Unable to create new segment. ${formatLoggedSegmentName(segmentName, { upperFirst: true })} already exists.`);
|
|
13
17
|
}
|
|
14
|
-
const code = await prettify(`import {
|
|
18
|
+
const code = await prettify(`import { initSegment${isStaticSegment ? ', generateStaticAPI' : ''} } from 'vovk';
|
|
15
19
|
|
|
16
20
|
const controllers = {};
|
|
17
|
-
const workers = {};
|
|
18
21
|
|
|
19
22
|
export type Controllers = typeof controllers;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export
|
|
23
|
+
${isStaticSegment
|
|
24
|
+
? `
|
|
25
|
+
export function generateStaticParams() {
|
|
26
|
+
return generateStaticAPI(controllers);
|
|
27
|
+
}
|
|
28
|
+
`
|
|
29
|
+
: ''}
|
|
30
|
+
export const { GET${isStaticSegment ? '' : ', POST, PATCH, PUT, HEAD, OPTIONS, DELETE'} } = initSegment({
|
|
23
31
|
${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
|
|
24
|
-
workers,
|
|
25
32
|
controllers,
|
|
26
33
|
});
|
|
27
34
|
`, absoluteSegmentRoutePath);
|
|
@@ -29,5 +36,7 @@ ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
|
|
|
29
36
|
await fs.mkdir(path.dirname(absoluteSegmentRoutePath), { recursive: true });
|
|
30
37
|
await fs.writeFile(absoluteSegmentRoutePath, code);
|
|
31
38
|
}
|
|
32
|
-
log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} created at ${absoluteSegmentRoutePath}
|
|
39
|
+
log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true, isStatic: isStaticSegment })} created at ${absoluteSegmentRoutePath}.`);
|
|
40
|
+
const dir = chalk.cyanBright([segmentName, 'thing'].filter(Boolean).join('/'));
|
|
41
|
+
log.info(`Run ${chalkHighlightThing(`npx vovk new service controller ${dir}`)} to create a new controller with a service at ${path.join(config.modulesDir, dir)} folder for this segment`);
|
|
33
42
|
}
|
package/dist/new/render.d.mts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { VovkStrictConfig } from 'vovk';
|
|
2
|
+
import type { VovkModuleRenderResult } from '../types.mjs';
|
|
3
|
+
export default function render(codeTemplate: string, { config, withService, segmentName, moduleName, empty, templateFileName, isNodeNextResolution, }: {
|
|
3
4
|
cwd: string;
|
|
4
|
-
config:
|
|
5
|
+
config: VovkStrictConfig;
|
|
5
6
|
withService: boolean;
|
|
6
7
|
segmentName: string;
|
|
7
8
|
moduleName: string;
|
|
9
|
+
empty?: boolean;
|
|
10
|
+
templateFileName: string;
|
|
11
|
+
isNodeNextResolution: boolean;
|
|
8
12
|
}): Promise<VovkModuleRenderResult>;
|
package/dist/new/render.mjs
CHANGED
|
@@ -4,25 +4,46 @@ import _ from 'lodash';
|
|
|
4
4
|
import pluralize from 'pluralize';
|
|
5
5
|
import addCommonTerms from './addCommonTerms.mjs';
|
|
6
6
|
addCommonTerms();
|
|
7
|
-
export default async function render(codeTemplate, { config, withService, segmentName, moduleName, }) {
|
|
7
|
+
export default async function render(codeTemplate, { config, withService, segmentName, moduleName, empty, templateFileName, isNodeNextResolution, }) {
|
|
8
8
|
const getModuleDirName = (givenSegmentName, givenModuleName) => [config.modulesDir, givenSegmentName || config.rootSegmentModulesDirName, _.camelCase(givenModuleName)]
|
|
9
9
|
.filter(Boolean)
|
|
10
10
|
.join('/');
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const theThing = _.camelCase(moduleName);
|
|
12
|
+
const TheThing = _.upperFirst(theThing);
|
|
13
|
+
const the_thing = _.snakeCase(moduleName);
|
|
14
|
+
const THE_THING = _.toUpper(the_thing);
|
|
15
|
+
const the__thing = _.kebabCase(moduleName);
|
|
16
|
+
const t = {
|
|
17
|
+
// module name variations
|
|
18
|
+
moduleName,
|
|
19
|
+
theThing,
|
|
20
|
+
theThings: pluralize(theThing),
|
|
21
|
+
TheThing,
|
|
22
|
+
TheThings: pluralize(TheThing),
|
|
23
|
+
the_thing,
|
|
24
|
+
the_things: pluralize(the_thing),
|
|
25
|
+
THE_THING,
|
|
26
|
+
THE_THINGS: pluralize(THE_THING),
|
|
27
|
+
'the-thing': the__thing,
|
|
28
|
+
'the-things': pluralize(the__thing),
|
|
29
|
+
// data
|
|
13
30
|
config,
|
|
14
31
|
withService,
|
|
15
32
|
segmentName,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
33
|
+
nodeNextResolutionExt: {
|
|
34
|
+
ts: isNodeNextResolution ? '.ts' : '',
|
|
35
|
+
js: isNodeNextResolution ? '.js' : '',
|
|
36
|
+
cjs: isNodeNextResolution ? '.cjs' : '',
|
|
37
|
+
mjs: isNodeNextResolution ? '.mjs' : '',
|
|
38
|
+
},
|
|
39
|
+
defaultDir: getModuleDirName(segmentName, theThing),
|
|
19
40
|
// libraries
|
|
20
41
|
_, // lodash
|
|
21
42
|
pluralize,
|
|
22
43
|
};
|
|
23
|
-
const parsed = matter((await ejs.render(codeTemplate,
|
|
44
|
+
const parsed = matter((await ejs.render(codeTemplate, { t }, { async: true, filename: templateFileName })).trim());
|
|
24
45
|
const { dir, fileName, sourceName, compiledName } = parsed.data;
|
|
25
|
-
const code = parsed.content;
|
|
46
|
+
const code = empty ? (sourceName ? `export default class ${sourceName} {}` : '') : parsed.content;
|
|
26
47
|
return {
|
|
27
48
|
dir,
|
|
28
49
|
fileName,
|
package/dist/types.d.mts
CHANGED
|
@@ -1,42 +1,5 @@
|
|
|
1
1
|
import type { LogLevelNames } from 'loglevel';
|
|
2
|
-
|
|
3
|
-
export type VovkDevEnv = {
|
|
4
|
-
PORT?: string;
|
|
5
|
-
VOVK_CLIENT_OUT_DIR?: string;
|
|
6
|
-
VOVK_SCHEMA_OUT_DIR?: string;
|
|
7
|
-
VOVK_FETCHER?: string;
|
|
8
|
-
VOVK_VALIDATE_ON_CLIENT?: string;
|
|
9
|
-
VOVK_MODULES_DIR?: string;
|
|
10
|
-
VOVK_VALIDATION_LIBRARY?: string;
|
|
11
|
-
VOVK_ORIGIN?: string;
|
|
12
|
-
VOVK_ROOT_ENTRY?: string;
|
|
13
|
-
VOVK_API_ENTRY_POINT?: string;
|
|
14
|
-
VOVK_ROOT_SEGMENT_MODULES_DIR_NAME?: string;
|
|
15
|
-
VOVK_LOG_LEVEL?: LogLevelNames;
|
|
16
|
-
VOVK_PRETTIFY_CLIENT?: string;
|
|
17
|
-
VOVK_DEV_HTTPS?: string;
|
|
18
|
-
__VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
|
|
19
|
-
};
|
|
20
|
-
export type VovkConfig = {
|
|
21
|
-
clientOutDir?: string;
|
|
22
|
-
schemaOutDir?: string;
|
|
23
|
-
fetcher?: string;
|
|
24
|
-
validateOnClient?: string | null;
|
|
25
|
-
modulesDir?: string;
|
|
26
|
-
validationLibrary?: string | null;
|
|
27
|
-
rootEntry?: string;
|
|
28
|
-
origin?: string;
|
|
29
|
-
rootSegmentModulesDirName?: string;
|
|
30
|
-
logLevel?: LogLevelNames;
|
|
31
|
-
prettifyClient?: boolean;
|
|
32
|
-
devHttps?: boolean;
|
|
33
|
-
templates?: {
|
|
34
|
-
service?: string;
|
|
35
|
-
controller?: string;
|
|
36
|
-
worker?: string;
|
|
37
|
-
[key: string]: string | undefined;
|
|
38
|
-
};
|
|
39
|
-
};
|
|
2
|
+
import type { VovkStrictConfig } from 'vovk';
|
|
40
3
|
export type VovkModuleRenderResult = {
|
|
41
4
|
fileName: string;
|
|
42
5
|
dir: string;
|
|
@@ -45,11 +8,41 @@ export type VovkModuleRenderResult = {
|
|
|
45
8
|
code: string;
|
|
46
9
|
};
|
|
47
10
|
export interface DevOptions {
|
|
48
|
-
|
|
11
|
+
schemaOut?: string;
|
|
49
12
|
nextDev?: boolean;
|
|
13
|
+
exit?: boolean;
|
|
50
14
|
}
|
|
51
15
|
export interface GenerateOptions {
|
|
52
|
-
|
|
16
|
+
prettify?: boolean;
|
|
17
|
+
configPath?: string;
|
|
18
|
+
schemaPath?: string;
|
|
19
|
+
openapiSpec?: string[];
|
|
20
|
+
openapiGetModuleName?: string[];
|
|
21
|
+
openapiGetMethodName?: string[];
|
|
22
|
+
openapiRootUrl?: string[];
|
|
23
|
+
openapiMixinName?: string[];
|
|
24
|
+
watch?: boolean | string;
|
|
25
|
+
forceTsStandalone?: boolean;
|
|
26
|
+
composedFrom?: string[];
|
|
27
|
+
composedOut?: string;
|
|
28
|
+
composedOnly?: boolean;
|
|
29
|
+
composedIncludeSegments?: string[];
|
|
30
|
+
composedExcludeSegments?: string[];
|
|
31
|
+
segmentedFrom?: string[];
|
|
32
|
+
segmentedOut?: string;
|
|
33
|
+
segmentedOnly?: boolean;
|
|
34
|
+
segmentedIncludeSegments?: string[];
|
|
35
|
+
segmentedExcludeSegments?: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface BundleOptions extends Partial<Omit<VovkStrictConfig['bundle'], 'requires'>> {
|
|
38
|
+
config?: string;
|
|
39
|
+
schema?: string;
|
|
40
|
+
openapiSpec?: string[];
|
|
41
|
+
openapiGetModuleName?: string[];
|
|
42
|
+
openapiGetMethodName?: string[];
|
|
43
|
+
openapiRootUrl?: string[];
|
|
44
|
+
openapiMixinName?: string[];
|
|
45
|
+
forceTsStandalone?: boolean;
|
|
53
46
|
}
|
|
54
47
|
export interface InitOptions {
|
|
55
48
|
yes?: boolean;
|
|
@@ -62,8 +55,9 @@ export interface InitOptions {
|
|
|
62
55
|
updateTsConfig?: boolean;
|
|
63
56
|
updateScripts?: 'implicit' | 'explicit';
|
|
64
57
|
validationLibrary?: string | null;
|
|
65
|
-
|
|
58
|
+
reactQuery?: boolean;
|
|
66
59
|
dryRun?: boolean;
|
|
60
|
+
lang?: string[];
|
|
67
61
|
channel?: 'latest' | 'beta' | 'draft';
|
|
68
62
|
}
|
|
69
63
|
export interface NewOptions {
|
|
@@ -72,4 +66,19 @@ export interface NewOptions {
|
|
|
72
66
|
dir?: string;
|
|
73
67
|
overwrite?: boolean;
|
|
74
68
|
noSegmentUpdate?: boolean;
|
|
69
|
+
empty?: boolean;
|
|
70
|
+
static?: boolean;
|
|
75
71
|
}
|
|
72
|
+
export type VovkEnv = {
|
|
73
|
+
PORT?: string;
|
|
74
|
+
VOVK_SCHEMA_OUT_DIR?: string;
|
|
75
|
+
VOVK_ORIGIN?: string;
|
|
76
|
+
VOVK_ROOT_ENTRY?: string;
|
|
77
|
+
VOVK_API_ENTRY_POINT?: string;
|
|
78
|
+
VOVK_LOG_LEVEL?: LogLevelNames;
|
|
79
|
+
VOVK_PRETTIFY_CLIENT?: string;
|
|
80
|
+
VOVK_DEV_HTTPS?: string;
|
|
81
|
+
__VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
|
|
82
|
+
__VOVK_SCHEMA_OUT_FLAG__?: string;
|
|
83
|
+
__VOVK_EXIT__?: 'true' | 'false';
|
|
84
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { JSONSchema } from 'json-schema-to-typescript';
|
|
2
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
3
|
+
export declare function compileJSONSchemaToTypeScriptType(schema: JSONSchema, typeName: string, components?: NonNullable<OpenAPIObject['components']>): Promise<string>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { compile } from 'json-schema-to-typescript';
|
|
2
|
+
function replaceUnresolvedRefs(schema, components = {}) {
|
|
3
|
+
if (schema === null || typeof schema !== 'object') {
|
|
4
|
+
return schema;
|
|
5
|
+
}
|
|
6
|
+
if (typeof schema.$ref === 'string') {
|
|
7
|
+
const refPath = schema.$ref.split('/');
|
|
8
|
+
const componentName = refPath[refPath.length - 1];
|
|
9
|
+
if (!(componentName in (components.schemas ?? schema.$defs ?? schema.definitions ?? {}))) {
|
|
10
|
+
return {
|
|
11
|
+
tsType: `unknown`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(schema)) {
|
|
16
|
+
return schema.map((item) => replaceUnresolvedRefs(item));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
21
|
+
result[key] = replaceUnresolvedRefs(value);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function compileJSONSchemaToTypeScriptType(schema, typeName, components = {}) {
|
|
27
|
+
if (!schema)
|
|
28
|
+
return '';
|
|
29
|
+
schema = replaceUnresolvedRefs(schema);
|
|
30
|
+
// tsType attribute isn't working with objects that use $ref, so we need to handle it separately
|
|
31
|
+
if ('tsType' in schema && typeof schema.tsType === 'string')
|
|
32
|
+
return `export type ${typeName} = ${schema.tsType};\n`;
|
|
33
|
+
const tsType = await compile({ ...schema, components }, typeName, {
|
|
34
|
+
bannerComment: schema.description ? `/**\n * ${schema.description}\n */` : '',
|
|
35
|
+
style: {
|
|
36
|
+
bracketSpacing: true,
|
|
37
|
+
printWidth: 80,
|
|
38
|
+
semi: true,
|
|
39
|
+
singleQuote: true,
|
|
40
|
+
tabWidth: 2,
|
|
41
|
+
useTabs: false,
|
|
42
|
+
trailingComma: 'all',
|
|
43
|
+
unreachableDefinitions: false,
|
|
44
|
+
declareExternallyReferenced: false,
|
|
45
|
+
},
|
|
46
|
+
// Don't generate separate interfaces for additionalProperties
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
// Enable strict null checks
|
|
49
|
+
strictIndexSignatures: true,
|
|
50
|
+
// Don't add schema as comment
|
|
51
|
+
});
|
|
52
|
+
return tsType;
|
|
53
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { KnownAny } from '
|
|
2
|
-
export default function debounceWithArgs<
|
|
1
|
+
import type { KnownAny } from 'vovk';
|
|
2
|
+
export default function debounceWithArgs<Callback extends (...args: KnownAny[]) => KnownAny>(callback: Callback, wait: number): (...args: Parameters<Callback>) => Promise<Awaited<ReturnType<Callback>>>;
|
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
1
|
+
export default function debounceWithArgs(callback, wait) {
|
|
2
|
+
// Stores timeouts keyed by the stringified arguments
|
|
3
|
+
const timeouts = new Map();
|
|
4
4
|
return (...args) => {
|
|
5
|
+
// Convert arguments to a JSON string (or any other stable key generation)
|
|
5
6
|
const key = JSON.stringify(args);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const debouncedFn = debouncedFunctions.get(key);
|
|
10
|
-
if (debouncedFn) {
|
|
11
|
-
debouncedFn(...args);
|
|
7
|
+
// Clear any existing timer for this specific key
|
|
8
|
+
if (timeouts.has(key)) {
|
|
9
|
+
clearTimeout(timeouts.get(key));
|
|
12
10
|
}
|
|
11
|
+
// Return a promise that resolves/rejects after the debounce delay
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const timeoutId = setTimeout(async () => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await callback(...args);
|
|
16
|
+
resolve(result);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
reject(error);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
// Remove the entry once the callback is invoked
|
|
23
|
+
timeouts.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}, wait);
|
|
26
|
+
timeouts.set(key, timeoutId);
|
|
27
|
+
});
|
|
13
28
|
};
|
|
14
29
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export default function formatLoggedSegmentName(segmentName: string, { withChalk, upperFirst }?: {
|
|
1
|
+
export default function formatLoggedSegmentName(segmentName: string, { withChalk, upperFirst, isStatic, segmentType, }?: {
|
|
2
2
|
withChalk?: boolean;
|
|
3
3
|
upperFirst?: boolean;
|
|
4
|
+
isStatic?: boolean;
|
|
5
|
+
segmentType?: 'segment' | 'mixin';
|
|
4
6
|
}): string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import upperFirstLodash from 'lodash/upperFirst.js';
|
|
2
2
|
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
3
|
-
export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false
|
|
4
|
-
|
|
3
|
+
export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false, isStatic = false, segmentType = 'segment', // TODO: Apply to all formatLoggedSegmentName invocations
|
|
4
|
+
} = {}) {
|
|
5
|
+
let text = segmentName ? `${isStatic ? 'static ' : ''}${segmentType} "${segmentName}"` : 'the root segment';
|
|
5
6
|
text = upperFirst ? upperFirstLodash(text) : text;
|
|
6
7
|
return withChalk ? chalkHighlightThing(text) : text;
|
|
7
8
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function getPublicModuleNameFromPath(modulePath) {
|
|
2
|
+
if (modulePath && !modulePath.startsWith('.') && !modulePath.startsWith('/')) {
|
|
3
|
+
const pathParts = modulePath.split('/');
|
|
4
|
+
const moduleName = pathParts[0].startsWith('@') ? `${pathParts[0]}/${pathParts[1]}` : pathParts[0];
|
|
5
|
+
const restPath = pathParts.slice(pathParts[0].startsWith('@') ? 2 : 1).join('/');
|
|
6
|
+
return { moduleName, restPath };
|
|
7
|
+
}
|
|
8
|
+
return { moduleName: null, restPath: modulePath };
|
|
9
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as YAML from 'yaml';
|
|
4
|
+
async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
5
|
+
const openApiSpecAbsolutePath = path.resolve(cwd, openApiSpecFilePath);
|
|
6
|
+
const fileName = path.basename(openApiSpecAbsolutePath);
|
|
7
|
+
if (!fileName.endsWith('.json') && !fileName.endsWith('.yaml') && !fileName.endsWith('.yml')) {
|
|
8
|
+
throw new Error(`Invalid OpenAPI spec file format: ${fileName}. Please provide a JSON or YAML file.`);
|
|
9
|
+
}
|
|
10
|
+
const openApiSpecContent = await fs.readFile(openApiSpecAbsolutePath, 'utf8');
|
|
11
|
+
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
12
|
+
}
|
|
13
|
+
async function getOpenApiSpecRemote(openApiSpecUrl) {
|
|
14
|
+
const resp = await fetch(openApiSpecUrl);
|
|
15
|
+
const text = await resp.text();
|
|
16
|
+
if (!resp.ok) {
|
|
17
|
+
throw new Error(`Failed to fetch OpenAPI spec from ${openApiSpecUrl}: ${resp.status} ${resp.statusText}`);
|
|
18
|
+
}
|
|
19
|
+
return (text.trim().startsWith('{') || text.trim().startsWith('[') ? JSON.parse(text) : YAML.parse(text));
|
|
20
|
+
}
|
|
21
|
+
export async function normalizeOpenAPIMixins({ mixinModules, cwd = process.cwd(), }) {
|
|
22
|
+
if (mixinModules) {
|
|
23
|
+
const modules = await Promise.all(Object.entries(mixinModules).map(async ([mixinName, { source, apiRoot, getModuleName, getMethodName, errorMessageKey }]) => {
|
|
24
|
+
let openAPIObject;
|
|
25
|
+
if ('url' in source) {
|
|
26
|
+
openAPIObject = await getOpenApiSpecRemote(source.url);
|
|
27
|
+
}
|
|
28
|
+
else if ('file' in source) {
|
|
29
|
+
openAPIObject = await getOpenApiSpecLocal(source.file, cwd);
|
|
30
|
+
}
|
|
31
|
+
else if ('object' in source) {
|
|
32
|
+
openAPIObject = source.object;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
throw new Error('Invalid source type for OpenAPI configuration');
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
source: { object: openAPIObject },
|
|
39
|
+
apiRoot,
|
|
40
|
+
getModuleName,
|
|
41
|
+
getMethodName,
|
|
42
|
+
errorMessageKey,
|
|
43
|
+
mixinName,
|
|
44
|
+
};
|
|
45
|
+
}));
|
|
46
|
+
return Object.fromEntries(modules.map((module) => [module.mixinName, module]));
|
|
47
|
+
}
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { VovkSchemaIdEnum } from 'vovk';
|
|
2
|
+
export default function pickSegmentFullSchema(schema, segmentNames) {
|
|
3
|
+
return {
|
|
4
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
5
|
+
meta: schema.meta,
|
|
6
|
+
segments: Object.fromEntries(segmentNames.map((segmentName) => [segmentName, schema.segments[segmentName]])),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function omitSegmentFullSchema(schema, segmentNames) {
|
|
10
|
+
return {
|
|
11
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
12
|
+
meta: schema.meta,
|
|
13
|
+
segments: Object.fromEntries(Object.entries(schema.segments).filter(([segmentName]) => !segmentNames.includes(segmentName))),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -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,61 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import getFileSystemEntryType, { FileSystemEntryType } from './getFileSystemEntryType.mjs';
|
|
4
|
+
/**
|
|
5
|
+
* Removes all directories in a folder that aren't in the provided allowlist
|
|
6
|
+
* Supports nested directory paths like 'foo/bar/baz'
|
|
7
|
+
*
|
|
8
|
+
* @param folderPath - The path to the folder to process
|
|
9
|
+
* @param allowedDirs - Array of relative directory paths to keep
|
|
10
|
+
* @returns Promise that resolves when all operations are complete
|
|
11
|
+
*/
|
|
12
|
+
async function removeUnlistedDirectories(folderPath, allowedDirs) {
|
|
13
|
+
// Normalize all allowed paths to use the system-specific separator
|
|
14
|
+
const normalizedAllowedDirs = allowedDirs.map((dir) => dir.split('/').join(path.sep));
|
|
15
|
+
// Process the directory tree recursively
|
|
16
|
+
await processDirectory(folderPath, '', normalizedAllowedDirs);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Recursively processes directories to determine which should be kept or removed
|
|
20
|
+
*
|
|
21
|
+
* @param basePath - The absolute base path being processed
|
|
22
|
+
* @param relativePath - The current relative path from the base
|
|
23
|
+
* @param allowedDirs - Normalized list of allowed directory paths
|
|
24
|
+
*/
|
|
25
|
+
async function processDirectory(basePath, relativePath, allowedDirs) {
|
|
26
|
+
const currentDirPath = path.join(basePath, relativePath);
|
|
27
|
+
// check if the current path is a directory
|
|
28
|
+
const type = await getFileSystemEntryType(currentDirPath);
|
|
29
|
+
if (type !== FileSystemEntryType.DIRECTORY) {
|
|
30
|
+
// If it's not a directory, return early
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Read all entries in the current directory
|
|
34
|
+
const entries = await fs.readdir(currentDirPath, { withFileTypes: true });
|
|
35
|
+
// Process only directories
|
|
36
|
+
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
37
|
+
// Check each directory
|
|
38
|
+
for (const dir of dirEntries) {
|
|
39
|
+
// Calculate the new relative path
|
|
40
|
+
const newRelativePath = relativePath ? path.join(relativePath, dir.name) : dir.name;
|
|
41
|
+
// Check if this directory or any of its subdirectories should be kept
|
|
42
|
+
const shouldKeep = allowedDirs.some((allowedDir) => {
|
|
43
|
+
// Direct match
|
|
44
|
+
if (allowedDir === newRelativePath)
|
|
45
|
+
return true;
|
|
46
|
+
// Check if it's a parent path of an allowed directory
|
|
47
|
+
// e.g. "foo" is a parent of "foo/bar/baz"
|
|
48
|
+
return allowedDir.startsWith(newRelativePath + path.sep);
|
|
49
|
+
});
|
|
50
|
+
if (shouldKeep) {
|
|
51
|
+
// Recursively process this directory's contents
|
|
52
|
+
await processDirectory(basePath, newRelativePath, allowedDirs);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Remove this directory since it's not in the allowed list
|
|
56
|
+
const fullPath = path.join(basePath, newRelativePath);
|
|
57
|
+
await fs.rm(fullPath, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export default removeUnlistedDirectories;
|