vovk-cli 0.0.1-beta.3 → 0.0.1-beta.31
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 +23 -0
- package/dist/generateClient.d.mts +7 -0
- package/dist/generateClient.mjs +97 -0
- package/dist/getProjectInfo/getConfig.d.mts +3 -1
- package/dist/getProjectInfo/getConfig.mjs +19 -15
- package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +4 -0
- package/dist/getProjectInfo/getConfigAbsolutePaths.mjs +22 -0
- package/dist/getProjectInfo/getRelativeSrcRoot.d.mts +3 -0
- package/dist/getProjectInfo/{getSrcRoot.mjs → getRelativeSrcRoot.mjs} +3 -4
- package/dist/getProjectInfo/getUserConfig.d.mts +8 -0
- package/dist/getProjectInfo/getUserConfig.mjs +19 -0
- package/dist/getProjectInfo/index.d.mts +5 -7
- package/dist/getProjectInfo/index.mjs +13 -22
- package/dist/index.d.mts +21 -1
- package/dist/index.mjs +52 -17
- package/dist/init/checkTSConfigForExperimentalDecorators.d.mts +1 -0
- package/dist/init/checkTSConfigForExperimentalDecorators.mjs +15 -0
- package/dist/init/createConfig.d.mts +7 -0
- package/dist/init/createConfig.mjs +38 -0
- package/dist/init/getTemplateFilesFromPackage.d.mts +6 -0
- package/dist/init/getTemplateFilesFromPackage.mjs +76 -0
- package/dist/init/index.d.mts +9 -0
- package/dist/init/index.mjs +251 -0
- package/dist/init/installDependencies.d.mts +9 -0
- package/dist/init/installDependencies.mjs +84 -0
- package/dist/init/updateNPMScripts.d.mts +1 -0
- package/dist/init/updateNPMScripts.mjs +13 -0
- package/dist/init/updateTSConfig.d.mts +1 -0
- package/dist/init/updateTSConfig.mjs +14 -0
- package/dist/locateSegments.mjs +2 -2
- package/dist/new/addClassToSegmentCode.d.mts +6 -0
- package/dist/new/addClassToSegmentCode.mjs +32 -0
- package/dist/new/addCommonTerms.d.mts +1 -0
- package/dist/new/addCommonTerms.mjs +91 -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 +90 -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/postinstall.mjs +2 -2
- package/dist/types.d.mts +10 -4
- package/dist/utils/chalkHighlightThing.d.mts +1 -0
- package/dist/utils/chalkHighlightThing.mjs +4 -0
- package/dist/utils/formatLoggedSegmentName.d.mts +4 -0
- package/dist/utils/formatLoggedSegmentName.mjs +7 -0
- package/dist/utils/getFileSystemEntryType.d.mts +5 -0
- package/dist/utils/getFileSystemEntryType.mjs +23 -0
- package/dist/utils/getLogger.d.mts +8 -0
- package/dist/utils/getLogger.mjs +13 -0
- package/dist/utils/prettify.d.mts +1 -0
- package/dist/utils/prettify.mjs +10 -0
- package/dist/{server/diffMetadata.d.mts → watcher/diffSchema.d.mts} +5 -5
- package/dist/{server/diffMetadata.mjs → watcher/diffSchema.mjs} +1 -1
- package/dist/watcher/ensureSchemaFiles.d.mts +3 -0
- package/dist/{server/ensureMetadataFiles.mjs → watcher/ensureSchemaFiles.mjs} +26 -25
- package/dist/watcher/index.d.mts +6 -0
- package/dist/watcher/index.mjs +279 -0
- package/dist/watcher/isMetadataEmpty.d.mts +2 -0
- package/dist/watcher/isMetadataEmpty.mjs +4 -0
- package/dist/{server → watcher}/logDiffResult.d.mts +1 -1
- package/dist/watcher/logDiffResult.mjs +90 -0
- package/dist/watcher/writeOneSchemaFile.d.mts +11 -0
- package/dist/{server/writeOneMetadataFile.mjs → watcher/writeOneSchemaFile.mjs} +7 -7
- package/package.json +25 -6
- package/templates/controller.ejs +1 -0
- package/templates/service.ejs +1 -0
- package/templates/worker.ejs +1 -0
- package/templates_old/MyThingController.c.only.template.ts +32 -0
- package/templates_old/MyThingController.c.template.ts +34 -0
- package/templates_old/MyThingService.s.template.ts +18 -0
- package/templates_old/controller.ejs +85 -0
- package/templates_old/service.ejs +9 -0
- package/templates_old/worker.ejs +9 -0
- package/templates_old/zod/MyThingController.c.only.template.ts +32 -0
- package/templates_old/zod/MyThingController.c.template.ts +39 -0
- package/templates_old/zod/MyThingService.s.template.ts +18 -0
- package/tmp_test_dir/README.md +36 -0
- package/tmp_test_dir/next.config.mjs +4 -0
- package/tmp_test_dir/package-lock.json +2914 -0
- package/tmp_test_dir/package.json +24 -0
- package/tmp_test_dir/vovk.config.js +11 -0
- package/.eslintrc.mjs +0 -20
- package/dist/getProjectInfo/getCwdPath.d.mts +0 -1
- package/dist/getProjectInfo/getCwdPath.mjs +0 -13
- package/dist/getProjectInfo/getSrcRoot.d.mts +0 -1
- package/dist/getProjectInfo/readConfig.d.mts +0 -3
- package/dist/getProjectInfo/readConfig.mjs +0 -37
- package/dist/init.d.mts +0 -2
- package/dist/init.mjs +0 -171
- package/dist/server/createMetadataServer.d.mts +0 -5
- package/dist/server/createMetadataServer.mjs +0 -25
- package/dist/server/ensureMetadataFiles.d.mts +0 -3
- package/dist/server/generateClient.d.mts +0 -7
- package/dist/server/generateClient.mjs +0 -92
- package/dist/server/index.d.mts +0 -6
- package/dist/server/index.mjs +0 -256
- package/dist/server/isMetadataEmpty.d.mts +0 -2
- package/dist/server/isMetadataEmpty.mjs +0 -4
- package/dist/server/logDiffResult.mjs +0 -78
- package/dist/server/writeOneMetadataFile.d.mts +0 -11
- package/dist/utils/fileExists.d.mts +0 -1
- package/dist/utils/fileExists.mjs +0 -10
- package/src/getProjectInfo/directoryExists.mts +0 -10
- package/src/getProjectInfo/getConfig.mts +0 -29
- package/src/getProjectInfo/getCwdPath.mts +0 -15
- package/src/getProjectInfo/getSrcRoot.mts +0 -14
- package/src/getProjectInfo/index.mts +0 -57
- package/src/getProjectInfo/readConfig.mts +0 -44
- package/src/index.mts +0 -113
- package/src/init.mts +0 -174
- package/src/locateSegments.mts +0 -40
- package/src/postinstall.mts +0 -27
- package/src/server/createMetadataServer.mts +0 -30
- package/src/server/diffMetadata.mts +0 -110
- package/src/server/ensureMetadataFiles.mts +0 -92
- package/src/server/generateClient.mts +0 -108
- package/src/server/index.mts +0 -307
- package/src/server/isMetadataEmpty.mts +0 -6
- package/src/server/logDiffResult.mts +0 -114
- package/src/server/writeOneMetadataFile.mts +0 -44
- package/src/types.mts +0 -58
- package/src/utils/debounceWithArgs.mts +0 -22
- package/src/utils/fileExists.mts +0 -10
- package/src/utils/getAvailablePort.mts +0 -50
- package/test/data/segments/[[...vovk]]/route.ts +0 -0
- package/test/data/segments/bar/[[...custom]]/route.ts +0 -0
- package/test/data/segments/baz/[[...vovk]]/noroute.ts +0 -0
- package/test/data/segments/foo/[[...vovk]]/route.ts +0 -0
- package/test/data/segments/garply/waldo/route.ts +0 -0
- package/test/data/segments/grault/xxxx/[[...vovk]]/noroute.ts +0 -0
- package/test/data/segments/quux/corge/[[...vovk]]/route.ts +0 -0
- package/test/index.ts +0 -3
- package/test/metadata-diff.test.mts +0 -300
- package/test/metadata-write.test.mts +0 -82
- package/test/utils.test.mts +0 -49
- package/tsconfig.json +0 -17
- package/tsconfig.test.json +0 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-present Andrii Gubanov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProjectInfo } from './getProjectInfo/index.mjs';
|
|
2
|
+
import type { Segment } from './locateSegments.mjs';
|
|
3
|
+
import type { VovkSchema } from 'vovk';
|
|
4
|
+
export default function generateClient(projectInfo: ProjectInfo, segments: Segment[], segmentsSchema: Record<string, VovkSchema>): Promise<{
|
|
5
|
+
written: boolean;
|
|
6
|
+
path: string;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import formatLoggedSegmentName from './utils/formatLoggedSegmentName.mjs';
|
|
4
|
+
import prettify from './utils/prettify.mjs';
|
|
5
|
+
export default async function generateClient(projectInfo, segments, segmentsSchema) {
|
|
6
|
+
const { config, cwd, log, validateOnClientImportPath, apiEntryPoint, fetcherClientImportPath, schemaOutImportPath } = projectInfo;
|
|
7
|
+
const now = Date.now();
|
|
8
|
+
const clientoOutDirAbsolutePath = path.join(cwd, config.clientOutDir);
|
|
9
|
+
let dts = `// auto-generated
|
|
10
|
+
/* eslint-disable */
|
|
11
|
+
import type { clientizeController } from 'vovk/client';
|
|
12
|
+
import type { promisifyWorker } from 'vovk/worker';
|
|
13
|
+
import type { VovkClientFetcher } from 'vovk/client';
|
|
14
|
+
import type fetcher from '${fetcherClientImportPath}';
|
|
15
|
+
|
|
16
|
+
`;
|
|
17
|
+
let js = `// auto-generated
|
|
18
|
+
/* eslint-disable */
|
|
19
|
+
const { clientizeController } = require('vovk/client');
|
|
20
|
+
const { promisifyWorker } = require('vovk/worker');
|
|
21
|
+
const { default: fetcher } = require('${fetcherClientImportPath}');
|
|
22
|
+
const schema = require('${schemaOutImportPath}');
|
|
23
|
+
`;
|
|
24
|
+
let ts = `// auto-generated
|
|
25
|
+
/* eslint-disable */
|
|
26
|
+
import { clientizeController } from 'vovk/client';
|
|
27
|
+
import { promisifyWorker } from 'vovk/worker';
|
|
28
|
+
import type { VovkClientFetcher } from 'vovk/client';
|
|
29
|
+
import fetcher from '${fetcherClientImportPath}';
|
|
30
|
+
import schema from '${schemaOutImportPath}';
|
|
31
|
+
|
|
32
|
+
`;
|
|
33
|
+
for (let i = 0; i < segments.length; i++) {
|
|
34
|
+
const { routeFilePath, segmentName } = segments[i];
|
|
35
|
+
const schema = segmentsSchema[segmentName];
|
|
36
|
+
if (!schema) {
|
|
37
|
+
throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
|
|
38
|
+
}
|
|
39
|
+
if (!schema.emitSchema)
|
|
40
|
+
continue;
|
|
41
|
+
const importRouteFilePath = path.relative(config.clientOutDir, routeFilePath);
|
|
42
|
+
dts += `import type { Controllers as Controllers${i}, Workers as Workers${i} } from "${importRouteFilePath}";\n`;
|
|
43
|
+
ts += `import type { Controllers as Controllers${i}, Workers as Workers${i} } from "${importRouteFilePath}";\n`;
|
|
44
|
+
}
|
|
45
|
+
dts += `
|
|
46
|
+
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
47
|
+
`;
|
|
48
|
+
ts += `
|
|
49
|
+
${validateOnClientImportPath ? `import validateOnClient from '${validateOnClientImportPath}';\n` : '\nconst validateOnClient = undefined;'}
|
|
50
|
+
type Options = typeof fetcher extends VovkClientFetcher<infer U> ? U : never;
|
|
51
|
+
const prefix = '${apiEntryPoint}';
|
|
52
|
+
`;
|
|
53
|
+
js += `
|
|
54
|
+
const { default: validateOnClient = null } = ${validateOnClientImportPath ? `require('${validateOnClientImportPath}')` : '{}'};
|
|
55
|
+
const prefix = '${apiEntryPoint}';
|
|
56
|
+
`;
|
|
57
|
+
for (let i = 0; i < segments.length; i++) {
|
|
58
|
+
const { segmentName } = segments[i];
|
|
59
|
+
const schema = segmentsSchema[segmentName];
|
|
60
|
+
if (!schema) {
|
|
61
|
+
throw new Error(`Unable to generate client. No schema found for ${formatLoggedSegmentName(segmentName)}`);
|
|
62
|
+
}
|
|
63
|
+
if (!schema.emitSchema)
|
|
64
|
+
continue;
|
|
65
|
+
for (const key of Object.keys(schema.controllers)) {
|
|
66
|
+
dts += `export const ${key}: ReturnType<typeof clientizeController<Controllers${i}["${key}"], Options>>;\n`;
|
|
67
|
+
js += `exports.${key} = clientizeController(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { prefix } });\n`;
|
|
68
|
+
ts += `export const ${key} = clientizeController<Controllers${i}["${key}"], Options>(schema['${segmentName}'].controllers.${key}, '${segmentName}', { fetcher, validateOnClient, defaultOptions: { prefix } });\n`;
|
|
69
|
+
}
|
|
70
|
+
for (const key of Object.keys(schema.workers)) {
|
|
71
|
+
dts += `export const ${key}: ReturnType<typeof promisifyWorker<Workers${i}["${key}"]>>;\n`;
|
|
72
|
+
js += `exports.${key} = promisifyWorker(null, schema['${segmentName}'].workers.${key});\n`;
|
|
73
|
+
ts += `export const ${key} = promisifyWorker<Workers${i}["${key}"]>(null, schema['${segmentName}'].workers.${key});\n`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
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
|
+
if (config.prettifyClient) {
|
|
83
|
+
js = await prettify(js, localJsAbsolutePath);
|
|
84
|
+
dts = await prettify(dts, localDtsAbsolutePath);
|
|
85
|
+
ts = await prettify(ts, localTsAbsolutePath);
|
|
86
|
+
}
|
|
87
|
+
if (existingJs === js && existingDts === dts && existingTs === ts) {
|
|
88
|
+
log.debug(`Client is up to date and doesn't need to be regenerated (${Date.now() - now}ms)`);
|
|
89
|
+
return { written: false, path: clientoOutDirAbsolutePath };
|
|
90
|
+
}
|
|
91
|
+
await fs.mkdir(clientoOutDirAbsolutePath, { recursive: true });
|
|
92
|
+
await fs.writeFile(localJsAbsolutePath, js);
|
|
93
|
+
await fs.writeFile(localDtsAbsolutePath, dts);
|
|
94
|
+
await fs.writeFile(localTsAbsolutePath, ts);
|
|
95
|
+
log.info(`Client generated in ${Date.now() - now}ms`);
|
|
96
|
+
return { written: true, path: clientoOutDirAbsolutePath };
|
|
97
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { VovkConfig } from '../types.mjs';
|
|
2
|
-
export default function getConfig({ clientOutDir }: {
|
|
2
|
+
export default function getConfig({ clientOutDir, cwd }: {
|
|
3
3
|
clientOutDir?: string;
|
|
4
|
+
cwd: string;
|
|
4
5
|
}): Promise<{
|
|
5
6
|
config: Required<VovkConfig>;
|
|
6
7
|
srcRoot: string;
|
|
8
|
+
configAbsolutePaths: string[];
|
|
7
9
|
}>;
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import path from 'path';
|
|
5
|
-
export default async function getConfig({ clientOutDir }) {
|
|
1
|
+
import getUserConfig from './getUserConfig.mjs';
|
|
2
|
+
import getRelativeSrcRoot from './getRelativeSrcRoot.mjs';
|
|
3
|
+
export default async function getConfig({ clientOutDir, cwd }) {
|
|
6
4
|
const env = process.env;
|
|
7
|
-
const userConfig = await
|
|
8
|
-
const srcRoot = await
|
|
9
|
-
const cwd = process.cwd();
|
|
5
|
+
const { userConfig, configAbsolutePaths } = await getUserConfig({ cwd });
|
|
6
|
+
const srcRoot = await getRelativeSrcRoot({ cwd });
|
|
10
7
|
const config = {
|
|
11
|
-
modulesDir:
|
|
12
|
-
validateOnClient:
|
|
13
|
-
validationLibrary:
|
|
14
|
-
fetcher:
|
|
15
|
-
|
|
16
|
-
clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? userConfig.clientOutDir ?? './node_modules/.vovk',
|
|
8
|
+
modulesDir: env.VOVK_MODULES_DIR ?? userConfig.modulesDir ?? './' + [srcRoot, 'modules'].filter(Boolean).join('/'),
|
|
9
|
+
validateOnClient: env.VOVK_VALIDATE_ON_CLIENT ?? userConfig.validateOnClient ?? null,
|
|
10
|
+
validationLibrary: env.VOVK_VALIDATION_LIBRARY ?? userConfig.validationLibrary ?? null,
|
|
11
|
+
fetcher: env.VOVK_FETCHER ?? userConfig.fetcher ?? 'vovk/client/defaultFetcher',
|
|
12
|
+
schemaOutDir: env.VOVK_SCHEMA_OUT_DIR ?? userConfig.schemaOutDir ?? './.vovk-schema',
|
|
13
|
+
clientOutDir: clientOutDir ?? env.VOVK_CLIENT_OUT_DIR ?? userConfig.clientOutDir ?? './node_modules/.vovk-client',
|
|
17
14
|
origin: (env.VOVK_ORIGIN ?? userConfig.origin ?? '').replace(/\/$/, ''), // Remove trailing slash
|
|
18
15
|
rootEntry: env.VOVK_ROOT_ENTRY ?? userConfig.rootEntry ?? 'api',
|
|
19
16
|
rootSegmentModulesDirName: env.VOVK_ROOT_SEGMENT_MODULES_DIR_NAME ?? userConfig.rootSegmentModulesDirName ?? '',
|
|
20
17
|
logLevel: env.VOVK_LOG_LEVEL ?? userConfig.logLevel ?? 'debug', // TODO: change to 'warn' when v3 is ready
|
|
18
|
+
prettifyClient: userConfig.prettifyClient ?? false,
|
|
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
|
+
},
|
|
21
25
|
};
|
|
22
|
-
return { config, srcRoot };
|
|
26
|
+
return { config, srcRoot, configAbsolutePaths };
|
|
23
27
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export default async function getConfigAbsolutePaths({ cwd, relativePath, }) {
|
|
4
|
+
const rootDir = path.resolve(cwd, relativePath || '');
|
|
5
|
+
const baseName = 'vovk.config';
|
|
6
|
+
const extensions = ['cjs', 'mjs', 'js'];
|
|
7
|
+
const dirs = [path.join(rootDir, '.config'), rootDir];
|
|
8
|
+
const configs = [];
|
|
9
|
+
for (const ext of extensions) {
|
|
10
|
+
for (const dir of dirs) {
|
|
11
|
+
const filePath = path.join(dir, `${baseName}.${ext}`);
|
|
12
|
+
try {
|
|
13
|
+
await fs.stat(filePath);
|
|
14
|
+
configs.push(filePath); // Return the path if the file exists
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Empty
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return configs;
|
|
22
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import directoryExists from './directoryExists.mjs';
|
|
3
|
-
export default async function
|
|
4
|
-
const cwd = process.cwd();
|
|
3
|
+
export default async function getRelativeSrcRoot({ cwd }) {
|
|
5
4
|
// Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
|
|
6
5
|
if (await directoryExists(path.join(cwd, 'app'))) {
|
|
7
|
-
return
|
|
6
|
+
return '.';
|
|
8
7
|
}
|
|
9
8
|
else if (await directoryExists(path.join(cwd, 'src/app'))) {
|
|
10
|
-
return
|
|
9
|
+
return './src';
|
|
11
10
|
}
|
|
12
11
|
throw new Error(`Could not find app router directory. Check Next.js docs for more info.`);
|
|
13
12
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import getConfigAbsolutePaths from './getConfigAbsolutePaths.mjs';
|
|
2
|
+
async function getUserConfig({ cwd, }) {
|
|
3
|
+
const configAbsolutePaths = await getConfigAbsolutePaths({ cwd });
|
|
4
|
+
let userConfig = {};
|
|
5
|
+
if (!configAbsolutePaths.length) {
|
|
6
|
+
return { userConfig, configAbsolutePaths };
|
|
7
|
+
}
|
|
8
|
+
const configPath = configAbsolutePaths[0];
|
|
9
|
+
try {
|
|
10
|
+
const cacheBuster = Date.now();
|
|
11
|
+
({ default: userConfig } = (await import(`${configPath}?cache=${cacheBuster}`)));
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
// eslint-disable-next-line no-console
|
|
15
|
+
console.error('🐺 ❌ Error reading config file:', e.message);
|
|
16
|
+
}
|
|
17
|
+
return { userConfig, configAbsolutePaths };
|
|
18
|
+
}
|
|
19
|
+
export default getUserConfig;
|
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
import loglevel from 'loglevel';
|
|
2
1
|
export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
|
|
3
|
-
export default function getProjectInfo({ port: givenPort, clientOutDir, }?: {
|
|
2
|
+
export default function getProjectInfo({ port: givenPort, clientOutDir, cwd, }?: {
|
|
4
3
|
port?: number;
|
|
5
4
|
clientOutDir?: string;
|
|
5
|
+
cwd?: string;
|
|
6
6
|
}): Promise<{
|
|
7
7
|
cwd: string;
|
|
8
8
|
port: string;
|
|
9
|
-
vovkPort: string;
|
|
10
9
|
apiEntryPoint: string;
|
|
11
10
|
apiDir: string;
|
|
12
11
|
srcRoot: string;
|
|
13
|
-
|
|
14
|
-
metadataOutImportPath: string;
|
|
15
|
-
clientOutFullPath: string;
|
|
12
|
+
schemaOutImportPath: string;
|
|
16
13
|
fetcherClientImportPath: string;
|
|
14
|
+
validateOnClientImportPath: string | null;
|
|
17
15
|
config: Required<import("../types.mjs").VovkConfig>;
|
|
18
16
|
log: {
|
|
19
17
|
info: (msg: string) => void;
|
|
20
18
|
warn: (msg: string) => void;
|
|
21
19
|
error: (msg: string) => void;
|
|
22
20
|
debug: (msg: string) => void;
|
|
23
|
-
raw: loglevel.RootLogger;
|
|
21
|
+
raw: import("loglevel").RootLogger;
|
|
24
22
|
};
|
|
25
23
|
}>;
|
|
@@ -1,42 +1,33 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import loglevel from 'loglevel';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
2
|
import getConfig from './getConfig.mjs';
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import getLogger from '../utils/getLogger.mjs';
|
|
4
|
+
export default async function getProjectInfo({ port: givenPort, clientOutDir, cwd = process.cwd(), } = {}) {
|
|
7
5
|
const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
|
|
8
6
|
// Make PORT available to the config file at getConfig
|
|
9
7
|
process.env.PORT = port;
|
|
10
|
-
const
|
|
11
|
-
const { config, srcRoot } = await getConfig({ clientOutDir });
|
|
12
|
-
const vovkPort = env.VOVK_PORT || (parseInt(port) + 6969).toString();
|
|
8
|
+
const { config, srcRoot, configAbsolutePaths } = await getConfig({ clientOutDir, cwd });
|
|
13
9
|
const apiEntryPoint = `${config.origin ?? ''}/${config.rootEntry}`;
|
|
14
10
|
const apiDir = path.join(srcRoot, 'app', config.rootEntry);
|
|
15
|
-
const
|
|
16
|
-
const metadataOutImportPath = path.relative(config.clientOutDir, metadataOutFullPath);
|
|
11
|
+
const schemaOutImportPath = path.relative(config.clientOutDir, config.schemaOutDir);
|
|
17
12
|
const fetcherClientImportPath = config.fetcher.startsWith('.')
|
|
18
13
|
? path.relative(config.clientOutDir, config.fetcher)
|
|
19
14
|
: config.fetcher;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
28
|
-
loglevel.setLevel(config.logLevel);
|
|
15
|
+
const validateOnClientImportPath = config.validateOnClient?.startsWith('.')
|
|
16
|
+
? path.relative(config.clientOutDir, config.validateOnClient)
|
|
17
|
+
: config.validateOnClient;
|
|
18
|
+
const log = getLogger(config.logLevel);
|
|
19
|
+
if (configAbsolutePaths.length > 1) {
|
|
20
|
+
log.warn(`Multiple config files found. Using the first one: ${configAbsolutePaths[0]}`);
|
|
21
|
+
}
|
|
29
22
|
return {
|
|
30
23
|
cwd,
|
|
31
24
|
port,
|
|
32
|
-
vovkPort,
|
|
33
25
|
apiEntryPoint,
|
|
34
26
|
apiDir,
|
|
35
27
|
srcRoot,
|
|
36
|
-
|
|
37
|
-
metadataOutImportPath,
|
|
38
|
-
clientOutFullPath,
|
|
28
|
+
schemaOutImportPath,
|
|
39
29
|
fetcherClientImportPath,
|
|
30
|
+
validateOnClientImportPath,
|
|
40
31
|
config,
|
|
41
32
|
log,
|
|
42
33
|
};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import type { LogLevelNames } from 'loglevel';
|
|
4
|
+
import type { VovkConfig, VovkEnv } from './types.mjs';
|
|
3
5
|
export type { VovkConfig, VovkEnv };
|
|
6
|
+
export interface InitOptions {
|
|
7
|
+
yes?: boolean;
|
|
8
|
+
logLevel: LogLevelNames;
|
|
9
|
+
useNpm?: boolean;
|
|
10
|
+
useYarn?: boolean;
|
|
11
|
+
usePnpm?: boolean;
|
|
12
|
+
useBun?: boolean;
|
|
13
|
+
skipInstall?: boolean;
|
|
14
|
+
updateTsConfig?: boolean;
|
|
15
|
+
updateScripts?: 'implicit' | 'explicit';
|
|
16
|
+
validationLibrary?: string;
|
|
17
|
+
validateOnClient?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface NewOptions {
|
|
20
|
+
dryRun: boolean;
|
|
21
|
+
}
|
|
22
|
+
declare const program: Command;
|
|
23
|
+
export declare function initProgram(p: typeof program, command: string): Command;
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
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 { VovkCLIServer } from './server/index.mjs';
|
|
6
7
|
import getProjectInfo from './getProjectInfo/index.mjs';
|
|
7
|
-
import generateClient from './
|
|
8
|
+
import generateClient from './generateClient.mjs';
|
|
8
9
|
import locateSegments from './locateSegments.mjs';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
10
|
+
import { VovkCLIWatcher } from './watcher/index.mjs';
|
|
11
|
+
import { Init } from './init/index.mjs';
|
|
12
|
+
import newComponents from './new/index.mjs';
|
|
11
13
|
const program = new Command();
|
|
12
14
|
const packageJSON = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
|
|
13
15
|
program.name('vovk').description('Vovk CLI').version(packageJSON.version);
|
|
14
16
|
program
|
|
15
17
|
.command('dev')
|
|
16
|
-
.description('Start
|
|
17
|
-
.option('--project <path>', 'Path to Next.js project', process.cwd())
|
|
18
|
+
.description('Start schema watcher (optional flag --next-dev to start it with Next.js)')
|
|
18
19
|
.option('--client-out <path>', 'Path to client output directory')
|
|
19
|
-
.option('--next-dev', 'Start
|
|
20
|
+
.option('--next-dev', 'Start schema watcher and Next.js with automatic port allocation', false)
|
|
20
21
|
.allowUnknownOption(true)
|
|
21
22
|
.action(async (options, command) => {
|
|
22
23
|
const portAttempts = 30;
|
|
@@ -34,12 +35,12 @@ program
|
|
|
34
35
|
if (options.nextDev) {
|
|
35
36
|
const { result } = concurrently([
|
|
36
37
|
{
|
|
37
|
-
command: `node ${import.meta.dirname}/
|
|
38
|
-
name: 'Vovk.ts
|
|
39
|
-
env: Object.assign({ PORT,
|
|
38
|
+
command: `node ${import.meta.dirname}/watcher/index.mjs`,
|
|
39
|
+
name: 'Vovk.ts Schema Watcher',
|
|
40
|
+
env: Object.assign({ PORT, __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true' }, options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}),
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
|
-
command: `
|
|
43
|
+
command: `npx next dev ${command.args.join(' ')}`,
|
|
43
44
|
name: 'Next.js Development Server',
|
|
44
45
|
env: { PORT },
|
|
45
46
|
},
|
|
@@ -51,12 +52,11 @@ program
|
|
|
51
52
|
await result;
|
|
52
53
|
}
|
|
53
54
|
finally {
|
|
54
|
-
//
|
|
55
|
-
console.log('🐺 Exiting...');
|
|
55
|
+
// do nothing, all processes are killed
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
else {
|
|
59
|
-
void new
|
|
59
|
+
void new VovkCLIWatcher().start({ clientOutDir: options.clientOut });
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
program
|
|
@@ -65,14 +65,49 @@ program
|
|
|
65
65
|
.option('--client-out <path>', 'Path to output directory')
|
|
66
66
|
.action(async (options) => {
|
|
67
67
|
const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
68
|
+
const { cwd, config, apiDir } = projectInfo;
|
|
69
|
+
const segments = await locateSegments(apiDir);
|
|
70
|
+
const schemaOutAbsolutePath = path.join(cwd, config.schemaOutDir);
|
|
71
|
+
const schema = (await import(path.join(schemaOutAbsolutePath, 'index.js')));
|
|
72
|
+
await generateClient(projectInfo, segments, schema.default);
|
|
71
73
|
});
|
|
74
|
+
// reused at vovk-init
|
|
75
|
+
export function initProgram(p, command) {
|
|
76
|
+
return p
|
|
77
|
+
.command(command + '[prefix]')
|
|
78
|
+
.description('Initialize Vovk project')
|
|
79
|
+
.option('-Y, --yes', 'Skip all prompts and use default values')
|
|
80
|
+
.option('--log-level <level>', 'Set log level', 'debug') // TODO change to 'info'
|
|
81
|
+
.option('--use-npm', 'Use npm as package manager')
|
|
82
|
+
.option('--use-yarn', 'Use yarn as package manager')
|
|
83
|
+
.option('--use-pnpm', 'Use pnpm as package manager')
|
|
84
|
+
.option('--use-bun', 'Use bun as package manager')
|
|
85
|
+
.option('--skip-install', 'Skip installing dependencies')
|
|
86
|
+
.option('--update-ts-config', 'Update tsconfig.json')
|
|
87
|
+
.option('--update-scripts <mode>', 'Update package.json scripts (implicit or explicit)')
|
|
88
|
+
.option('--validation-library <library>', 'Validation library to use')
|
|
89
|
+
.option('--validate-on-client', 'Validate on client')
|
|
90
|
+
.action((prefix = '.', options) => new Init().main(prefix, options));
|
|
91
|
+
}
|
|
92
|
+
initProgram(program, 'init ');
|
|
93
|
+
program
|
|
94
|
+
.command('new [components...]')
|
|
95
|
+
.alias('n')
|
|
96
|
+
.description('Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment')
|
|
97
|
+
.option('--dry-run', 'Do not write files to disk')
|
|
98
|
+
.action((components, options) => newComponents(components, options));
|
|
72
99
|
program
|
|
73
100
|
.command('help')
|
|
74
101
|
.description('Show help message')
|
|
75
102
|
.action(() => program.help());
|
|
103
|
+
/*
|
|
104
|
+
vovk new segment [segmentName]
|
|
105
|
+
vovk new controller service [segmentName/]moduleName
|
|
106
|
+
vovk new c s w [segmentName/]moduleName
|
|
107
|
+
|
|
108
|
+
vovk c s w userApi/user
|
|
109
|
+
vovk new c s w user
|
|
110
|
+
*/
|
|
76
111
|
program.parse(process.argv);
|
|
77
112
|
if (!process.argv.slice(2).length) {
|
|
78
113
|
program.outputHelp();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function checkTSConfigForExperimentalDecorators(root: string): Promise<boolean>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import * as jsonc from 'jsonc-parser';
|
|
4
|
+
export default async function checkTSConfigForExperimentalDecorators(root) {
|
|
5
|
+
const tsconfigPath = path.resolve(root, 'tsconfig.json');
|
|
6
|
+
let tsconfigContent;
|
|
7
|
+
try {
|
|
8
|
+
tsconfigContent = await fs.readFile(tsconfigPath, 'utf8');
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw new Error(`Failed to read tsconfig.json at ${tsconfigPath}. You can run "npx tsc --init" to create it. ${String(error)}`);
|
|
12
|
+
}
|
|
13
|
+
const tsconfig = jsonc.parse(tsconfigContent);
|
|
14
|
+
return !!tsconfig?.compilerOptions?.experimentalDecorators;
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type getLogger from '../utils/getLogger.mjs';
|
|
2
|
+
import type { InitOptions } from '../index.mjs';
|
|
3
|
+
export default function createConfig({ root, log, options: { validationLibrary, validateOnClient }, }: {
|
|
4
|
+
root: string;
|
|
5
|
+
log: ReturnType<typeof getLogger>;
|
|
6
|
+
options: Pick<InitOptions, 'validationLibrary' | 'validateOnClient'>;
|
|
7
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import getFileSystemEntryType, { FileSystemEntryType } from '../utils/getFileSystemEntryType.mjs';
|
|
4
|
+
import getTemplateFilesFromPackage from './getTemplateFilesFromPackage.mjs';
|
|
5
|
+
import prettify from '../utils/prettify.mjs';
|
|
6
|
+
export default async function createConfig({ root, log, options: { validationLibrary, validateOnClient }, }) {
|
|
7
|
+
const config = {};
|
|
8
|
+
const dotConfigPath = path.join(root, '.config');
|
|
9
|
+
const dir = (await getFileSystemEntryType(dotConfigPath)) === FileSystemEntryType.DIRECTORY ? dotConfigPath : root;
|
|
10
|
+
const isModule = await fs
|
|
11
|
+
.readFile(path.join(root, 'package.json'), 'utf-8')
|
|
12
|
+
.then((content) => JSON.parse(content).type === 'module');
|
|
13
|
+
const configAbsolutePath = path.join(dir, isModule ? 'vovk.config.mjs' : 'vovk.config.js');
|
|
14
|
+
config.validationLibrary = validationLibrary;
|
|
15
|
+
if (validateOnClient) {
|
|
16
|
+
config.validateOnClient = `${validationLibrary}/validateOnClient`;
|
|
17
|
+
}
|
|
18
|
+
const templates = {
|
|
19
|
+
controller: 'vovk-cli/templates/controller.ejs',
|
|
20
|
+
service: 'vovk-cli/templates/service.ejs',
|
|
21
|
+
worker: 'vovk-cli/templates/worker.ejs',
|
|
22
|
+
};
|
|
23
|
+
if (validationLibrary) {
|
|
24
|
+
try {
|
|
25
|
+
const validationTemplates = await getTemplateFilesFromPackage(validationLibrary);
|
|
26
|
+
Object.assign(templates, validationTemplates);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
log.error(`Failed to fetch validation library templates: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
config.templates = templates;
|
|
33
|
+
const configStr = await prettify(`/** @type {import('vovk-cli').VovkConfig} */
|
|
34
|
+
const config = ${JSON.stringify(config, null, 2)};
|
|
35
|
+
${isModule ? '\nexport default config;' : 'module.exports = config;'}`, configAbsolutePath);
|
|
36
|
+
await fs.writeFile(configAbsolutePath, configStr, 'utf-8');
|
|
37
|
+
log.info(`Config created at ${configAbsolutePath}`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieves a list of files in the 'templates' folder of an NPM package.
|
|
3
|
+
* @param packageName - The name of the NPM package.
|
|
4
|
+
* @returns A promise that resolves to an array of file paths.
|
|
5
|
+
*/
|
|
6
|
+
export default function getTemplatesFiles(packageName: string): Promise<Record<string, string>>;
|