vovk-cli 0.0.1-draft.333 → 0.0.1-draft.335
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/readme/README.md.ejs +3 -2
- package/client-templates/schemaCjs/schema.cjs.ejs +1 -4
- package/client-templates/schemaTs/schema.ts.ejs +1 -5
- package/dist/bundle/index.mjs +10 -10
- package/dist/dev/ensureSchemaFiles.d.mts +1 -1
- package/dist/dev/index.d.mts +1 -1
- package/dist/dev/index.mjs +28 -19
- package/dist/dev/writeMetaJson.d.mts +1 -1
- package/dist/dev/writeMetaJson.mjs +5 -2
- package/dist/generate/generate.d.mts +2 -5
- package/dist/generate/generate.mjs +38 -40
- package/dist/generate/getClientTemplateFiles.mjs +1 -1
- package/dist/generate/getProjectFullSchema.d.mts +5 -2
- package/dist/generate/getProjectFullSchema.mjs +7 -7
- package/dist/generate/index.mjs +3 -1
- package/dist/generate/writeOneClientFile.d.mts +4 -3
- package/dist/generate/writeOneClientFile.mjs +11 -5
- package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +3 -3
- package/dist/getProjectInfo/getConfig/index.d.mts +9 -51
- package/dist/getProjectInfo/getConfig/index.mjs +18 -16
- package/dist/getProjectInfo/getMetaSchema.d.mts +10 -0
- package/dist/getProjectInfo/getMetaSchema.mjs +13 -0
- package/dist/getProjectInfo/index.d.mts +4 -2
- package/dist/getProjectInfo/index.mjs +5 -2
- package/dist/index.mjs +25 -6
- package/dist/init/createConfig.mjs +26 -4
- package/dist/init/createStandardSchemaValidatorFile.d.mts +4 -0
- package/dist/init/createStandardSchemaValidatorFile.mjs +38 -0
- package/dist/init/index.mjs +47 -21
- package/dist/init/updateNPMScripts.d.mts +0 -1
- package/dist/init/updateNPMScripts.mjs +1 -5
- package/dist/initProgram.mjs +1 -1
- package/dist/new/index.d.mts +2 -1
- package/dist/new/index.mjs +3 -2
- package/dist/new/newModule.d.mts +3 -1
- package/dist/new/newModule.mjs +2 -3
- package/dist/new/newSegment.d.mts +3 -1
- package/dist/new/newSegment.mjs +2 -3
- package/dist/types.d.mts +8 -3
- package/dist/utils/generateFnName.d.mts +23 -0
- package/dist/utils/generateFnName.mjs +76 -0
- package/dist/utils/normalizeOpenAPIMixin.d.mts +14 -0
- package/dist/utils/normalizeOpenAPIMixin.mjs +114 -0
- package/module-templates/arktype/controller.ts.ejs +68 -0
- package/module-templates/valibot/controller.ts.ejs +68 -0
- package/package.json +2 -2
- package/dist/generate/mergePackages.d.mts +0 -7
- package/dist/generate/mergePackages.mjs +0 -55
- package/dist/utils/normalizeOpenAPIMixins.d.mts +0 -7
- package/dist/utils/normalizeOpenAPIMixins.mjs +0 -67
- /package/module-templates/{controller.ts.ejs → type/controller.ts.ejs} +0 -0
- /package/module-templates/{service.ts.ejs → type/service.ts.ejs} +0 -0
package/dist/new/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import newModule from './newModule.mjs';
|
|
2
2
|
import newSegment from './newSegment.mjs';
|
|
3
|
-
export async function newComponents(components, { dryRun, dir, templates, overwrite, noSegmentUpdate, empty, static: isStaticSegment }) {
|
|
3
|
+
export async function newComponents(components, projectInfo, { dryRun, dir, templates, overwrite, noSegmentUpdate, empty, static: isStaticSegment }) {
|
|
4
4
|
if (components[0] === 'segment' || components[0] === 'segments') {
|
|
5
5
|
// vovk new segment [segmentName]
|
|
6
6
|
let segmentNames = components
|
|
@@ -10,7 +10,7 @@ export async function newComponents(components, { dryRun, dir, templates, overwr
|
|
|
10
10
|
segmentNames = [''];
|
|
11
11
|
}
|
|
12
12
|
for (const segmentName of segmentNames) {
|
|
13
|
-
await newSegment({ segmentName, isStaticSegment, overwrite, dryRun });
|
|
13
|
+
await newSegment({ projectInfo, segmentName, isStaticSegment, overwrite, dryRun });
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
@@ -24,6 +24,7 @@ export async function newComponents(components, { dryRun, dir, templates, overwr
|
|
|
24
24
|
throw new Error('A module name with an optional segment cannot be empty');
|
|
25
25
|
}
|
|
26
26
|
await newModule({
|
|
27
|
+
projectInfo,
|
|
27
28
|
what,
|
|
28
29
|
moduleNameWithOptionalSegment,
|
|
29
30
|
dir,
|
package/dist/new/newModule.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
2
|
+
export default function newModule({ projectInfo, what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }: {
|
|
3
|
+
projectInfo: ProjectInfo;
|
|
2
4
|
what: string[];
|
|
3
5
|
moduleNameWithOptionalSegment: string;
|
|
4
6
|
dryRun?: boolean;
|
package/dist/new/newModule.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import { getTsconfig } from 'get-tsconfig';
|
|
4
4
|
import render from './render.mjs';
|
|
5
5
|
import addClassToSegmentCode from './addClassToSegmentCode.mjs';
|
|
6
|
-
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
7
6
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
8
7
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
9
8
|
import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
@@ -20,8 +19,8 @@ function splitByLast(str, delimiter = '/') {
|
|
|
20
19
|
const after = str.substring(index + delimiter.length);
|
|
21
20
|
return [before, after];
|
|
22
21
|
}
|
|
23
|
-
export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
|
|
24
|
-
const { config, log, cwd, apiDirAbsolutePath } =
|
|
22
|
+
export default async function newModule({ projectInfo, what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
|
|
23
|
+
const { config, log, cwd, apiDirAbsolutePath } = projectInfo;
|
|
25
24
|
const segments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
|
|
26
25
|
const isNodeNextResolution = ['node16', 'nodenext'].includes((await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase()) ?? '');
|
|
27
26
|
let templates = config.moduleTemplates;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
2
|
+
export default function newSegment({ projectInfo, segmentName, isStaticSegment, overwrite, dryRun, }: {
|
|
3
|
+
projectInfo: ProjectInfo;
|
|
2
4
|
segmentName: string;
|
|
3
5
|
isStaticSegment?: boolean;
|
|
4
6
|
overwrite?: boolean;
|
package/dist/new/newSegment.mjs
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
-
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
4
3
|
import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
|
|
5
4
|
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
6
5
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
7
6
|
import prettify from '../utils/prettify.mjs';
|
|
8
7
|
import chalk from 'chalk';
|
|
9
|
-
export default async function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }) {
|
|
10
|
-
const { apiDirAbsolutePath, log, config } =
|
|
8
|
+
export default async function newSegment({ projectInfo, segmentName, isStaticSegment, overwrite, dryRun, }) {
|
|
9
|
+
const { apiDirAbsolutePath, log, config } = projectInfo;
|
|
11
10
|
if (!apiDirAbsolutePath) {
|
|
12
11
|
throw new Error('No API directory found. Please ensure you are in a Nest.js project.');
|
|
13
12
|
}
|
package/dist/types.d.mts
CHANGED
|
@@ -12,6 +12,7 @@ export interface DevOptions {
|
|
|
12
12
|
nextDev?: boolean;
|
|
13
13
|
exit?: boolean;
|
|
14
14
|
devHttps?: boolean;
|
|
15
|
+
logLevel?: LogLevelNames;
|
|
15
16
|
}
|
|
16
17
|
export interface GenerateOptions {
|
|
17
18
|
prettify?: boolean;
|
|
@@ -35,22 +36,24 @@ export interface GenerateOptions {
|
|
|
35
36
|
segmentedOnly?: boolean;
|
|
36
37
|
segmentedIncludeSegments?: string[];
|
|
37
38
|
segmentedExcludeSegments?: string[];
|
|
39
|
+
logLevel?: LogLevelNames;
|
|
38
40
|
}
|
|
39
41
|
export interface BundleOptions extends Partial<Pick<VovkStrictConfig['bundle'], 'prebundleOutDir' | 'keepPrebundleDir' | 'includeSegments' | 'excludeSegments'>> {
|
|
40
42
|
config?: string;
|
|
41
43
|
schema?: string;
|
|
42
44
|
outDir?: string;
|
|
43
45
|
origin?: string;
|
|
46
|
+
tsconfig?: string;
|
|
44
47
|
openapiSpec?: string[];
|
|
45
48
|
openapiGetModuleName?: string[];
|
|
46
49
|
openapiGetMethodName?: string[];
|
|
47
50
|
openapiRootUrl?: string[];
|
|
48
51
|
openapiMixinName?: string[];
|
|
52
|
+
logLevel?: LogLevelNames;
|
|
49
53
|
}
|
|
50
54
|
export interface InitOptions {
|
|
51
55
|
prefix?: string;
|
|
52
56
|
yes?: boolean;
|
|
53
|
-
logLevel: LogLevelNames;
|
|
54
57
|
useNpm?: boolean;
|
|
55
58
|
useYarn?: boolean;
|
|
56
59
|
usePnpm?: boolean;
|
|
@@ -58,10 +61,11 @@ export interface InitOptions {
|
|
|
58
61
|
skipInstall?: boolean;
|
|
59
62
|
updateTsConfig?: boolean;
|
|
60
63
|
updateScripts?: 'implicit' | 'explicit';
|
|
61
|
-
validationLibrary?:
|
|
64
|
+
validationLibrary?: 'zod' | 'yup' | 'class-validator' | 'valibot' | 'arktype' | null;
|
|
62
65
|
dryRun?: boolean;
|
|
63
66
|
lang?: string[];
|
|
64
67
|
channel?: 'latest' | 'beta' | 'draft';
|
|
68
|
+
logLevel?: LogLevelNames;
|
|
65
69
|
}
|
|
66
70
|
export interface NewOptions {
|
|
67
71
|
dryRun?: boolean;
|
|
@@ -71,6 +75,7 @@ export interface NewOptions {
|
|
|
71
75
|
noSegmentUpdate?: boolean;
|
|
72
76
|
empty?: boolean;
|
|
73
77
|
static?: boolean;
|
|
78
|
+
logLevel?: LogLevelNames;
|
|
74
79
|
}
|
|
75
80
|
export type VovkEnv = {
|
|
76
81
|
PORT?: string;
|
|
@@ -78,9 +83,9 @@ export type VovkEnv = {
|
|
|
78
83
|
VOVK_ORIGIN?: string;
|
|
79
84
|
VOVK_ROOT_ENTRY?: string;
|
|
80
85
|
VOVK_API_ENTRY_POINT?: string;
|
|
81
|
-
VOVK_LOG_LEVEL?: LogLevelNames;
|
|
82
86
|
__VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
|
|
83
87
|
__VOVK_SCHEMA_OUT_FLAG__?: string;
|
|
84
88
|
__VOVK_DEV_HTTPS_FLAG__?: 'true' | 'false';
|
|
85
89
|
__VOVK_EXIT__?: 'true' | 'false';
|
|
90
|
+
__VOVK_LOG_LEVEL__?: LogLevelNames;
|
|
86
91
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HttpMethod } from 'vovk';
|
|
2
|
+
export interface VerbMapEntry {
|
|
3
|
+
noParams?: string;
|
|
4
|
+
withParams?: string;
|
|
5
|
+
default?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const VERB_MAP: Record<HttpMethod, VerbMapEntry>;
|
|
8
|
+
export declare function capitalize(str: string): string;
|
|
9
|
+
interface GenerateFnNameOptions {
|
|
10
|
+
/** Segments to strip out (e.g. ['api','v1']) */
|
|
11
|
+
ignoreSegments?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* generateFnName('GET', '/users') // "listUsers"
|
|
18
|
+
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
19
|
+
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
20
|
+
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateFnName(method: HttpMethod, rawPath: string, opts?: GenerateFnNameOptions): string;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const VERB_MAP = {
|
|
2
|
+
GET: { noParams: 'list', withParams: 'get' },
|
|
3
|
+
POST: { default: 'create' },
|
|
4
|
+
PUT: { default: 'update' },
|
|
5
|
+
PATCH: { default: 'patch' },
|
|
6
|
+
DELETE: { default: 'delete' },
|
|
7
|
+
HEAD: { default: 'head' },
|
|
8
|
+
OPTIONS: { default: 'options' },
|
|
9
|
+
};
|
|
10
|
+
export function capitalize(str) {
|
|
11
|
+
if (str.length === 0)
|
|
12
|
+
return '';
|
|
13
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_OPTIONS = {
|
|
16
|
+
ignoreSegments: ['api'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
20
|
+
*
|
|
21
|
+
* Examples:
|
|
22
|
+
* generateFnName('GET', '/users') // "listUsers"
|
|
23
|
+
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
24
|
+
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
25
|
+
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
26
|
+
*/
|
|
27
|
+
export function generateFnName(method, rawPath, opts = {}) {
|
|
28
|
+
const { ignoreSegments } = {
|
|
29
|
+
...DEFAULT_OPTIONS,
|
|
30
|
+
...opts,
|
|
31
|
+
};
|
|
32
|
+
// 1. Clean & split path
|
|
33
|
+
const parts = rawPath
|
|
34
|
+
.replace(/^\/|\/$/g, '') // strip leading/trailing slash
|
|
35
|
+
.split('/')
|
|
36
|
+
.filter((seg) => !ignoreSegments?.includes(seg.toLowerCase()))
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
// 2. Separate resource tokens from path-params
|
|
39
|
+
const resources = [];
|
|
40
|
+
const params = [];
|
|
41
|
+
parts.forEach((seg) => {
|
|
42
|
+
const match = seg.match(/^{?([^}]+)}?$/);
|
|
43
|
+
if (match) {
|
|
44
|
+
params.push(match[1]);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
resources.push(seg);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// 3. Pick base verb from VERB_MAP
|
|
51
|
+
let baseVerb;
|
|
52
|
+
if (method === 'GET') {
|
|
53
|
+
baseVerb = params.length ? VERB_MAP.GET.withParams : VERB_MAP.GET.noParams;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
baseVerb = VERB_MAP[method].default;
|
|
57
|
+
}
|
|
58
|
+
// 4. Build the “resource” part
|
|
59
|
+
const resourcePart = resources.map(capitalize).join('');
|
|
60
|
+
// 5. Build the “ByParam” suffix
|
|
61
|
+
const byParams = params.length ? 'By' + params.map(capitalize).join('') : '';
|
|
62
|
+
// 6. Combine and ensure camelCase
|
|
63
|
+
const rawName = `${baseVerb}${resourcePart}${byParams}`;
|
|
64
|
+
return rawName[0].toLowerCase() + rawName.slice(1);
|
|
65
|
+
}
|
|
66
|
+
/*
|
|
67
|
+
// --- Example usage ---
|
|
68
|
+
console.log(generateFnName('GET', '/users')); // listUsers
|
|
69
|
+
console.log(generateFnName('GET', '/users/{id}')); // getUsersById
|
|
70
|
+
console.log(generateFnName('POST', '/users')); // createUsers
|
|
71
|
+
console.log(generateFnName('PATCH', '/users/{userId}/profile')); // patchUsersProfileByUserId
|
|
72
|
+
console.log(generateFnName('DELETE', '/v1/api/orders/{orderId}')); // deleteOrdersByOrderId
|
|
73
|
+
|
|
74
|
+
// You can also enable singularization:
|
|
75
|
+
console.log(generateFnName('GET', '/users/{userId}/orders', { singularizeResources: true })); // getUserOrderByUserId
|
|
76
|
+
*/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HttpMethod, VovkOperationObject, VovkStrictConfig, type VovkConfig } from 'vovk';
|
|
2
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
3
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
4
|
+
export type GetOpenAPINameFn = (config: {
|
|
5
|
+
operationObject: VovkOperationObject;
|
|
6
|
+
method: HttpMethod;
|
|
7
|
+
path: string;
|
|
8
|
+
openAPIObject: OpenAPIObject;
|
|
9
|
+
}) => string;
|
|
10
|
+
export declare function normalizeOpenAPIMixin({ mixinModule, log, cwd, }: {
|
|
11
|
+
mixinModule: NonNullable<NonNullable<NonNullable<NonNullable<VovkConfig['projectConfig']>['segments']>[string]>['openAPIMixin']>;
|
|
12
|
+
log: ProjectInfo['log'];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}): Promise<NonNullable<NonNullable<NonNullable<VovkStrictConfig['projectConfig']>['segments']>[string]>['openAPIMixin']>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as YAML from 'yaml';
|
|
4
|
+
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
5
|
+
import camelCase from 'lodash/camelCase.js';
|
|
6
|
+
import { generateFnName } from './generateFnName.mjs';
|
|
7
|
+
const getNamesNestJS = (operationObject) => {
|
|
8
|
+
const operationId = operationObject.operationId;
|
|
9
|
+
if (!operationId) {
|
|
10
|
+
throw new Error('Operation ID is required for NestJS module name generation');
|
|
11
|
+
}
|
|
12
|
+
const controllerHandlerMatch = operationId?.match(/^([A-Z][a-zA-Z0-9]*)_([a-zA-Z0-9_]+)/);
|
|
13
|
+
if (!controllerHandlerMatch) {
|
|
14
|
+
throw new Error(`Invalid operationId format for NestJS: ${operationId}`);
|
|
15
|
+
}
|
|
16
|
+
const [controllerName, handlerName] = controllerHandlerMatch.slice(1, 3);
|
|
17
|
+
return [controllerName.replace(/Controller$/, 'RPC'), handlerName];
|
|
18
|
+
};
|
|
19
|
+
const normalizeGetModuleName = (getModuleName) => {
|
|
20
|
+
if (getModuleName === 'nestjs-operation-id') {
|
|
21
|
+
getModuleName = ({ operationObject }) => getNamesNestJS(operationObject)[0];
|
|
22
|
+
}
|
|
23
|
+
else if (typeof getModuleName === 'string') {
|
|
24
|
+
const moduleName = getModuleName;
|
|
25
|
+
getModuleName = () => moduleName;
|
|
26
|
+
}
|
|
27
|
+
else if (typeof getModuleName !== 'function') {
|
|
28
|
+
throw new Error('getModuleName must be a function or one of the predefined strings');
|
|
29
|
+
}
|
|
30
|
+
return getModuleName;
|
|
31
|
+
};
|
|
32
|
+
const normalizeGetMethodName = (getMethodName) => {
|
|
33
|
+
if (getMethodName === 'nestjs-operation-id') {
|
|
34
|
+
getMethodName = ({ operationObject }) => getNamesNestJS(operationObject)[1];
|
|
35
|
+
}
|
|
36
|
+
else if (getMethodName === 'camel-case-operation-id') {
|
|
37
|
+
getMethodName = ({ operationObject }) => {
|
|
38
|
+
const operationId = operationObject.operationId;
|
|
39
|
+
if (!operationId) {
|
|
40
|
+
throw new Error('Operation ID is required for camel-case method name generation');
|
|
41
|
+
}
|
|
42
|
+
return camelCase(operationId);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else if (getMethodName === 'auto') {
|
|
46
|
+
getMethodName = ({ operationObject, method, path }) => {
|
|
47
|
+
const operationId = operationObject.operationId;
|
|
48
|
+
const isCamelCase = operationId && /^[a-z][a-zA-Z0-9]*$/.test(operationId);
|
|
49
|
+
const isSnakeCase = operationId && /^[a-z][a-z0-9_]+$/.test(operationId);
|
|
50
|
+
return isCamelCase ? operationId : isSnakeCase ? camelCase(operationId) : generateFnName(method, path);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else if (typeof getMethodName !== 'function') {
|
|
54
|
+
throw new Error('getMethodName must be a function or one of the predefined strings');
|
|
55
|
+
}
|
|
56
|
+
return getMethodName;
|
|
57
|
+
};
|
|
58
|
+
async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
59
|
+
const openApiSpecAbsolutePath = path.resolve(cwd, openApiSpecFilePath);
|
|
60
|
+
const fileName = path.basename(openApiSpecAbsolutePath);
|
|
61
|
+
if (!fileName.endsWith('.json') && !fileName.endsWith('.yaml') && !fileName.endsWith('.yml')) {
|
|
62
|
+
throw new Error(`Invalid OpenAPI spec file format: ${fileName}. Please provide a JSON or YAML file.`);
|
|
63
|
+
}
|
|
64
|
+
const openApiSpecContent = await fs.readFile(openApiSpecAbsolutePath, 'utf8');
|
|
65
|
+
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
66
|
+
}
|
|
67
|
+
async function getOpenApiSpecRemote({ cwd, url, fallback, log, }) {
|
|
68
|
+
const resp = await fetch(url);
|
|
69
|
+
const text = await resp.text();
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
if (fallback) {
|
|
72
|
+
log.warn(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}. Falling back to ${chalkHighlightThing(fallback)}`);
|
|
73
|
+
return getOpenApiSpecLocal(fallback, cwd);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
if (fallback) {
|
|
78
|
+
const fallbackAbsolutePath = path.resolve(cwd, fallback);
|
|
79
|
+
const existingFallback = await fs.readFile(fallbackAbsolutePath, 'utf8').catch(() => null);
|
|
80
|
+
if (existingFallback !== text) {
|
|
81
|
+
await fs.mkdir(path.dirname(fallbackAbsolutePath), { recursive: true });
|
|
82
|
+
await fs.writeFile(fallbackAbsolutePath, text);
|
|
83
|
+
log.info(`Saved OpenAPI spec to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
log.debug(`OpenAPI spec at ${chalkHighlightThing(url)} is unchanged. Skipping write to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return (text.trim().startsWith('{') || text.trim().startsWith('[') ? JSON.parse(text) : YAML.parse(text));
|
|
90
|
+
}
|
|
91
|
+
export async function normalizeOpenAPIMixin({
|
|
92
|
+
// mixinName,
|
|
93
|
+
mixinModule, log, cwd = process.cwd(), }) {
|
|
94
|
+
const { source, getModuleName, getMethodName } = mixinModule;
|
|
95
|
+
let openAPIObject;
|
|
96
|
+
if ('url' in source) {
|
|
97
|
+
openAPIObject = await getOpenApiSpecRemote({ url: source.url, fallback: source.fallback, log, cwd });
|
|
98
|
+
}
|
|
99
|
+
else if ('file' in source) {
|
|
100
|
+
openAPIObject = await getOpenApiSpecLocal(source.file, cwd);
|
|
101
|
+
}
|
|
102
|
+
else if ('object' in source) {
|
|
103
|
+
openAPIObject = source.object;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new Error('Invalid source type for OpenAPI configuration');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...mixinModule,
|
|
110
|
+
source: { object: openAPIObject },
|
|
111
|
+
getModuleName: normalizeGetModuleName(getModuleName),
|
|
112
|
+
getMethodName: normalizeGetMethodName(getMethodName),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<% const vars = {
|
|
2
|
+
ModuleName: t.TheThing + 'Controller',
|
|
3
|
+
ServiceName: t.TheThing + 'Service',
|
|
4
|
+
}; %>
|
|
5
|
+
---
|
|
6
|
+
dir: <%= t.defaultDir %>
|
|
7
|
+
fileName: <%= vars.ModuleName + '.ts' %>
|
|
8
|
+
sourceName: <%= vars.ModuleName %>
|
|
9
|
+
compiledName: <%= t.TheThing + 'RPC' %>
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
import { prefix, get, put, post, del, openapi } from 'vovk';
|
|
13
|
+
import { type } from 'arktype';
|
|
14
|
+
import withArk from '@/lib/withArk<%= t.nodeNextResolutionExt.ts %>';
|
|
15
|
+
<% if(t.withService) { %>
|
|
16
|
+
import <%= vars.ServiceName %> from './<%= vars.ServiceName %><%= t.nodeNextResolutionExt.ts %>';
|
|
17
|
+
<% } %>
|
|
18
|
+
|
|
19
|
+
@prefix('<%= t['the-things'] %>')
|
|
20
|
+
export default class <%= vars.ModuleName %> {
|
|
21
|
+
@openapi({
|
|
22
|
+
summary: 'Get <%= t.TheThings %>',
|
|
23
|
+
})
|
|
24
|
+
@get()
|
|
25
|
+
static get<%= t.TheThings %> = withArk({
|
|
26
|
+
query: type({ search: type('string') }),
|
|
27
|
+
handle(req) {
|
|
28
|
+
const search = req.nextUrl.searchParams.get('search');
|
|
29
|
+
<% if(t.withService) { %>
|
|
30
|
+
return <%= vars.ServiceName %>.get<%= t.TheThings %>(search);
|
|
31
|
+
<% } else { %>
|
|
32
|
+
return { results: [], search };
|
|
33
|
+
<% } %>
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
@openapi({
|
|
38
|
+
summary: 'Update <%= t.TheThing %>',
|
|
39
|
+
})
|
|
40
|
+
@put('{id}')
|
|
41
|
+
static update<%= t.TheThing %> = withArk({
|
|
42
|
+
body: type({
|
|
43
|
+
foo: type('"bar" | "baz"'),
|
|
44
|
+
}),
|
|
45
|
+
query: type({ q: type('string') }),
|
|
46
|
+
params: type({ id: type('string') }),
|
|
47
|
+
async handle(req, params) {
|
|
48
|
+
const { id } = params;
|
|
49
|
+
const body = await req.json();
|
|
50
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
51
|
+
<% if(t.withService) { %>
|
|
52
|
+
return <%= vars.ServiceName %>.update<%= t.TheThing %>(id, q, body);
|
|
53
|
+
<% } else { %>
|
|
54
|
+
return { id, body, q };
|
|
55
|
+
<% } %>
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
@post()
|
|
60
|
+
static create<%= t.TheThing %> = () => {
|
|
61
|
+
// ...
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
@del(':id')
|
|
65
|
+
static delete<%= t.TheThing %> = () => {
|
|
66
|
+
// ...
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<% const vars = {
|
|
2
|
+
ModuleName: t.TheThing + 'Controller',
|
|
3
|
+
ServiceName: t.TheThing + 'Service',
|
|
4
|
+
}; %>
|
|
5
|
+
---
|
|
6
|
+
dir: <%= t.defaultDir %>
|
|
7
|
+
fileName: <%= vars.ModuleName + '.ts' %>
|
|
8
|
+
sourceName: <%= vars.ModuleName %>
|
|
9
|
+
compiledName: <%= t.TheThing + 'RPC' %>
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
import { prefix, get, put, post, del, openapi } from 'vovk';
|
|
13
|
+
import * as v from 'valibot';
|
|
14
|
+
import withValibot from '@/lib/withValibot<%= t.nodeNextResolutionExt.ts %>';
|
|
15
|
+
<% if(t.withService) { %>
|
|
16
|
+
import <%= vars.ServiceName %> from './<%= vars.ServiceName %><%= t.nodeNextResolutionExt.ts %>';
|
|
17
|
+
<% } %>
|
|
18
|
+
|
|
19
|
+
@prefix('<%= t['the-things'] %>')
|
|
20
|
+
export default class <%= vars.ModuleName %> {
|
|
21
|
+
@openapi({
|
|
22
|
+
summary: 'Get <%= t.TheThings %>',
|
|
23
|
+
})
|
|
24
|
+
@get()
|
|
25
|
+
static get<%= t.TheThings %> = withValibot({
|
|
26
|
+
query: v.object({ search: v.string() }),
|
|
27
|
+
handle(req) {
|
|
28
|
+
const search = req.nextUrl.searchParams.get('search');
|
|
29
|
+
<% if(t.withService) { %>
|
|
30
|
+
return <%= vars.ServiceName %>.get<%= t.TheThings %>(search);
|
|
31
|
+
<% } else { %>
|
|
32
|
+
return { results: [], search };
|
|
33
|
+
<% } %>
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
@openapi({
|
|
38
|
+
summary: 'Update <%= t.TheThing %>',
|
|
39
|
+
})
|
|
40
|
+
@put('{id}')
|
|
41
|
+
static update<%= t.TheThing %> = withValibot({
|
|
42
|
+
body: v.object({
|
|
43
|
+
foo: v.union([v.literal('bar'), v.literal('baz')]),
|
|
44
|
+
}),
|
|
45
|
+
query: v.object({ q: v.string() }),
|
|
46
|
+
params: v.object({ id: v.string() }),
|
|
47
|
+
async handle(req, params) {
|
|
48
|
+
const { id } = params;
|
|
49
|
+
const body = await req.json();
|
|
50
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
51
|
+
<% if(t.withService) { %>
|
|
52
|
+
return <%= vars.ServiceName %>.update<%= t.TheThing %>(id, q, body);
|
|
53
|
+
<% } else { %>
|
|
54
|
+
return { id, body, q };
|
|
55
|
+
<% } %>
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
@post()
|
|
60
|
+
static create<%= t.TheThing %> = () => {
|
|
61
|
+
// ...
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
@del(':id')
|
|
65
|
+
static delete<%= t.TheThing %> = () => {
|
|
66
|
+
// ...
|
|
67
|
+
};
|
|
68
|
+
}
|
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.335",
|
|
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.397"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
41
|
"@rolldown/binding-linux-x64-gnu": "1.0.0-beta.31"
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { PackageJson } from 'type-fest';
|
|
2
|
-
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
-
export default function mergePackages({ rootPackageJson, packages, }: {
|
|
4
|
-
rootPackageJson: PackageJson;
|
|
5
|
-
packages: (PackageJson | undefined)[];
|
|
6
|
-
log: ProjectInfo['log'];
|
|
7
|
-
}): Promise<PackageJson>;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import pick from 'lodash/pick.js';
|
|
2
|
-
function mergeTwoPackageJsons(base, additional) {
|
|
3
|
-
const merged = { ...base, ...additional };
|
|
4
|
-
// TODO: Add deep merge for all properties
|
|
5
|
-
if (base.scripts || additional.scripts) {
|
|
6
|
-
merged.scripts = { ...base.scripts, ...additional.scripts };
|
|
7
|
-
}
|
|
8
|
-
if (base.dependencies || additional.dependencies) {
|
|
9
|
-
merged.dependencies = { ...base.dependencies, ...additional.dependencies };
|
|
10
|
-
}
|
|
11
|
-
if (base.devDependencies || additional.devDependencies) {
|
|
12
|
-
merged.devDependencies = { ...base.devDependencies, ...additional.devDependencies };
|
|
13
|
-
}
|
|
14
|
-
if (base.peerDependencies || additional.peerDependencies) {
|
|
15
|
-
merged.peerDependencies = { ...base.peerDependencies, ...additional.peerDependencies };
|
|
16
|
-
}
|
|
17
|
-
return merged;
|
|
18
|
-
}
|
|
19
|
-
export default async function mergePackages({ rootPackageJson, packages, }) {
|
|
20
|
-
const defaultPackageJson = {
|
|
21
|
-
main: './index.cjs',
|
|
22
|
-
module: './index.mjs',
|
|
23
|
-
types: './index.d.mts',
|
|
24
|
-
exports: {
|
|
25
|
-
'.': {
|
|
26
|
-
import: './index.mjs',
|
|
27
|
-
require: './index.cjs',
|
|
28
|
-
},
|
|
29
|
-
'./schema': {
|
|
30
|
-
import: './schema.cjs',
|
|
31
|
-
require: './schema.cjs',
|
|
32
|
-
types: './schema.d.cts',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
const pickedPackageJson = pick(rootPackageJson, [
|
|
37
|
-
'name',
|
|
38
|
-
'version',
|
|
39
|
-
'description',
|
|
40
|
-
'author',
|
|
41
|
-
'contributors',
|
|
42
|
-
'license',
|
|
43
|
-
'repository',
|
|
44
|
-
'homepage',
|
|
45
|
-
'bugs',
|
|
46
|
-
'keywords',
|
|
47
|
-
]);
|
|
48
|
-
let result = { ...pickedPackageJson, ...defaultPackageJson };
|
|
49
|
-
for (const pkg of packages) {
|
|
50
|
-
if (pkg) {
|
|
51
|
-
result = mergeTwoPackageJsons(result, pkg);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { VovkStrictConfig, type VovkConfig } from 'vovk';
|
|
2
|
-
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
3
|
-
export declare function normalizeOpenAPIMixins({ mixinModules, log, cwd, }: {
|
|
4
|
-
mixinModules: NonNullable<VovkConfig['openApiMixins']>;
|
|
5
|
-
log: ProjectInfo['log'];
|
|
6
|
-
cwd?: string;
|
|
7
|
-
}): Promise<VovkStrictConfig['openApiMixins']>;
|