vovk-cli 0.0.1-beta.0
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/.eslintrc.js +20 -0
- package/README.md +1 -0
- package/dist/getProjectInfo/directoryExists.d.ts +1 -0
- package/dist/getProjectInfo/directoryExists.js +16 -0
- package/dist/getProjectInfo/getConfig.d.ts +7 -0
- package/dist/getProjectInfo/getConfig.js +29 -0
- package/dist/getProjectInfo/getCwdPath.d.ts +1 -0
- package/dist/getProjectInfo/getCwdPath.js +19 -0
- package/dist/getProjectInfo/getSrcRoot.d.ts +1 -0
- package/dist/getProjectInfo/getSrcRoot.js +19 -0
- package/dist/getProjectInfo/index.d.ts +48 -0
- package/dist/getProjectInfo/index.js +78 -0
- package/dist/getProjectInfo/readConfig.d.ts +3 -0
- package/dist/getProjectInfo/readConfig.js +73 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +104 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +173 -0
- package/dist/locateSegments.d.ts +5 -0
- package/dist/locateSegments.js +58 -0
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +27 -0
- package/dist/server/createMetadataServer.d.ts +5 -0
- package/dist/server/createMetadataServer.js +31 -0
- package/dist/server/diffMetadata.d.ts +43 -0
- package/dist/server/diffMetadata.js +77 -0
- package/dist/server/ensureMetadataFiles.d.ts +3 -0
- package/dist/server/ensureMetadataFiles.js +100 -0
- package/dist/server/generateClient.d.ts +7 -0
- package/dist/server/generateClient.js +98 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +285 -0
- package/dist/server/isMetadataEmpty.d.ts +2 -0
- package/dist/server/isMetadataEmpty.js +7 -0
- package/dist/server/logDiffResult.d.ts +3 -0
- package/dist/server/logDiffResult.js +84 -0
- package/dist/server/writeOneMetadataFile.d.ts +11 -0
- package/dist/server/writeOneMetadataFile.js +34 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.js +2 -0
- package/dist/utils/debounceWithArgs.d.ts +2 -0
- package/dist/utils/debounceWithArgs.js +20 -0
- package/dist/utils/fileExists.d.ts +1 -0
- package/dist/utils/fileExists.js +16 -0
- package/dist/utils/getAvailablePort.d.ts +10 -0
- package/dist/utils/getAvailablePort.js +47 -0
- package/package.json +43 -0
- package/src/getProjectInfo/directoryExists.ts +10 -0
- package/src/getProjectInfo/getConfig.ts +29 -0
- package/src/getProjectInfo/getCwdPath.ts +15 -0
- package/src/getProjectInfo/getSrcRoot.ts +14 -0
- package/src/getProjectInfo/index.ts +63 -0
- package/src/getProjectInfo/readConfig.ts +50 -0
- package/src/index.ts +112 -0
- package/src/init.ts +174 -0
- package/src/locateSegments.ts +40 -0
- package/src/postinstall.ts +27 -0
- package/src/server/createMetadataServer.ts +30 -0
- package/src/server/diffMetadata.ts +110 -0
- package/src/server/ensureMetadataFiles.ts +92 -0
- package/src/server/generateClient.ts +108 -0
- package/src/server/index.ts +306 -0
- package/src/server/isMetadataEmpty.ts +6 -0
- package/src/server/logDiffResult.ts +114 -0
- package/src/server/writeOneMetadataFile.ts +44 -0
- package/src/types.ts +58 -0
- package/src/utils/debounceWithArgs.ts +22 -0
- package/src/utils/fileExists.ts +10 -0
- package/src/utils/getAvailablePort.ts +50 -0
- 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 +3 -0
- package/test/metadata-diff.test.ts +300 -0
- package/test/metadata-write.test.ts +82 -0
- package/test/utils.test.ts +49 -0
- package/tsconfig.json +11 -0
- package/tsconfig.test.json +4 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
// TODO Rename
|
|
4
|
+
export default function getCwdPath<T extends string | null>(inputPath: T, baseDir = process.cwd()): T {
|
|
5
|
+
if (inputPath === null) {
|
|
6
|
+
return null as T;
|
|
7
|
+
}
|
|
8
|
+
// Check if the path is absolute
|
|
9
|
+
if (path.isAbsolute(inputPath) || inputPath.startsWith('./') || inputPath.startsWith('../')) {
|
|
10
|
+
return path.resolve(baseDir, inputPath) as T;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// If it's a module or absolute path, keep it as is
|
|
14
|
+
return inputPath;
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import directoryExists from './directoryExists';
|
|
3
|
+
|
|
4
|
+
export default async function getSrcRoot() {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
// Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
|
|
7
|
+
if (await directoryExists(path.join(cwd, 'app'))) {
|
|
8
|
+
return cwd;
|
|
9
|
+
} else if (await directoryExists(path.join(cwd, 'src/app'))) {
|
|
10
|
+
return path.join(cwd, 'src');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
throw new Error(`Could not find app router directory. Check Next.js docs for more info.`);
|
|
14
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { VovkEnv } from '../types';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import * as loglevel from 'loglevel';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import getConfig from './getConfig';
|
|
6
|
+
|
|
7
|
+
export type ProjectInfo = Awaited<ReturnType<typeof getProjectInfo>>;
|
|
8
|
+
|
|
9
|
+
// TODO: Rename all occurrences of metadata to schema
|
|
10
|
+
// TODO: Rename default API option "prefix" to "apiRoot" or just "root" (?). Also think of renaming prefix as an option to origin (?)
|
|
11
|
+
// TODO: Load config dynamically to generate client and write schema
|
|
12
|
+
// TODO: Create VovkCLIError class
|
|
13
|
+
export default async function getProjectInfo({
|
|
14
|
+
port: givenPort,
|
|
15
|
+
clientOutDir,
|
|
16
|
+
}: { port?: number; clientOutDir?: string } = {}) {
|
|
17
|
+
const env = process.env as VovkEnv;
|
|
18
|
+
const port = givenPort?.toString() ?? process.env.PORT ?? '3000';
|
|
19
|
+
|
|
20
|
+
// Make PORT available to the config file at getConfig
|
|
21
|
+
process.env.PORT = port;
|
|
22
|
+
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
const { config, srcRoot } = await getConfig({ clientOutDir });
|
|
25
|
+
const vovkPort = env.VOVK_PORT || (parseInt(port) + 6969).toString();
|
|
26
|
+
const apiEntryPoint = `${config.origin}/${config.rootEntry}`; // ??? TODO
|
|
27
|
+
const apiPrefix = `${config.origin}/${config.rootEntry}`; // ??? TODO
|
|
28
|
+
const apiDir = path.join(srcRoot, 'app', config.rootEntry);
|
|
29
|
+
|
|
30
|
+
const metadataOutFullPath = path.join(cwd, config.metadataOutDir);
|
|
31
|
+
const metadataOutImportPath = path.relative(config.clientOutDir, metadataOutFullPath);
|
|
32
|
+
const fetcherClientImportPath = config.fetcher.startsWith('.')
|
|
33
|
+
? path.relative(config.clientOutDir, config.fetcher)
|
|
34
|
+
: config.fetcher;
|
|
35
|
+
|
|
36
|
+
const clientOutFullPath = path.join(cwd, config.clientOutDir);
|
|
37
|
+
|
|
38
|
+
const log = {
|
|
39
|
+
info: (msg: string) => loglevel.info(chalk.blueBright(`🐺 ${msg}`)),
|
|
40
|
+
warn: (msg: string) => loglevel.warn(chalk.yellowBright(`🐺 ${msg}`)),
|
|
41
|
+
error: (msg: string) => loglevel.error(chalk.redBright(`🐺 ${msg}`)),
|
|
42
|
+
debug: (msg: string) => loglevel.debug(chalk.gray(`🐺 ${msg}`)),
|
|
43
|
+
raw: loglevel,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
loglevel.setLevel(config.logLevel);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
cwd,
|
|
50
|
+
port,
|
|
51
|
+
vovkPort,
|
|
52
|
+
apiEntryPoint,
|
|
53
|
+
apiPrefix,
|
|
54
|
+
apiDir,
|
|
55
|
+
srcRoot,
|
|
56
|
+
metadataOutFullPath,
|
|
57
|
+
metadataOutImportPath,
|
|
58
|
+
clientOutFullPath,
|
|
59
|
+
fetcherClientImportPath,
|
|
60
|
+
config,
|
|
61
|
+
log,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { VovkConfig } from '../types';
|
|
4
|
+
|
|
5
|
+
async function findConfigPath(): Promise<string | null> {
|
|
6
|
+
const rootDir = process.cwd();
|
|
7
|
+
const baseName = 'vovk.config';
|
|
8
|
+
const extensions = ['cjs', 'mjs', 'js'];
|
|
9
|
+
|
|
10
|
+
for (const ext of extensions) {
|
|
11
|
+
const filePath = path.join(rootDir, `${baseName}.${ext}`);
|
|
12
|
+
try {
|
|
13
|
+
await fs.stat(filePath);
|
|
14
|
+
return filePath; // Return the path if the file exists
|
|
15
|
+
} catch {
|
|
16
|
+
// If the file doesn't exist, an error is thrown. Catch it and continue checking.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return null; // Return null if no config file was found
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readConfig(): Promise<VovkConfig> {
|
|
24
|
+
const configPath = await findConfigPath();
|
|
25
|
+
let config: VovkConfig = {};
|
|
26
|
+
|
|
27
|
+
if (!configPath) {
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (configPath.endsWith('.cjs') || configPath.endsWith('.js')) {
|
|
33
|
+
try {
|
|
34
|
+
delete require.cache[require.resolve(configPath)];
|
|
35
|
+
} finally {
|
|
36
|
+
config = require(configPath) as VovkConfig;
|
|
37
|
+
}
|
|
38
|
+
} else if (configPath.endsWith('.mjs')) {
|
|
39
|
+
const cacheBuster = Date.now();
|
|
40
|
+
({ default: config } = await import(`${configPath}?cache=${cacheBuster}`));
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
// eslint-disable-next-line no-console
|
|
44
|
+
console.error('🐺 ❌ Error reading config file:', (e as Error).message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default readConfig;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import concurrently from 'concurrently';
|
|
4
|
+
import getAvailablePort from './utils/getAvailablePort';
|
|
5
|
+
import { VovkCLIServer } from './server';
|
|
6
|
+
import getProjectInfo from './getProjectInfo';
|
|
7
|
+
import generateClient from './server/generateClient';
|
|
8
|
+
import locateSegments from './locateSegments';
|
|
9
|
+
import { VovkConfig, VovkEnv } from './types';
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
TODO:
|
|
13
|
+
- Use ts-morph to update files
|
|
14
|
+
- Vovk create segment <segmentName>
|
|
15
|
+
- Vovk create module <segmentName>/module.ts
|
|
16
|
+
- Explicit concurrently, implicit concurrently instead of "standalone" mode
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export type { VovkConfig, VovkEnv };
|
|
20
|
+
|
|
21
|
+
interface DevOptions {
|
|
22
|
+
project: string;
|
|
23
|
+
clientOut?: string;
|
|
24
|
+
nextDev: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface GenerateOptions {
|
|
28
|
+
clientOut?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const program = new Command();
|
|
32
|
+
|
|
33
|
+
program.name('vovk').description('Vovk CLI tool').version('1.0.0');
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.command('dev')
|
|
37
|
+
.description('Start development server (optional flag --next-dev to start Vovk Server with Next.js)')
|
|
38
|
+
.option('--project <path>', 'Path to Next.js project', process.cwd())
|
|
39
|
+
.option('--client-out <path>', 'Path to client output directory')
|
|
40
|
+
.option('--next-dev', 'Start Vovk Server and Next.js with automatic port allocation', false)
|
|
41
|
+
.allowUnknownOption(true)
|
|
42
|
+
.action(async (options: DevOptions, command: Command) => {
|
|
43
|
+
const portAttempts = 30;
|
|
44
|
+
const PORT = !options.nextDev
|
|
45
|
+
? process.env.PORT
|
|
46
|
+
: process.env.PORT ||
|
|
47
|
+
(await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.warn(`🐺 Next.js Port ${failedPort} is in use, trying ${tryingPort} instead.`)
|
|
50
|
+
).catch(() => {
|
|
51
|
+
throw new Error(`🐺 ❌ Failed to find available Next port after ${portAttempts} attempts`);
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
if (!PORT) {
|
|
55
|
+
throw new Error('🐺 ❌ PORT env variable is required');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (options.nextDev) {
|
|
59
|
+
const { result } = concurrently(
|
|
60
|
+
[
|
|
61
|
+
{
|
|
62
|
+
command: `node ${__dirname}/server/index.js`,
|
|
63
|
+
name: 'Vovk.ts Metadata Server',
|
|
64
|
+
env: Object.assign(
|
|
65
|
+
{ PORT, __VOVK_START_SERVER_IN_STANDALONE_MODE__: 'true' as const },
|
|
66
|
+
options.clientOut ? { VOVK_CLIENT_OUT_DIR: options.clientOut } : {}
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
command: `cd ${options.project} && npx next dev ${command.args.join(' ')}`,
|
|
71
|
+
name: 'Next.js Development Server',
|
|
72
|
+
env: { PORT },
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
{
|
|
76
|
+
killOthers: ['failure', 'success'],
|
|
77
|
+
prefix: 'none',
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
try {
|
|
81
|
+
await result;
|
|
82
|
+
} finally {
|
|
83
|
+
// eslint-disable-next-line no-console
|
|
84
|
+
console.log('🐺 Exiting...');
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
void new VovkCLIServer().startServer({ clientOutDir: options.clientOut });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
program
|
|
92
|
+
.command('generate')
|
|
93
|
+
.description('Generate client')
|
|
94
|
+
.option('--client-out <path>', 'Path to output directory')
|
|
95
|
+
.action(async (options: GenerateOptions) => {
|
|
96
|
+
const projectInfo = await getProjectInfo({ clientOutDir: options.clientOut });
|
|
97
|
+
const segments = await locateSegments(projectInfo.apiDir);
|
|
98
|
+
const metadata = await import(projectInfo.metadataOutFullPath);
|
|
99
|
+
|
|
100
|
+
await generateClient(projectInfo, segments, metadata.default);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
program
|
|
104
|
+
.command('help')
|
|
105
|
+
.description('Show help message')
|
|
106
|
+
.action(() => program.help());
|
|
107
|
+
|
|
108
|
+
program.parse(process.argv);
|
|
109
|
+
|
|
110
|
+
if (!process.argv.slice(2).length) {
|
|
111
|
+
program.outputHelp();
|
|
112
|
+
}
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
npx vovk-cli init
|
|
4
|
+
- Check if the project is already initialized
|
|
5
|
+
- Do you want to reinitialize the project?
|
|
6
|
+
- Yes
|
|
7
|
+
- No (exit)
|
|
8
|
+
- Check for package.json, if not found, show error and exit
|
|
9
|
+
- Check for tsconfig.json, if not found, show error and exit
|
|
10
|
+
- Check Next.js installed
|
|
11
|
+
- Choose validation library: add to the installation list
|
|
12
|
+
- vovk-zod
|
|
13
|
+
- Further installation notes: install zod
|
|
14
|
+
- vovk-yup
|
|
15
|
+
- Further installation notes: install yup
|
|
16
|
+
- vovk-dto
|
|
17
|
+
- Further installation notes: install class-validator and class-transformer
|
|
18
|
+
- None
|
|
19
|
+
- If validation library is not None,
|
|
20
|
+
- Do you want to enable client validation?
|
|
21
|
+
- Yes
|
|
22
|
+
- Add client validation to the config
|
|
23
|
+
- No
|
|
24
|
+
- Do you want to use concurrently? (NO NEED, USE CONCURRENTLY BY DEFAULT)
|
|
25
|
+
- Yes (recommended)
|
|
26
|
+
- Add concurrently to the installation list
|
|
27
|
+
- No
|
|
28
|
+
- Do you want to update NPM scripts?
|
|
29
|
+
- Yes
|
|
30
|
+
- Update NPM scripts
|
|
31
|
+
- No
|
|
32
|
+
- if experimentalDecorators is not found in tsconfig.json,
|
|
33
|
+
- Do you want to add experimentalDecorators to tsconfig.json?
|
|
34
|
+
- Yes
|
|
35
|
+
- Add experimentalDecorators to tsconfig.json
|
|
36
|
+
- No
|
|
37
|
+
- Do you want to create route file with example service and controller? (NO NEED)
|
|
38
|
+
- Yes
|
|
39
|
+
- Create route file with example controller
|
|
40
|
+
- No, I will create it myself
|
|
41
|
+
- End
|
|
42
|
+
- If there are any packages to install, install them
|
|
43
|
+
- Show installation notes
|
|
44
|
+
- If there are any files to create, create
|
|
45
|
+
- If there are any config files to update, update
|
|
46
|
+
- If example route file is NOT created, show example route file and controller
|
|
47
|
+
- Show how to run the project
|
|
48
|
+
- If npm scripts are updated
|
|
49
|
+
- npm run dev
|
|
50
|
+
- If npm scripts are NOT updated
|
|
51
|
+
- If concurrently is installed
|
|
52
|
+
- concurrently "vovk dev" "next dev"
|
|
53
|
+
- If concurrently is NOT installed
|
|
54
|
+
- vovk dev --next-dev
|
|
55
|
+
- Open http://localhost:3000/api/hello-world
|
|
56
|
+
- Show how to make a request to the example route
|
|
57
|
+
- Show success message
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
import { confirm } from '@inquirer/prompts';
|
|
61
|
+
// Or
|
|
62
|
+
// import confirm from '@inquirer/confirm';
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
void confirm({ message: 'Continue?' }).then(console.info);
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
const wizard = [
|
|
69
|
+
{
|
|
70
|
+
description: 'Check if the project is already initialized',
|
|
71
|
+
type: 'check',
|
|
72
|
+
choices: {
|
|
73
|
+
type: 'choice',
|
|
74
|
+
choices: [
|
|
75
|
+
{
|
|
76
|
+
label: 'Yes',
|
|
77
|
+
action: 'continue',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: 'No',
|
|
81
|
+
action: 'exit',
|
|
82
|
+
exitMessage: 'Exiting...',
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
description: 'Check for package.json',
|
|
89
|
+
type: 'check',
|
|
90
|
+
handleCheck: () => {
|
|
91
|
+
// Check if the project is already initialized
|
|
92
|
+
},
|
|
93
|
+
yes: {
|
|
94
|
+
action: 'continue',
|
|
95
|
+
},
|
|
96
|
+
no: {
|
|
97
|
+
action: 'exit',
|
|
98
|
+
exitMessage: 'Exiting...',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
description: 'Check for tsconfig.json',
|
|
103
|
+
type: 'check',
|
|
104
|
+
handleCheck: () => {
|
|
105
|
+
// Check for package.json
|
|
106
|
+
},
|
|
107
|
+
yes: {
|
|
108
|
+
action: 'continue',
|
|
109
|
+
},
|
|
110
|
+
no: {
|
|
111
|
+
action: 'exit',
|
|
112
|
+
exitMessage: 'Exiting...',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
description: 'Check Next.js installed with app router',
|
|
117
|
+
type: 'check',
|
|
118
|
+
handleCheck: () => {
|
|
119
|
+
// Check for tsconfig.json
|
|
120
|
+
},
|
|
121
|
+
yes: {
|
|
122
|
+
action: 'continue',
|
|
123
|
+
},
|
|
124
|
+
no: {
|
|
125
|
+
action: 'exit',
|
|
126
|
+
exitMessage: 'Exiting...',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
description: 'Choose validation library',
|
|
131
|
+
type: 'install',
|
|
132
|
+
choices: {
|
|
133
|
+
type: 'choice',
|
|
134
|
+
choices: [
|
|
135
|
+
{
|
|
136
|
+
package: 'vovk-zod',
|
|
137
|
+
action: 'install',
|
|
138
|
+
notes: 'Further installation notes: install zod',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
package: 'vovk-yup',
|
|
142
|
+
action: 'install',
|
|
143
|
+
notes: 'Further installation notes: install yup',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
package: 'vovk-dto',
|
|
147
|
+
action: 'install',
|
|
148
|
+
notes: 'Further installation notes: install class-validator and class-transformer',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
package: null,
|
|
152
|
+
action: 'continue',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
description: 'Do you want to enable client validation?',
|
|
159
|
+
type: 'choice',
|
|
160
|
+
choices: [
|
|
161
|
+
{
|
|
162
|
+
label: 'Yes',
|
|
163
|
+
action: 'updateConfig',
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: 'No',
|
|
167
|
+
action: 'updateConfig',
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// export default console.info(wizard);
|
|
174
|
+
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import fileExists from './utils/fileExists';
|
|
4
|
+
|
|
5
|
+
export type Segment = {
|
|
6
|
+
routeFilePath: string;
|
|
7
|
+
segmentName: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default async function locateSegments(dir: string, rootDir = dir): Promise<Segment[]> {
|
|
11
|
+
let results: Segment[] = [];
|
|
12
|
+
|
|
13
|
+
// Read the contents of the directory
|
|
14
|
+
const list = await fs.readdir(dir);
|
|
15
|
+
|
|
16
|
+
// Iterate through each item in the directory
|
|
17
|
+
for (const file of list) {
|
|
18
|
+
const filePath = path.join(dir, file);
|
|
19
|
+
const stat = await fs.stat(filePath);
|
|
20
|
+
|
|
21
|
+
if (stat.isDirectory()) {
|
|
22
|
+
// Check if the directory name matches the pattern [[...something]]
|
|
23
|
+
if (file.startsWith('[[...') && file.endsWith(']]')) {
|
|
24
|
+
// Check if there's a route.ts file inside this directory
|
|
25
|
+
const routeFilePath = path.join(filePath, 'route.ts');
|
|
26
|
+
if (await fileExists(routeFilePath)) {
|
|
27
|
+
// Calculate the basePath relative to the root directory
|
|
28
|
+
const segmentName = path.relative(rootDir, dir);
|
|
29
|
+
results.push({ routeFilePath, segmentName });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Recursively search inside subdirectories
|
|
34
|
+
const subDirResults = await locateSegments(filePath, rootDir);
|
|
35
|
+
results = results.concat(subDirResults);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a file exists at the given path.
|
|
6
|
+
* @param {string} filePath - The path to the file.
|
|
7
|
+
* @returns {Promise<boolean>} - A promise that resolves to true if the file exists, false otherwise.
|
|
8
|
+
*/
|
|
9
|
+
const fileExists = async (filePath: string): Promise<boolean> => !!(await fs.stat(filePath).catch(() => false));
|
|
10
|
+
|
|
11
|
+
async function postinstall(): Promise<void> {
|
|
12
|
+
const vovk = path.join(__dirname, '../../.vovk');
|
|
13
|
+
const js = path.join(vovk, 'client.js');
|
|
14
|
+
const ts = path.join(vovk, 'client.d.ts');
|
|
15
|
+
const index = path.join(vovk, 'index.ts');
|
|
16
|
+
|
|
17
|
+
if ((await fileExists(js)) || (await fileExists(ts)) || (await fileExists(index))) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
await fs.mkdir(vovk, { recursive: true });
|
|
22
|
+
await fs.writeFile(js, '/* postinstall */');
|
|
23
|
+
await fs.writeFile(ts, '/* postinstall */');
|
|
24
|
+
await fs.writeFile(index, '/* postinstall */');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void postinstall();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { VovkMetadata } from 'vovk';
|
|
3
|
+
|
|
4
|
+
export default function createMetadataServer(
|
|
5
|
+
then: (metadata: { metadata: VovkMetadata }) => void | Promise<void>,
|
|
6
|
+
catchFn: (err: Error) => void | Promise<void>
|
|
7
|
+
) {
|
|
8
|
+
return http.createServer((req, res) => {
|
|
9
|
+
if (req.method === 'POST' && req.url === '/__metadata') {
|
|
10
|
+
let body = '';
|
|
11
|
+
|
|
12
|
+
req.on('data', (chunk) => {
|
|
13
|
+
body += chunk.toString();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
req.on('end', () => {
|
|
17
|
+
try {
|
|
18
|
+
const result: { metadata: VovkMetadata } = JSON.parse(body);
|
|
19
|
+
void then(result);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
void catchFn(e as Error);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
26
|
+
res.end('Not Found');
|
|
27
|
+
void catchFn(new Error('Not Found'));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { VovkMetadata } from 'vovk';
|
|
2
|
+
import { _VovkControllerMetadata, _VovkWorkerMetadata } from 'vovk/types';
|
|
3
|
+
import { isEqual } from 'lodash';
|
|
4
|
+
|
|
5
|
+
interface HandlersDiff {
|
|
6
|
+
nameOfClass: string;
|
|
7
|
+
added: string[];
|
|
8
|
+
removed: string[];
|
|
9
|
+
changed: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface WorkersOrControllersDiff {
|
|
13
|
+
added: string[];
|
|
14
|
+
removed: string[];
|
|
15
|
+
handlers: HandlersDiff[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DiffResult {
|
|
19
|
+
workers: WorkersOrControllersDiff;
|
|
20
|
+
controllers: WorkersOrControllersDiff;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function diffHandlers<T extends _VovkWorkerMetadata['_handlers'] | _VovkControllerMetadata['_handlers']>(
|
|
24
|
+
oldHandlers: T,
|
|
25
|
+
newHandlers: T,
|
|
26
|
+
nameOfClass: string
|
|
27
|
+
): HandlersDiff {
|
|
28
|
+
const added: string[] = [];
|
|
29
|
+
const removed: string[] = [];
|
|
30
|
+
const changed: string[] = []; // Array to store changed handlers
|
|
31
|
+
|
|
32
|
+
for (const [handler, newHandler] of Object.entries(newHandlers)) {
|
|
33
|
+
if (!(handler in oldHandlers)) {
|
|
34
|
+
added.push(handler);
|
|
35
|
+
} else if (!isEqual(newHandler, oldHandlers[handler])) {
|
|
36
|
+
changed.push(handler); // Add to changed if handlers are not shallow equal
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const [handler] of Object.entries(oldHandlers)) {
|
|
41
|
+
if (!(handler in newHandlers)) {
|
|
42
|
+
removed.push(handler);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { nameOfClass, added, removed, changed };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function diffWorkersOrControllers<T extends VovkMetadata['controllers'] | VovkMetadata['workers']>(
|
|
50
|
+
oldItems: T,
|
|
51
|
+
newItems: T
|
|
52
|
+
): WorkersOrControllersDiff {
|
|
53
|
+
const added: string[] = [];
|
|
54
|
+
const removed: string[] = [];
|
|
55
|
+
const handlersDiff: HandlersDiff[] = [];
|
|
56
|
+
|
|
57
|
+
for (const [item, newItem] of Object.entries(newItems)) {
|
|
58
|
+
if (!(item in oldItems)) {
|
|
59
|
+
added.push(item);
|
|
60
|
+
} else {
|
|
61
|
+
const handlers = diffHandlers(oldItems[item]._handlers, newItem._handlers, item);
|
|
62
|
+
if (handlers.added.length || handlers.removed.length || handlers.changed.length) {
|
|
63
|
+
handlersDiff.push(handlers);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const [item] of Object.entries(oldItems)) {
|
|
69
|
+
if (!(item in newItems)) {
|
|
70
|
+
removed.push(item);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { added, removed, handlers: handlersDiff };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
example output:
|
|
79
|
+
{
|
|
80
|
+
workers: {
|
|
81
|
+
added: ["WorkerC"],
|
|
82
|
+
removed: ["WorkerA"],
|
|
83
|
+
handlers: []
|
|
84
|
+
},
|
|
85
|
+
controllers: {
|
|
86
|
+
added: ["ControllerC"],
|
|
87
|
+
removed: ["ControllerB"],
|
|
88
|
+
handlers: [
|
|
89
|
+
{
|
|
90
|
+
nameOfClass: "ControllerA",
|
|
91
|
+
added: ["handlerF"],
|
|
92
|
+
removed: [],
|
|
93
|
+
changed: ["handlerD"]
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
*/
|
|
99
|
+
export default function diffMetadata(oldJson: VovkMetadata, newJson: VovkMetadata): DiffResult {
|
|
100
|
+
const workersDiff = diffWorkersOrControllers<VovkMetadata['workers']>(oldJson.workers ?? {}, newJson.workers ?? {});
|
|
101
|
+
const controllersDiff = diffWorkersOrControllers<VovkMetadata['controllers']>(
|
|
102
|
+
oldJson.controllers ?? {},
|
|
103
|
+
newJson.controllers ?? {}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
workers: workersDiff,
|
|
108
|
+
controllers: controllersDiff,
|
|
109
|
+
};
|
|
110
|
+
}
|