vovk-cli 0.0.1-beta.18 → 0.0.1-beta.19
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/generateClient.mjs +30 -28
- package/dist/getProjectInfo/getConfig.d.mts +1 -1
- package/dist/getProjectInfo/getConfig.mjs +8 -0
- package/dist/getProjectInfo/index.d.mts +2 -1
- package/dist/getProjectInfo/index.mjs +4 -0
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +21 -8
- package/dist/init/installDependencies.mjs +5 -5
- package/dist/new/addClassToSegmentCode.d.mts +6 -0
- package/dist/new/addClassToSegmentCode.mjs +32 -0
- package/dist/{generate/addCommonTerms.js → new/addCommonTerms.mjs} +1 -0
- package/dist/new/index.d.mts +2 -0
- package/dist/new/index.mjs +24 -0
- package/dist/new/newModule.d.mts +5 -0
- package/dist/new/newModule.mjs +89 -0
- package/dist/new/newSegment.d.mts +4 -0
- package/dist/new/newSegment.mjs +33 -0
- package/dist/new/render.d.mts +12 -0
- package/dist/new/render.mjs +28 -0
- package/dist/types.d.mts +7 -0
- package/dist/utils/chalkHighlightThing.mjs +1 -1
- package/dist/utils/formatLoggedSegmentName.d.mts +4 -1
- package/dist/utils/formatLoggedSegmentName.mjs +4 -2
- package/dist/utils/prettify.d.mts +1 -0
- package/dist/utils/prettify.mjs +10 -0
- package/dist/watcher/ensureSchemaFiles.d.mts +1 -1
- package/dist/watcher/ensureSchemaFiles.mjs +16 -16
- package/dist/watcher/index.mjs +25 -27
- package/dist/watcher/logDiffResult.mjs +9 -9
- package/dist/watcher/writeOneSchemaFile.d.mts +2 -2
- package/dist/watcher/writeOneSchemaFile.mjs +2 -2
- package/package.json +7 -2
- package/templates/controller.ejs +85 -0
- package/templates/service.ejs +9 -0
- package/templates/worker.ejs +9 -0
- package/templates/zod/MyThingController.c.only.template.ts +32 -0
- package/templates/zod/MyThingController.c.template.ts +39 -0
- package/templates/zod/MyThingService.s.template.ts +18 -0
- package/dist/generate/replaceOccurrences.d.mts +0 -1
- package/dist/generate/replaceOccurrences.mjs +0 -49
- /package/dist/{generate/addCommonTerms.d.ts → new/addCommonTerms.d.mts} +0 -0
package/dist/generateClient.mjs
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
|
|
4
|
+
import prettify from './utils/prettify.mjs';
|
|
4
5
|
export default async function generateClient(projectInfo, segments, segmentsSchema) {
|
|
6
|
+
const { config, cwd, log, validateOnClientImportPath, apiEntryPoint, fetcherClientImportPath, schemaOutImportPath } = projectInfo;
|
|
5
7
|
const now = Date.now();
|
|
6
|
-
const
|
|
7
|
-
const validateFullPath = projectInfo.config.validateOnClient?.startsWith('.')
|
|
8
|
-
? path.join(projectInfo.cwd, projectInfo.config.validateOnClient)
|
|
9
|
-
: projectInfo.config.validateOnClient;
|
|
8
|
+
const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
|
|
10
9
|
let dts = `// auto-generated
|
|
11
10
|
/* eslint-disable */
|
|
12
11
|
import type { clientizeController } from 'vovk/client';
|
|
13
12
|
import type { promisifyWorker } from 'vovk/worker';
|
|
14
13
|
import type { VovkClientFetcher } from 'vovk/client';
|
|
15
|
-
import type fetcher from '${
|
|
14
|
+
import type fetcher from '${fetcherClientImportPath}';
|
|
16
15
|
|
|
17
16
|
`;
|
|
18
17
|
let js = `// auto-generated
|
|
19
18
|
/* eslint-disable */
|
|
20
19
|
const { clientizeController } = require('vovk/client');
|
|
21
20
|
const { promisifyWorker } = require('vovk/worker');
|
|
22
|
-
const { default: fetcher } = require('${
|
|
23
|
-
const schema = require('${
|
|
21
|
+
const { default: fetcher } = require('${fetcherClientImportPath}');
|
|
22
|
+
const schema = require('${schemaOutImportPath}');
|
|
24
23
|
`;
|
|
25
24
|
let ts = `// auto-generated
|
|
26
25
|
/* eslint-disable */
|
|
27
26
|
import { clientizeController } from 'vovk/client';
|
|
28
27
|
import { promisifyWorker } from 'vovk/worker';
|
|
29
28
|
import type { VovkClientFetcher } from 'vovk/client';
|
|
30
|
-
import fetcher from '${
|
|
31
|
-
import schema from '${
|
|
29
|
+
import fetcher from '${fetcherClientImportPath}';
|
|
30
|
+
import schema from '${schemaOutImportPath}';
|
|
32
31
|
|
|
33
32
|
`;
|
|
34
33
|
for (let i = 0; i < segments.length; i++) {
|
|
@@ -39,7 +38,7 @@ import schema from '${projectInfo.schemaOutImportPath}';
|
|
|
39
38
|
}
|
|
40
39
|
if (!schema.emitSchema)
|
|
41
40
|
continue;
|
|
42
|
-
const importRouteFilePath = path.relative(
|
|
41
|
+
const importRouteFilePath = path.relative(config.clientOutDir, routeFilePath);
|
|
43
42
|
dts += `import type { Controllers as Controllers${i}, Workers as Workers${i} } from "${importRouteFilePath}";\n`;
|
|
44
43
|
ts += `import type { Controllers as Controllers${i}, Workers as Workers${i} } from "${importRouteFilePath}";\n`;
|
|
45
44
|
}
|
|
@@ -47,13 +46,13 @@ import schema from '${projectInfo.schemaOutImportPath}';
|
|
|
47
46
|
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
48
47
|
`;
|
|
49
48
|
ts += `
|
|
50
|
-
${
|
|
49
|
+
${validateOnClientImportPath ? `import validateOnClient from '${validateOnClientImportPath}';\n` : '\nconst validateOnClient = undefined;'}
|
|
51
50
|
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
52
|
-
const prefix = '${
|
|
51
|
+
const prefix = '${apiEntryPoint}';
|
|
53
52
|
`;
|
|
54
53
|
js += `
|
|
55
|
-
const { default: validateOnClient = null } = ${
|
|
56
|
-
const prefix = '${
|
|
54
|
+
const { default: validateOnClient = null } = ${validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'};
|
|
55
|
+
const prefix = '${apiEntryPoint}';
|
|
57
56
|
`;
|
|
58
57
|
for (let i = 0; i < segments.length; i++) {
|
|
59
58
|
const { segmentName } = segments[i];
|
|
@@ -74,20 +73,23 @@ const prefix = '${projectInfo.apiEntryPoint}';
|
|
|
74
73
|
ts += `export const ${key} = promisifyWorker<Workers${i}["${key}"]>(null, schema['${segmentName}'].workers.${key});\n`;
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const existingJs = await fs.readFile(
|
|
81
|
-
const existingDts = await fs.readFile(
|
|
82
|
-
const existingTs = await fs.readFile(
|
|
76
|
+
const localJsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.js');
|
|
77
|
+
const localDtsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'client.d.ts');
|
|
78
|
+
const localTsAbsolutePath = path.join(clientoOutDirAbsolutePath, 'index.ts');
|
|
79
|
+
const existingJs = await fs.readFile(localJsAbsolutePath, 'utf-8').catch(() => '');
|
|
80
|
+
const existingDts = await fs.readFile(localDtsAbsolutePath, 'utf-8').catch(() => '');
|
|
81
|
+
const existingTs = await fs.readFile(localTsAbsolutePath, 'utf-8').catch(() => '');
|
|
82
|
+
js = await prettify(js, localJsAbsolutePath);
|
|
83
|
+
dts = await prettify(dts, localDtsAbsolutePath);
|
|
84
|
+
ts = await prettify(ts, localTsAbsolutePath);
|
|
83
85
|
if (existingJs === js && existingDts === dts && existingTs === ts) {
|
|
84
|
-
|
|
85
|
-
return { written: false, path:
|
|
86
|
+
log.info(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
|
|
87
|
+
return { written: false, path: clientoOutDirAbsolutePath };
|
|
86
88
|
}
|
|
87
|
-
await fs.mkdir(
|
|
88
|
-
await fs.writeFile(
|
|
89
|
-
await fs.writeFile(
|
|
90
|
-
await fs.writeFile(
|
|
91
|
-
|
|
92
|
-
return { written: true, path:
|
|
89
|
+
await fs.mkdir(clientoOutDirAbsolutePath, { recursive: true });
|
|
90
|
+
await fs.writeFile(localJsAbsolutePath, js);
|
|
91
|
+
await fs.writeFile(localDtsAbsolutePath, dts);
|
|
92
|
+
await fs.writeFile(localTsAbsolutePath, ts);
|
|
93
|
+
log.info(`Client generated in ${Date.now() - now}ms`);
|
|
94
|
+
return { written: true, path: clientoOutDirAbsolutePath };
|
|
93
95
|
}
|
|
@@ -15,6 +15,14 @@ export default async function getConfig({ clientOutDir }) {
|
|
|
15
15
|
rootEntry: env.VOVK_ROOT_ENTRY ?? userConfig.rootEntry ?? 'api',
|
|
16
16
|
rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? userConfig.rootSegmentModulesDirName ?? '',
|
|
17
17
|
logLevel: env.VOVK_LOG_LEVEL ?? userConfig.logLevel ?? 'debug', // TODO: change to 'warn' when v3 is ready
|
|
18
|
+
custom: userConfig.custom ?? {},
|
|
19
|
+
templates: {
|
|
20
|
+
service: 'vovk-cli/templates/service.ejs',
|
|
21
|
+
controller: 'vovk-cli/templates/controller.ejs',
|
|
22
|
+
worker: 'vovk-cli/templates/worker.ejs',
|
|
23
|
+
...userConfig.templates,
|
|
24
|
+
},
|
|
18
25
|
};
|
|
26
|
+
// forceAppDir is used for testing purposes
|
|
19
27
|
return { config, srcRoot };
|
|
20
28
|
}
|
|
@@ -10,7 +10,8 @@ export default function getProjectInfo({ port: givenPort, clientOutDir, }?: {
|
|
|
10
10
|
srcRoot: string;
|
|
11
11
|
schemaOutImportPath: string;
|
|
12
12
|
fetcherClientImportPath: string;
|
|
13
|
-
|
|
13
|
+
validateOnClientImportPath: string | null;
|
|
14
|
+
config: Required<Omit<import("../types.mjs").VovkConfig, "_devForceAppDir">>;
|
|
14
15
|
log: {
|
|
15
16
|
info: (msg: string) => void;
|
|
16
17
|
warn: (msg: string) => void;
|
|
@@ -13,6 +13,9 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, }
|
|
|
13
13
|
const fetcherClientImportPath = config.fetcher.startsWith('.')
|
|
14
14
|
? path.relative(config.clientOutDir, config.fetcher)
|
|
15
15
|
: config.fetcher;
|
|
16
|
+
const validateOnClientImportPath = config.validateOnClient?.startsWith('.')
|
|
17
|
+
? path.relative(config.clientOutDir, config.validateOnClient)
|
|
18
|
+
: config.validateOnClient;
|
|
16
19
|
const log = getLogger(config.logLevel);
|
|
17
20
|
return {
|
|
18
21
|
cwd,
|
|
@@ -22,6 +25,7 @@ export default async function getProjectInfo({ port: givenPort, clientOutDir, }
|
|
|
22
25
|
srcRoot,
|
|
23
26
|
schemaOutImportPath,
|
|
24
27
|
fetcherClientImportPath,
|
|
28
|
+
validateOnClientImportPath,
|
|
25
29
|
config,
|
|
26
30
|
log,
|
|
27
31
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VovkConfig, VovkEnv } from './types.mjs';
|
|
3
2
|
import type { LogLevelNames } from 'loglevel';
|
|
3
|
+
import type { VovkConfig, VovkEnv } from './types.mjs';
|
|
4
4
|
export type { VovkConfig, VovkEnv };
|
|
5
5
|
export interface InitOptions {
|
|
6
6
|
yes: boolean;
|
|
7
7
|
logLevel: LogLevelNames;
|
|
8
8
|
}
|
|
9
|
+
export interface NewOptions {
|
|
10
|
+
dryRun: boolean;
|
|
11
|
+
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import path from 'path';
|
|
2
3
|
import { Command } from 'commander';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
3
5
|
import concurrently from 'concurrently';
|
|
4
6
|
import getAvailablePort from './utils/getAvailablePort.mjs';
|
|
5
|
-
import { VovkCLIWatcher } from './watcher/index.mjs';
|
|
6
7
|
import getProjectInfo from './getProjectInfo/index.mjs';
|
|
7
8
|
import generateClient from './generateClient.mjs';
|
|
8
9
|
import locateSegments from './locateSegments.mjs';
|
|
9
|
-
import
|
|
10
|
-
import { readFileSync } from 'fs';
|
|
10
|
+
import { VovkCLIWatcher } from './watcher/index.mjs';
|
|
11
11
|
import { Init } from './init/index.mjs';
|
|
12
|
+
import newComponents from './new/index.mjs';
|
|
12
13
|
const program = new Command();
|
|
13
14
|
const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
|
|
14
15
|
program.name('vovk').description('Vovk CLI').version(packageJSON.version);
|
|
@@ -66,8 +67,8 @@ program
|
|
|
66
67
|
const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
|
|
67
68
|
const { cwd, config, apiDir } = projectInfo;
|
|
68
69
|
const segments = await locateSegments(apiDir);
|
|
69
|
-
const
|
|
70
|
-
const schema = (await import(path.join(
|
|
70
|
+
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
71
|
+
const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
|
|
71
72
|
await generateClient(projectInfo, segments, schema.default);
|
|
72
73
|
});
|
|
73
74
|
program
|
|
@@ -75,13 +76,25 @@ program
|
|
|
75
76
|
.description('Initialize Vovk project')
|
|
76
77
|
.option('-Y, --yes', 'Skip all prompts and use default values')
|
|
77
78
|
.option('--log-level <level>', 'Set log level', 'info')
|
|
78
|
-
.action(
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
.action((prefix = '.', options) => Init.main(prefix, options));
|
|
80
|
+
program
|
|
81
|
+
.command('new [components...]')
|
|
82
|
+
.alias('n')
|
|
83
|
+
.description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
|
|
84
|
+
.option('--dry-run', 'Do not write files to disk')
|
|
85
|
+
.action((components, options) => newComponents(components, options));
|
|
81
86
|
program
|
|
82
87
|
.command('help')
|
|
83
88
|
.description('Show help message')
|
|
84
89
|
.action(() => program.help());
|
|
90
|
+
/*
|
|
91
|
+
vovk new segment [segmentName]
|
|
92
|
+
vovk new controller service [segmentName/]moduleName
|
|
93
|
+
vovk new c s w [segmentName/]moduleName
|
|
94
|
+
|
|
95
|
+
vovk c s w userApi/user
|
|
96
|
+
vovk new c s w user
|
|
97
|
+
*/
|
|
85
98
|
program.parse(process.argv);
|
|
86
99
|
if (!process.argv.slice(2).length) {
|
|
87
100
|
program.outputHelp();
|
|
@@ -3,19 +3,19 @@ import { promisify } from 'util';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
const execPromise = promisify(exec);
|
|
5
5
|
export default async function installDependencies(installDir, dependencies, devDependencies) {
|
|
6
|
-
const
|
|
6
|
+
const absolutePath = path.resolve(installDir);
|
|
7
7
|
try {
|
|
8
8
|
if (dependencies.length > 0) {
|
|
9
|
-
console.log(`Installing dependencies in ${
|
|
10
|
-
const { stdout, stderr } = await execPromise(`npm install ${dependencies.join(' ')} --prefix ${
|
|
9
|
+
console.log(`Installing dependencies in ${absolutePath}...`);
|
|
10
|
+
const { stdout, stderr } = await execPromise(`npm install ${dependencies.join(' ')} --prefix ${absolutePath}`);
|
|
11
11
|
console.log(stdout);
|
|
12
12
|
if (stderr) {
|
|
13
13
|
console.error(stderr);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
if (devDependencies.length > 0) {
|
|
17
|
-
console.log(`Installing dev dependencies in ${
|
|
18
|
-
const { stdout, stderr } = await execPromise(`npm install --save-dev ${devDependencies.join(' ')} --prefix ${
|
|
17
|
+
console.log(`Installing dev dependencies in ${absolutePath}...`);
|
|
18
|
+
const { stdout, stderr } = await execPromise(`npm install --save-dev ${devDependencies.join(' ')} --prefix ${absolutePath}`);
|
|
19
19
|
console.log(stdout);
|
|
20
20
|
if (stderr) {
|
|
21
21
|
console.error(stderr);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Project, SyntaxKind } from 'ts-morph';
|
|
2
|
+
export default function addClassToSegmentCode(segmentSourceCode, { className, rpcName, type, importPath, }) {
|
|
3
|
+
const project = new Project();
|
|
4
|
+
const sourceFile = project.createSourceFile('route.ts', segmentSourceCode, { overwrite: true });
|
|
5
|
+
// Add the import if it doesn't exist
|
|
6
|
+
let importDeclaration = sourceFile.getImportDeclaration((imp) => {
|
|
7
|
+
return imp.getModuleSpecifierValue() === importPath;
|
|
8
|
+
});
|
|
9
|
+
if (!importDeclaration) {
|
|
10
|
+
importDeclaration = sourceFile.addImportDeclaration({
|
|
11
|
+
defaultImport: className,
|
|
12
|
+
moduleSpecifier: importPath,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
// Get the variable declaration for controllers or workers
|
|
16
|
+
const variableDeclaration = sourceFile.getVariableDeclaration(`${type}s`);
|
|
17
|
+
if (variableDeclaration) {
|
|
18
|
+
const initializer = variableDeclaration.getInitializer();
|
|
19
|
+
if (initializer && initializer.getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
20
|
+
const objectLiteral = initializer.asKindOrThrow(SyntaxKind.ObjectLiteralExpression);
|
|
21
|
+
// Check if the property already exists
|
|
22
|
+
const existingProperty = objectLiteral.getProperty(rpcName);
|
|
23
|
+
if (!existingProperty) {
|
|
24
|
+
objectLiteral.addPropertyAssignment({
|
|
25
|
+
name: rpcName,
|
|
26
|
+
initializer: className,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return sourceFile.getFullText();
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import newModule from './newModule.mjs';
|
|
2
|
+
import newSegment from './newSegment.mjs';
|
|
3
|
+
export default async function newComponents(components, options) {
|
|
4
|
+
if (components[0] === 'segment' || components[0] === 'segments') {
|
|
5
|
+
let segmentNames = components.slice(1);
|
|
6
|
+
if (!segmentNames.length) {
|
|
7
|
+
segmentNames = [''];
|
|
8
|
+
}
|
|
9
|
+
for (const segmentName of segmentNames) {
|
|
10
|
+
await newSegment({ segmentName, dryRun: options.dryRun });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
if (components.length < 2) {
|
|
15
|
+
throw new Error('Invalid command invocation. Please provide at least two arguments.');
|
|
16
|
+
}
|
|
17
|
+
const what = components.slice(0, -1);
|
|
18
|
+
const moduleNameWithOptionalSegment = components[components.length - 1];
|
|
19
|
+
if (!moduleNameWithOptionalSegment) {
|
|
20
|
+
throw new Error('A module name with an optional segment cannot be empty');
|
|
21
|
+
}
|
|
22
|
+
await newModule({ what, moduleNameWithOptionalSegment, dryRun: options.dryRun });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
4
|
+
import render from './render.mjs';
|
|
5
|
+
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
6
|
+
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
7
|
+
import locateSegments from '../locateSegments.mjs';
|
|
8
|
+
import addClassToSegmentCode from './addClassToSegmentCode.mjs';
|
|
9
|
+
import fileExists from '../utils/fileExists.mjs';
|
|
10
|
+
import prettify from '../utils/prettify.mjs';
|
|
11
|
+
function splitByLast(str, delimiter = '/') {
|
|
12
|
+
const index = str.lastIndexOf(delimiter);
|
|
13
|
+
if (index === -1) {
|
|
14
|
+
// Delimiter not found; return empty string and the original string
|
|
15
|
+
return ['', str];
|
|
16
|
+
}
|
|
17
|
+
const before = str.substring(0, index);
|
|
18
|
+
const after = str.substring(index + delimiter.length);
|
|
19
|
+
return [before, after];
|
|
20
|
+
}
|
|
21
|
+
export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, }) {
|
|
22
|
+
const { config, log, apiDir, cwd } = await getProjectInfo();
|
|
23
|
+
const templates = config.templates;
|
|
24
|
+
const [segmentName, moduleName] = splitByLast(moduleNameWithOptionalSegment);
|
|
25
|
+
// replace c by controller, s by service, w by worker, everything else keeps the same
|
|
26
|
+
what = what.map((s) => {
|
|
27
|
+
switch (s) {
|
|
28
|
+
case 'c':
|
|
29
|
+
return 'controller';
|
|
30
|
+
case 's':
|
|
31
|
+
return 'service';
|
|
32
|
+
case 'w':
|
|
33
|
+
return 'worker';
|
|
34
|
+
default:
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// check if template exists
|
|
39
|
+
for (const type of what) {
|
|
40
|
+
if (!templates[type]) {
|
|
41
|
+
throw new Error(`Template for ${type} not found in config`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const segments = await locateSegments(apiDir);
|
|
45
|
+
const segment = segments.find((s) => s.segmentName === segmentName);
|
|
46
|
+
if (!segment) {
|
|
47
|
+
throw new Error(`Segment ${segmentName} not found`);
|
|
48
|
+
}
|
|
49
|
+
for (const type of what) {
|
|
50
|
+
const templatePath = templates[type];
|
|
51
|
+
const templateAbsolutePath = path.join(cwd, '../..', templatePath); // TODO WRONG, use import.meta.resolve, also in other modules for fetcher, validateOnClient, etc.
|
|
52
|
+
const templateCode = await fs.readFile(templateAbsolutePath, 'utf-8');
|
|
53
|
+
const { fileName, className, rpcName, code } = await render(templateCode, {
|
|
54
|
+
config,
|
|
55
|
+
withService: what.includes('service'),
|
|
56
|
+
segmentName,
|
|
57
|
+
moduleName,
|
|
58
|
+
});
|
|
59
|
+
console.log(config.modulesDir, fileName);
|
|
60
|
+
const absoluteModulePath = path.join(config.modulesDir, fileName);
|
|
61
|
+
const dirName = path.dirname(absoluteModulePath);
|
|
62
|
+
const prettiedCode = await prettify(code, absoluteModulePath);
|
|
63
|
+
if (!dryRun) {
|
|
64
|
+
if (await fileExists(absoluteModulePath)) {
|
|
65
|
+
log.warn(`File ${chalkHighlightThing(absoluteModulePath)} already exists, skipping this "${type}"`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
await fs.mkdir(dirName, { recursive: true });
|
|
69
|
+
await fs.writeFile(absoluteModulePath, prettiedCode);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (type === 'controller' || type === 'worker') {
|
|
73
|
+
const { routeFilePath } = segment;
|
|
74
|
+
const segmentSourceCode = await fs.readFile(routeFilePath, 'utf-8');
|
|
75
|
+
const importPath = path.relative(dirName, fileName); // TODO WRONG
|
|
76
|
+
const newSegmentCode = addClassToSegmentCode(segmentSourceCode, {
|
|
77
|
+
className,
|
|
78
|
+
rpcName,
|
|
79
|
+
type,
|
|
80
|
+
importPath,
|
|
81
|
+
});
|
|
82
|
+
if (!dryRun) {
|
|
83
|
+
await fs.writeFile(routeFilePath, newSegmentCode);
|
|
84
|
+
}
|
|
85
|
+
log.info(`Added ${chalkHighlightThing(className)} ${type} to ${formatLoggedSegmentName(segmentName)} as ${chalkHighlightThing(rpcName)}`);
|
|
86
|
+
}
|
|
87
|
+
log.info(`Created ${chalkHighlightThing(fileName)} with ${chalkHighlightThing(type)} template for ${formatLoggedSegmentName(segmentName)}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import getProjectInfo from '../getProjectInfo/index.mjs';
|
|
4
|
+
import fileExists from '../utils/fileExists.mjs';
|
|
5
|
+
import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
|
|
6
|
+
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
7
|
+
import prettify from '../utils/prettify.mjs';
|
|
8
|
+
export default async function newSegment({ segmentName, dryRun }) {
|
|
9
|
+
const { apiDir, cwd, log } = await getProjectInfo();
|
|
10
|
+
const absoluteSegmentRoutePath = path.join(cwd, apiDir, segmentName, '[[...vovk]]/route.ts');
|
|
11
|
+
if (await fileExists(absoluteSegmentRoutePath)) {
|
|
12
|
+
return log.error(`Unable to create new segment. ${formatLoggedSegmentName(segmentName, { upperFirst: true })} already exists.`);
|
|
13
|
+
}
|
|
14
|
+
const code = await prettify(`import { initVovk } from 'vovk';
|
|
15
|
+
|
|
16
|
+
const controllers = {};
|
|
17
|
+
const workers = {};
|
|
18
|
+
|
|
19
|
+
export type Controllers = typeof controllers;
|
|
20
|
+
export type Workers = typeof workers;
|
|
21
|
+
|
|
22
|
+
export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initVovk({
|
|
23
|
+
emitSchema: true,
|
|
24
|
+
workers,
|
|
25
|
+
controllers,
|
|
26
|
+
});
|
|
27
|
+
`, absoluteSegmentRoutePath);
|
|
28
|
+
if (!dryRun) {
|
|
29
|
+
await fs.mkdir(path.dirname(absoluteSegmentRoutePath), { recursive: true });
|
|
30
|
+
await fs.writeFile(absoluteSegmentRoutePath, code);
|
|
31
|
+
}
|
|
32
|
+
log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} created at ${absoluteSegmentRoutePath}. Run ${chalkHighlightThing(`vovk new controller ${segmentName}/someName`)} to create a controller or modify the segment file manually`);
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { VovkConfig } from '../types.mjs';
|
|
2
|
+
export default function render(codeTemplate: string, { config, withService, segmentName, moduleName, }: {
|
|
3
|
+
config: VovkConfig;
|
|
4
|
+
withService: boolean;
|
|
5
|
+
segmentName: string;
|
|
6
|
+
moduleName: string;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
fileName: string;
|
|
9
|
+
className: string;
|
|
10
|
+
rpcName: string;
|
|
11
|
+
code: string;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import ejs from 'ejs';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import pluralize from 'pluralize';
|
|
5
|
+
import addCommonTerms from './addCommonTerms.mjs';
|
|
6
|
+
addCommonTerms();
|
|
7
|
+
export default async function render(codeTemplate, { config, withService, segmentName, moduleName, }) {
|
|
8
|
+
const getFileDir = (givenSegmentName, givenModuleName) => [_.camelCase(givenModuleName), givenSegmentName || config.rootSegmentModulesDirName].filter(Boolean).join('/') +
|
|
9
|
+
'/';
|
|
10
|
+
const templateVars = {
|
|
11
|
+
// input
|
|
12
|
+
config,
|
|
13
|
+
withService,
|
|
14
|
+
segmentName,
|
|
15
|
+
moduleName,
|
|
16
|
+
// utils
|
|
17
|
+
getFileDir,
|
|
18
|
+
// libraries
|
|
19
|
+
_, // lodash
|
|
20
|
+
pluralize,
|
|
21
|
+
};
|
|
22
|
+
// first, render the front matter because it can use ejs variables
|
|
23
|
+
const parsed = matter((await ejs.render(codeTemplate, templateVars, { async: true })).trim());
|
|
24
|
+
const { fileName, className, rpcName } = parsed.data;
|
|
25
|
+
const templateContent = parsed.content;
|
|
26
|
+
const code = await ejs.render(templateContent, templateVars, { async: true });
|
|
27
|
+
return { fileName, className, rpcName, code };
|
|
28
|
+
}
|
package/dist/types.d.mts
CHANGED
|
@@ -26,4 +26,11 @@ export type VovkConfig = {
|
|
|
26
26
|
origin?: string;
|
|
27
27
|
rootSegmentModulesDirName?: string;
|
|
28
28
|
logLevel?: LogLevelNames;
|
|
29
|
+
custom?: KnownAny;
|
|
30
|
+
templates?: {
|
|
31
|
+
service?: string;
|
|
32
|
+
controller?: string;
|
|
33
|
+
worker?: string;
|
|
34
|
+
[key: string]: string | undefined;
|
|
35
|
+
};
|
|
29
36
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import upperFirstLodash from 'lodash/upperFirst.js';
|
|
3
|
+
export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false } = {}) {
|
|
4
|
+
let text = segmentName ? `segment "${segmentName}"` : 'the root segment';
|
|
5
|
+
text = upperFirst ? upperFirstLodash(text) : text;
|
|
4
6
|
return withChalk ? chalkHighlightThing(text) : text;
|
|
5
7
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function prettify(code: string, absoluteFilePath: string): Promise<string>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import prettier from 'prettier';
|
|
2
|
+
export default async function prettify(code, absoluteFilePath) {
|
|
3
|
+
const options = await prettier.resolveConfig(absoluteFilePath);
|
|
4
|
+
const finalOptions = {
|
|
5
|
+
...options,
|
|
6
|
+
filepath: absoluteFilePath, // for selecting the correct parser
|
|
7
|
+
};
|
|
8
|
+
const formattedCode = await prettier.format(code, finalOptions);
|
|
9
|
+
return formattedCode;
|
|
10
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
2
|
-
export default function ensureSchemaFiles(
|
|
2
|
+
export default function ensureSchemaFiles(projectInfo: ProjectInfo | null, schemaOutAbsolutePath: string, segmentNames: string[]): Promise<void>;
|
|
3
3
|
export declare const debouncedEnsureSchemaFiles: import("lodash").DebouncedFunc<typeof ensureSchemaFiles>;
|
|
@@ -3,7 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import debounce from 'lodash/debounce.js';
|
|
4
4
|
import writeOneSchemaFile, { ROOT_SEGMENT_SCHEMA_NAME } from './writeOneSchemaFile.mjs';
|
|
5
5
|
import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
|
|
6
|
-
export default async function ensureSchemaFiles(
|
|
6
|
+
export default async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePath, segmentNames) {
|
|
7
7
|
const now = Date.now();
|
|
8
8
|
let hasChanged = false;
|
|
9
9
|
// Create index.js file
|
|
@@ -15,13 +15,13 @@ export default async function ensureSchemaFiles(schemaOutFullPath, segmentNames,
|
|
|
15
15
|
const dTsContent = `import type { VovkSchema } from 'vovk';
|
|
16
16
|
declare const segmentSchema: Record<string, VovkSchema>;
|
|
17
17
|
export default segmentSchema;`;
|
|
18
|
-
await fs.mkdir(
|
|
19
|
-
await fs.writeFile(path.join(
|
|
20
|
-
await fs.writeFile(path.join(
|
|
21
|
-
// Create JSON files (if not exist) with name [segmentName].json (where segmentName can include /, which means the folder structure can be nested)
|
|
18
|
+
await fs.mkdir(schemaOutAbsolutePath, { recursive: true });
|
|
19
|
+
await fs.writeFile(path.join(schemaOutAbsolutePath, 'index.js'), indexContent);
|
|
20
|
+
await fs.writeFile(path.join(schemaOutAbsolutePath, 'index.d.ts'), dTsContent);
|
|
21
|
+
// Create JSON files (if not exist) with name [segmentName].json (where segmentName can include /, which means the folder structure can be nested)
|
|
22
22
|
await Promise.all(segmentNames.map(async (segmentName) => {
|
|
23
23
|
const { isCreated } = await writeOneSchemaFile({
|
|
24
|
-
|
|
24
|
+
schemaOutAbsolutePath,
|
|
25
25
|
schema: {
|
|
26
26
|
emitSchema: false,
|
|
27
27
|
segmentName,
|
|
@@ -31,7 +31,7 @@ export default segmentSchema;`;
|
|
|
31
31
|
skipIfExists: true,
|
|
32
32
|
});
|
|
33
33
|
if (isCreated) {
|
|
34
|
-
projectInfo?.log.debug(`Created empty schema file for ${formatLoggedSegmentName(segmentName
|
|
34
|
+
projectInfo?.log.debug(`Created empty schema file for ${formatLoggedSegmentName(segmentName)}`);
|
|
35
35
|
hasChanged = true;
|
|
36
36
|
}
|
|
37
37
|
}));
|
|
@@ -39,32 +39,32 @@ export default segmentSchema;`;
|
|
|
39
39
|
async function deleteUnnecessaryJsonFiles(dirPath) {
|
|
40
40
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
41
41
|
await Promise.all(entries.map(async (entry) => {
|
|
42
|
-
const
|
|
42
|
+
const absolutePath = path.join(dirPath, entry.name);
|
|
43
43
|
if (entry.isDirectory()) {
|
|
44
44
|
// Recursively delete unnecessary files and folders within nested directories
|
|
45
|
-
await deleteUnnecessaryJsonFiles(
|
|
45
|
+
await deleteUnnecessaryJsonFiles(absolutePath);
|
|
46
46
|
// Check if the directory is empty after deletion and remove it if so
|
|
47
|
-
const remainingEntries = await fs.readdir(
|
|
47
|
+
const remainingEntries = await fs.readdir(absolutePath);
|
|
48
48
|
if (remainingEntries.length === 0) {
|
|
49
|
-
await fs.rmdir(
|
|
50
|
-
projectInfo?.log.debug(`Deleted unnecessary schema directory "${
|
|
49
|
+
await fs.rmdir(absolutePath);
|
|
50
|
+
projectInfo?.log.debug(`Deleted unnecessary schema directory "${absolutePath}"`);
|
|
51
51
|
hasChanged = true;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
55
|
-
const relativePath = path.relative(
|
|
55
|
+
const relativePath = path.relative(schemaOutAbsolutePath, absolutePath);
|
|
56
56
|
const segmentName = relativePath.replace(/\\/g, '/').slice(0, -5); // Remove '.json' extension
|
|
57
57
|
if (!segmentNames.includes(segmentName) &&
|
|
58
58
|
!segmentNames.includes(segmentName.replace(ROOT_SEGMENT_SCHEMA_NAME, ''))) {
|
|
59
|
-
await fs.unlink(
|
|
60
|
-
projectInfo?.log.debug(`Deleted unnecessary schema file for ${formatLoggedSegmentName(segmentName
|
|
59
|
+
await fs.unlink(absolutePath);
|
|
60
|
+
projectInfo?.log.debug(`Deleted unnecessary schema file for ${formatLoggedSegmentName(segmentName)}`);
|
|
61
61
|
hasChanged = true;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
}));
|
|
65
65
|
}
|
|
66
66
|
// Start the recursive deletion from the root directory
|
|
67
|
-
await deleteUnnecessaryJsonFiles(
|
|
67
|
+
await deleteUnnecessaryJsonFiles(schemaOutAbsolutePath);
|
|
68
68
|
if (hasChanged)
|
|
69
69
|
projectInfo?.log.info(`Schema files updated in ${Date.now() - now}ms`);
|
|
70
70
|
}
|
package/dist/watcher/index.mjs
CHANGED
|
@@ -23,12 +23,12 @@ export class VovkCLIWatcher {
|
|
|
23
23
|
#watchSegments = () => {
|
|
24
24
|
const segmentReg = /\/?\[\[\.\.\.[a-zA-Z-_]+\]\]\/route.ts$/;
|
|
25
25
|
const { cwd, log, config, apiDir } = this.#projectInfo;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const getSegmentName = (filePath) => path.relative(
|
|
29
|
-
log.debug(`Watching segments in ${
|
|
26
|
+
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
27
|
+
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
28
|
+
const getSegmentName = (filePath) => path.relative(apiDirAbsolutePath, filePath).replace(segmentReg, '');
|
|
29
|
+
log.debug(`Watching segments in ${apiDirAbsolutePath}`);
|
|
30
30
|
this.#segmentWatcher = chokidar
|
|
31
|
-
.watch(
|
|
31
|
+
.watch(apiDirAbsolutePath, {
|
|
32
32
|
persistent: true,
|
|
33
33
|
ignoreInitial: true,
|
|
34
34
|
})
|
|
@@ -39,10 +39,9 @@ export class VovkCLIWatcher {
|
|
|
39
39
|
this.#segments = this.#segments.find((s) => s.segmentName === segmentName)
|
|
40
40
|
? this.#segments
|
|
41
41
|
: [...this.#segments, { routeFilePath: filePath, segmentName }];
|
|
42
|
-
log.info(`${capitalize(formatLoggedSegmentName(segmentName
|
|
42
|
+
log.info(`${capitalize(formatLoggedSegmentName(segmentName))} has been added`);
|
|
43
43
|
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
44
|
-
void debouncedEnsureSchemaFiles(
|
|
45
|
-
);
|
|
44
|
+
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
46
45
|
}
|
|
47
46
|
})
|
|
48
47
|
.on('change', (filePath) => {
|
|
@@ -54,7 +53,7 @@ export class VovkCLIWatcher {
|
|
|
54
53
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
55
54
|
.on('addDir', async (dirPath) => {
|
|
56
55
|
log.debug(`Directory ${dirPath} has been added to segments folder`);
|
|
57
|
-
this.#segments = await locateSegments(
|
|
56
|
+
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
58
57
|
for (const { segmentName } of this.#segments) {
|
|
59
58
|
void this.#requestSchema(segmentName);
|
|
60
59
|
}
|
|
@@ -62,7 +61,7 @@ export class VovkCLIWatcher {
|
|
|
62
61
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
63
62
|
.on('unlinkDir', async (dirPath) => {
|
|
64
63
|
log.debug(`Directory ${dirPath} has been removed from segments folder`);
|
|
65
|
-
this.#segments = await locateSegments(
|
|
64
|
+
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
66
65
|
for (const { segmentName } of this.#segments) {
|
|
67
66
|
void this.#requestSchema(segmentName);
|
|
68
67
|
}
|
|
@@ -72,10 +71,9 @@ export class VovkCLIWatcher {
|
|
|
72
71
|
if (segmentReg.test(filePath)) {
|
|
73
72
|
const segmentName = getSegmentName(filePath);
|
|
74
73
|
this.#segments = this.#segments.filter((s) => s.segmentName !== segmentName);
|
|
75
|
-
log.info(`${
|
|
74
|
+
log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} has been removed`);
|
|
76
75
|
log.debug(`Full list of segments: ${this.#segments.map((s) => s.segmentName).join(', ')}`);
|
|
77
|
-
void debouncedEnsureSchemaFiles(
|
|
78
|
-
);
|
|
76
|
+
void debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
79
77
|
}
|
|
80
78
|
})
|
|
81
79
|
.on('ready', () => {
|
|
@@ -87,10 +85,10 @@ export class VovkCLIWatcher {
|
|
|
87
85
|
};
|
|
88
86
|
#watchModules = () => {
|
|
89
87
|
const { config, cwd, log } = this.#projectInfo;
|
|
90
|
-
const
|
|
91
|
-
log.debug(`Watching modules in ${
|
|
88
|
+
const modulesDirAbsolutePath = path.join(cwd, config.modulesDir);
|
|
89
|
+
log.debug(`Watching modules in ${modulesDirAbsolutePath}`);
|
|
92
90
|
this.#modulesWatcher = chokidar
|
|
93
|
-
.watch(
|
|
91
|
+
.watch(modulesDirAbsolutePath, {
|
|
94
92
|
persistent: true,
|
|
95
93
|
ignoreInitial: true,
|
|
96
94
|
})
|
|
@@ -193,10 +191,10 @@ export class VovkCLIWatcher {
|
|
|
193
191
|
#requestSchema = debounceWithArgs(async (segmentName) => {
|
|
194
192
|
const { apiEntryPoint, log, port } = this.#projectInfo;
|
|
195
193
|
const endpoint = `${apiEntryPoint.startsWith('http') ? apiEntryPoint : `http://localhost:${port}${apiEntryPoint}`}/${segmentName ? `${segmentName}/` : ''}_schema_`;
|
|
196
|
-
log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName
|
|
194
|
+
log.debug(`Requesting schema for ${formatLoggedSegmentName(segmentName)} at ${endpoint}`);
|
|
197
195
|
const resp = await fetch(endpoint);
|
|
198
196
|
if (resp.status !== 200) {
|
|
199
|
-
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName
|
|
197
|
+
log.warn(`Schema request to ${formatLoggedSegmentName(segmentName)} failed with status code ${resp.status}. Expected 200.`);
|
|
200
198
|
return;
|
|
201
199
|
}
|
|
202
200
|
let schema = null;
|
|
@@ -204,7 +202,7 @@ export class VovkCLIWatcher {
|
|
|
204
202
|
({ schema } = (await resp.json()));
|
|
205
203
|
}
|
|
206
204
|
catch (error) {
|
|
207
|
-
log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName
|
|
205
|
+
log.error(`Error parsing schema for ${formatLoggedSegmentName(segmentName)}: ${error.message}`);
|
|
208
206
|
}
|
|
209
207
|
await this.#handleSchema(schema);
|
|
210
208
|
}, 500);
|
|
@@ -215,7 +213,7 @@ export class VovkCLIWatcher {
|
|
|
215
213
|
log.warn('Segment schema is null');
|
|
216
214
|
return;
|
|
217
215
|
}
|
|
218
|
-
const
|
|
216
|
+
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
219
217
|
const segment = this.#segments.find((s) => s.segmentName === schema.segmentName);
|
|
220
218
|
if (!segment) {
|
|
221
219
|
log.warn(`Segment "${schema.segmentName}" not found`);
|
|
@@ -225,18 +223,18 @@ export class VovkCLIWatcher {
|
|
|
225
223
|
if (schema.emitSchema) {
|
|
226
224
|
const now = Date.now();
|
|
227
225
|
const { diffResult } = await writeOneSchemaFile({
|
|
228
|
-
|
|
226
|
+
schemaOutAbsolutePath,
|
|
229
227
|
schema,
|
|
230
228
|
skipIfExists: false,
|
|
231
229
|
});
|
|
232
230
|
const timeTook = Date.now() - now;
|
|
233
231
|
if (diffResult) {
|
|
234
232
|
logDiffResult(segment.segmentName, diffResult, this.#projectInfo);
|
|
235
|
-
log.info(`Schema for ${formatLoggedSegmentName(segment.segmentName
|
|
233
|
+
log.info(`Schema for ${formatLoggedSegmentName(segment.segmentName)} has been updated in ${timeTook}ms`);
|
|
236
234
|
}
|
|
237
235
|
}
|
|
238
236
|
else if (schema && (!isEmpty(schema.controllers) || !isEmpty(schema.workers))) {
|
|
239
|
-
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName
|
|
237
|
+
log.error(`Non-empty schema provided for ${formatLoggedSegmentName(segment.segmentName)} but emitSchema is false`);
|
|
240
238
|
}
|
|
241
239
|
if (this.#segments.every((s) => this.#schemas[s.segmentName])) {
|
|
242
240
|
log.debug(`All segments with "emitSchema" have schema.`);
|
|
@@ -252,10 +250,10 @@ export class VovkCLIWatcher {
|
|
|
252
250
|
process.on('unhandledRejection', (reason) => {
|
|
253
251
|
log.error(`Unhandled Rejection: ${String(reason)}`);
|
|
254
252
|
});
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
this.#segments = await locateSegments(
|
|
258
|
-
await debouncedEnsureSchemaFiles(
|
|
253
|
+
const apiDirAbsolutePath = path.join(cwd, apiDir);
|
|
254
|
+
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
255
|
+
this.#segments = await locateSegments(apiDirAbsolutePath);
|
|
256
|
+
await debouncedEnsureSchemaFiles(this.#projectInfo, schemaOutAbsolutePath, this.#segments.map((s) => s.segmentName));
|
|
259
257
|
// Request schema every segment in 3 seconds in order to update schema and start watching
|
|
260
258
|
setTimeout(() => {
|
|
261
259
|
for (const { segmentName } of this.#segments) {
|
|
@@ -31,43 +31,43 @@ export default function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
|
31
31
|
case 'worker':
|
|
32
32
|
switch (diffNormalizedItem.type) {
|
|
33
33
|
case 'added':
|
|
34
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName
|
|
34
|
+
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
|
|
35
35
|
break;
|
|
36
36
|
case 'removed':
|
|
37
|
-
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName
|
|
37
|
+
projectInfo.log.info(`Schema for worker ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
|
|
38
38
|
break;
|
|
39
39
|
}
|
|
40
40
|
break;
|
|
41
41
|
case 'controller':
|
|
42
42
|
switch (diffNormalizedItem.type) {
|
|
43
43
|
case 'added':
|
|
44
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName
|
|
44
|
+
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
|
|
45
45
|
break;
|
|
46
46
|
case 'removed':
|
|
47
|
-
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName
|
|
47
|
+
projectInfo.log.info(`Schema for controller ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
|
|
48
48
|
break;
|
|
49
49
|
}
|
|
50
50
|
break;
|
|
51
51
|
case 'workerHandler':
|
|
52
52
|
switch (diffNormalizedItem.type) {
|
|
53
53
|
case 'added':
|
|
54
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName
|
|
54
|
+
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
|
|
55
55
|
break;
|
|
56
56
|
case 'removed':
|
|
57
|
-
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName
|
|
57
|
+
projectInfo.log.info(`Schema for worker method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
|
|
58
58
|
break;
|
|
59
59
|
}
|
|
60
60
|
break;
|
|
61
61
|
case 'controllerHandler':
|
|
62
62
|
switch (diffNormalizedItem.type) {
|
|
63
63
|
case 'added':
|
|
64
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName
|
|
64
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been added at ${formatLoggedSegmentName(segmentName)}`);
|
|
65
65
|
break;
|
|
66
66
|
case 'removed':
|
|
67
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName
|
|
67
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been removed from ${formatLoggedSegmentName(segmentName)}`);
|
|
68
68
|
break;
|
|
69
69
|
case 'changed':
|
|
70
|
-
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been changed at ${formatLoggedSegmentName(segmentName
|
|
70
|
+
projectInfo.log.info(`Schema for controller method ${chalkHighlightThing(diffNormalizedItem.name)} has been changed at ${formatLoggedSegmentName(segmentName)}`);
|
|
71
71
|
break;
|
|
72
72
|
}
|
|
73
73
|
break;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { VovkSchema } from 'vovk';
|
|
2
2
|
import { DiffResult } from './diffSchema.mjs';
|
|
3
3
|
export declare const ROOT_SEGMENT_SCHEMA_NAME = "_root";
|
|
4
|
-
export default function writeOneSchemaFile({
|
|
5
|
-
|
|
4
|
+
export default function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists, }: {
|
|
5
|
+
schemaOutAbsolutePath: string;
|
|
6
6
|
schema: VovkSchema;
|
|
7
7
|
skipIfExists?: boolean;
|
|
8
8
|
}): Promise<{
|
|
@@ -2,8 +2,8 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
3
|
import diffSchema from './diffSchema.mjs';
|
|
4
4
|
export const ROOT_SEGMENT_SCHEMA_NAME = '_root';
|
|
5
|
-
export default async function writeOneSchemaFile({
|
|
6
|
-
const segmentPath = path.join(
|
|
5
|
+
export default async function writeOneSchemaFile({ schemaOutAbsolutePath, schema, skipIfExists = false, }) {
|
|
6
|
+
const segmentPath = path.join(schemaOutAbsolutePath, `${schema.segmentName || ROOT_SEGMENT_SCHEMA_NAME}.json`);
|
|
7
7
|
if (skipIfExists) {
|
|
8
8
|
try {
|
|
9
9
|
await fs.stat(segmentPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vovk-cli",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.19",
|
|
4
4
|
"bin": {
|
|
5
5
|
"vovk": "./dist/index.mjs"
|
|
6
6
|
},
|
|
@@ -40,13 +40,18 @@
|
|
|
40
40
|
"chokidar": "^3.6.0",
|
|
41
41
|
"commander": "^12.1.0",
|
|
42
42
|
"concurrently": "^8.2.2",
|
|
43
|
+
"ejs": "^3.1.10",
|
|
44
|
+
"gray-matter": "^4.0.3",
|
|
43
45
|
"inflection": "^3.0.0",
|
|
44
46
|
"jsonc-parser": "^3.3.1",
|
|
45
47
|
"lodash": "^4.17.21",
|
|
46
48
|
"loglevel": "^1.9.2",
|
|
47
|
-
"pluralize": "^8.0.0"
|
|
49
|
+
"pluralize": "^8.0.0",
|
|
50
|
+
"prettier": "^3.3.3",
|
|
51
|
+
"ts-morph": "^23.0.0"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
54
|
+
"@types/ejs": "^3.1.5",
|
|
50
55
|
"@types/npmcli__package-json": "^4.0.4",
|
|
51
56
|
"@types/pluralize": "^0.0.33",
|
|
52
57
|
"type-fest": "^4.26.0"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<% var modulePascalName = _.upperFirst(_.camelCase(moduleName)); %>
|
|
2
|
+
<% var modulePascalNamePlural = pluralize(modulePascalName); %>
|
|
3
|
+
<% var ControllerName = modulePascalName + 'Controller'; %>
|
|
4
|
+
<% var RPCName = modulePascalName + 'RPC'; %>
|
|
5
|
+
<% var ServiceName = modulePascalName + 'Service'; %>
|
|
6
|
+
---
|
|
7
|
+
# Relative to modules dir
|
|
8
|
+
fileName: <%= getFileDir(segmentName, moduleName) + ControllerName + '.ts' %>
|
|
9
|
+
className: <%= ControllerName %> # Used to define a controller in a segment
|
|
10
|
+
rpcName: <%= RPCName %> # Used to define an exported RPC class in a segment
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
import { prefix, get, put, post, del<%= !config.validationLibrary ? ', type VovkRequest' : '' %> } from 'vovk';
|
|
14
|
+
<% if(withService) { %>
|
|
15
|
+
import <%= ServiceName %> from './<%= ServiceName %>';
|
|
16
|
+
<% } %>
|
|
17
|
+
<% if(config.validationLibrary === 'vovk-zod') { %>
|
|
18
|
+
import { withZod } from 'vovk-zod';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
<% } %>
|
|
21
|
+
|
|
22
|
+
@prefix('<%= _.kebabCase(moduleName).toLowerCase() %>')
|
|
23
|
+
export default class <%= ControllerName %> {
|
|
24
|
+
@get()
|
|
25
|
+
<% if(config.validationLibrary === 'vovk-zod') { %>
|
|
26
|
+
async get<%= modulePascalNamePlural %> = withZod(null, z.object({ q: z.string() }), (req) => {
|
|
27
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
28
|
+
<% if(withService) { %>
|
|
29
|
+
return <%= ServiceName %>.getMyThingsExample(q);
|
|
30
|
+
<% } else { %>
|
|
31
|
+
return { q };
|
|
32
|
+
<% } %>
|
|
33
|
+
});
|
|
34
|
+
<% } else { %>
|
|
35
|
+
static get<%= modulePascalNamePlural %> = async (req: VovkRequest<null, { q: string }>) => {
|
|
36
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
37
|
+
<% if(withService) { %>
|
|
38
|
+
return <%= ServiceName %>.getMyThingsExample(q);
|
|
39
|
+
<% } else { %>
|
|
40
|
+
return { q };
|
|
41
|
+
<% } %>
|
|
42
|
+
}
|
|
43
|
+
<% } %>
|
|
44
|
+
|
|
45
|
+
@put(':id')
|
|
46
|
+
<% if(config.validationLibrary === 'vovk-zod') { %>
|
|
47
|
+
static update<%= modulePascalNamePlural %> = withZod(
|
|
48
|
+
z.object({
|
|
49
|
+
foo: z.union([z.literal('bar'), z.literal('baz')]),
|
|
50
|
+
}),
|
|
51
|
+
z.object({ q: z.string() }),
|
|
52
|
+
async (req, params: { id: string }) => {
|
|
53
|
+
const { id } = params;
|
|
54
|
+
const body = await req.json();
|
|
55
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
56
|
+
<% if(withService) { %>
|
|
57
|
+
return MyThingService.updateMyThingExample(id, q, body);
|
|
58
|
+
<% } else { %>
|
|
59
|
+
return { id, body, q };
|
|
60
|
+
<% } %>
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
<% } else { %>
|
|
64
|
+
static update<%= modulePascalNamePlural %> = async (req: VovkRequest<{ foo: 'bar' | 'baz' }, { q: string }>, params: { id: string }) => {
|
|
65
|
+
const { id } = params;
|
|
66
|
+
const body = await req.json();
|
|
67
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
68
|
+
<% if(withService) { %>
|
|
69
|
+
return MyThingService.updateMyThingExample(id, q, body);
|
|
70
|
+
<% } else { %>
|
|
71
|
+
return { id, body, q };
|
|
72
|
+
<% } %>
|
|
73
|
+
};
|
|
74
|
+
<% } %>
|
|
75
|
+
|
|
76
|
+
@post()
|
|
77
|
+
static create<%= modulePascalNamePlural %> = () => {
|
|
78
|
+
// ...
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
@del(':id')
|
|
82
|
+
static delete<%= modulePascalNamePlural %> = () => {
|
|
83
|
+
// ...
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% var modulePascalName = _.upperFirst(_.camelCase(moduleName); %>
|
|
2
|
+
<% var ServiceName = modulePascalName + 'Service'; %>
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
# Relative to modules dir
|
|
6
|
+
fileName: <%= getFileDir(segmentName, moduleName) + ServiceName + '.ts' %>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
// TO DO: Implement <%= ServiceName %>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<% var modulePascalName = _.upperFirst(_.camelCase(moduleName); %>
|
|
2
|
+
<% var WorkerName = modulePascalName + 'Worker'; %>
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
# Relative to modules dir
|
|
6
|
+
fileName: <%= getFileDir(segmentName, moduleName) + WorkerName + '.ts' %>
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
// TO DO: Implement <%= WorkerName %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { prefix, get, put, post, del, type VovkRequest } from 'vovk';
|
|
2
|
+
|
|
3
|
+
@prefix('my-thing')
|
|
4
|
+
export default class MyThingController {
|
|
5
|
+
@get()
|
|
6
|
+
static getMyThingsExample = (req: VovkRequest<null, { q: string }>) => {
|
|
7
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
8
|
+
return { q };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
@put(':id')
|
|
12
|
+
static updateMyThingExample = async (
|
|
13
|
+
req: VovkRequest<{ foo: 'bar' | 'baz' }, { q: string }>,
|
|
14
|
+
params: { id: string }
|
|
15
|
+
) => {
|
|
16
|
+
const { id } = params;
|
|
17
|
+
const body = await req.json();
|
|
18
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
19
|
+
|
|
20
|
+
return { id, q, body };
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
@post()
|
|
24
|
+
static createMyThingExample = () => {
|
|
25
|
+
// ...
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
@del(':id')
|
|
29
|
+
static deleteMyThingExample = () => {
|
|
30
|
+
// ...
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { prefix, get, put, post, del } from 'vovk';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { withZod } from 'vovk-zod';
|
|
4
|
+
import MyThingService from './MyThingService.s.template';
|
|
5
|
+
|
|
6
|
+
@prefix('my-thing')
|
|
7
|
+
export default class MyThingController {
|
|
8
|
+
@get()
|
|
9
|
+
static getMyThingsExample = withZod(null, z.object({ q: z.string() }), (req) => {
|
|
10
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
11
|
+
|
|
12
|
+
return MyThingService.getMyThingsExample(q);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
@put(':id')
|
|
16
|
+
static updateMyThingExample = withZod(
|
|
17
|
+
z.object({
|
|
18
|
+
foo: z.union([z.literal('bar'), z.literal('baz')]),
|
|
19
|
+
}),
|
|
20
|
+
z.object({ q: z.string() }),
|
|
21
|
+
async (req, params: { id: string }) => {
|
|
22
|
+
const { id } = params;
|
|
23
|
+
const body = await req.json();
|
|
24
|
+
const q = req.nextUrl.searchParams.get('q');
|
|
25
|
+
|
|
26
|
+
return MyThingService.updateMyThingExample(id, q, body);
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
@post()
|
|
31
|
+
static async createMyThingExample() {
|
|
32
|
+
// ...
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@del(':id')
|
|
36
|
+
static deleteMyThingExample() {
|
|
37
|
+
// ...
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { VovkControllerBody, VovkControllerQuery } from 'vovk';
|
|
2
|
+
import type MyThingController from './MyThingController.c.template';
|
|
3
|
+
|
|
4
|
+
export default class MyThingService {
|
|
5
|
+
static getMyThingsExample = (q: VovkControllerQuery<typeof MyThingController.getMyThingsExample>['q']) => {
|
|
6
|
+
return { q };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
static updateMyThingExample = (
|
|
10
|
+
id: string,
|
|
11
|
+
q: VovkControllerQuery<typeof MyThingController.updateMyThingExample>['q'],
|
|
12
|
+
body: VovkControllerBody<typeof MyThingController.updateMyThingExample>
|
|
13
|
+
) => {
|
|
14
|
+
return { id, q, body };
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ...
|
|
18
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default function replaceOccurrences(code: string, replacementSingular: string): string;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import camelCase from 'lodash/camelCase.js';
|
|
2
|
-
import kebabCase from 'lodash/kebabCase.js';
|
|
3
|
-
import snakeCase from 'lodash/snakeCase.js';
|
|
4
|
-
import upperFirst from 'lodash/upperFirst.js';
|
|
5
|
-
import pluralize from 'pluralize';
|
|
6
|
-
import addCommonTerms from './addCommonTerms';
|
|
7
|
-
addCommonTerms();
|
|
8
|
-
console.log('WoRD', pluralize('entity'));
|
|
9
|
-
export default function replaceOccurrences(code, replacementSingular) {
|
|
10
|
-
const replacementPlural = pluralize(replacementSingular);
|
|
11
|
-
// Different cases of the replacement string
|
|
12
|
-
const replacements = {
|
|
13
|
-
camelPlural: camelCase(replacementPlural),
|
|
14
|
-
camel: camelCase(replacementSingular),
|
|
15
|
-
pascalPlural: upperFirst(camelCase(replacementPlural)),
|
|
16
|
-
pascal: upperFirst(camelCase(replacementSingular)),
|
|
17
|
-
kebabPlural: kebabCase(replacementPlural),
|
|
18
|
-
kebab: kebabCase(replacementSingular),
|
|
19
|
-
snakePlural: snakeCase(replacementPlural),
|
|
20
|
-
snake: snakeCase(replacementSingular),
|
|
21
|
-
screamingSnakePlural: snakeCase(replacementPlural).toUpperCase(),
|
|
22
|
-
screamingSnake: snakeCase(replacementSingular).toUpperCase(),
|
|
23
|
-
screamingKebabPlural: kebabCase(replacementPlural).toUpperCase(),
|
|
24
|
-
screamingKebab: kebabCase(replacementSingular).toUpperCase(),
|
|
25
|
-
};
|
|
26
|
-
// Create a map of original patterns to their replacements
|
|
27
|
-
const originalPatterns = {
|
|
28
|
-
camelPlural: 'myThings',
|
|
29
|
-
camel: 'myThing',
|
|
30
|
-
pascalPlural: 'MyThings',
|
|
31
|
-
pascal: 'MyThing',
|
|
32
|
-
kebabPlural: 'my-things',
|
|
33
|
-
kebab: 'my-thing',
|
|
34
|
-
snakePlural: 'my_things',
|
|
35
|
-
snake: 'my_thing',
|
|
36
|
-
screamingSnakePlural: 'MY_THINGS',
|
|
37
|
-
screamingSnake: 'MY_THING',
|
|
38
|
-
screamingKebabPlural: 'MY-THINGS',
|
|
39
|
-
screamingKebab: 'MY-THING',
|
|
40
|
-
};
|
|
41
|
-
// Replace all occurrences in the code
|
|
42
|
-
Object.keys(originalPatterns).forEach((key) => {
|
|
43
|
-
const pattern = originalPatterns[key];
|
|
44
|
-
const replacementValue = replacements[key];
|
|
45
|
-
const regex = new RegExp(pattern, 'g');
|
|
46
|
-
code = code.replace(regex, replacementValue);
|
|
47
|
-
});
|
|
48
|
-
return code;
|
|
49
|
-
}
|
|
File without changes
|