vovk-cli 0.2.0 → 0.3.0-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/CHANGELOG.md +38 -0
- package/client-templates/schema-ts/schema.ts.ejs +1 -1
- package/client-templates/ts-base/index.ts.ejs +1 -1
- package/dist/bundle/index.mjs +64 -42
- package/dist/dev/diff-segment-schema.d.mts +0 -17
- package/dist/dev/diff-segment-schema.mjs +1 -17
- package/dist/dev/ensure-schema-files.mjs +28 -10
- package/dist/dev/index.mjs +19 -13
- package/dist/dev/log-diff-result.d.mts +1 -1
- package/dist/dev/log-diff-result.mjs +1 -1
- package/dist/dev/write-meta-json.mjs +2 -2
- package/dist/dev/write-one-segment-schema-file.mjs +7 -2
- package/dist/generate/ensure-client.mjs +1 -1
- package/dist/generate/generate.mjs +38 -25
- package/dist/generate/get-client-template-files.mjs +8 -4
- package/dist/generate/get-project-full-schema.d.mts +1 -1
- package/dist/generate/get-project-full-schema.mjs +2 -2
- package/dist/generate/index.d.mts +2 -2
- package/dist/generate/index.mjs +46 -34
- package/dist/generate/write-one-client-file.d.mts +3 -3
- package/dist/generate/write-one-client-file.mjs +8 -6
- package/dist/get-project-info/get-config/get-relative-src-root.mjs +1 -1
- package/dist/get-project-info/get-config/get-template-defs.d.mts +20 -23
- package/dist/get-project-info/get-config/get-template-defs.mjs +21 -47
- package/dist/get-project-info/get-config/get-user-config.mjs +1 -1
- package/dist/get-project-info/get-config/index.d.mts +1 -1
- package/dist/get-project-info/get-config/index.mjs +15 -10
- package/dist/get-project-info/get-meta-schema.d.mts +1 -1
- package/dist/get-project-info/get-meta-schema.mjs +1 -1
- package/dist/get-project-info/index.mjs +2 -2
- package/dist/index.mjs +25 -16
- package/dist/init/check-tsconfig-for-experimental-decorators.mjs +1 -1
- package/dist/init/create-config.d.mts +1 -1
- package/dist/init/create-config.mjs +5 -5
- package/dist/init/index.d.mts +1 -1
- package/dist/init/index.mjs +49 -17
- package/dist/init/install-dependencies.d.mts +2 -1
- package/dist/init/install-dependencies.mjs +10 -2
- package/dist/init/update-dependencies-without-installing.d.mts +1 -1
- package/dist/init/update-dependencies-without-installing.mjs +0 -1
- package/dist/init/update-gitignore.d.mts +2 -0
- package/dist/init/update-gitignore.mjs +20 -0
- package/dist/init/update-typescript-config.mjs +1 -1
- package/dist/new/index.d.mts +1 -1
- package/dist/new/new-module.mjs +11 -16
- package/dist/new/new-segment.mjs +9 -4
- package/dist/new/render.mjs +1 -1
- package/dist/types.d.mts +1 -1
- package/dist/utils/compile-json-schema-to-typescript-type.d.mts +1 -1
- package/dist/utils/compile-ts.mjs +1 -1
- package/dist/utils/generate-fn-name.d.mts +0 -9
- package/dist/utils/generate-fn-name.mjs +2 -9
- package/dist/utils/generated-banner.d.mts +1 -0
- package/dist/utils/generated-banner.mjs +3 -0
- package/dist/utils/get-available-port.d.mts +0 -8
- package/dist/utils/get-available-port.mjs +2 -14
- package/dist/utils/get-file-system-entry-type.d.mts +5 -4
- package/dist/utils/get-file-system-entry-type.mjs +5 -5
- package/dist/utils/normalize-openapi-mixin.d.mts +1 -1
- package/dist/utils/normalize-openapi-mixin.mjs +18 -3
- package/dist/utils/prettify.d.mts +4 -0
- package/dist/utils/prettify.mjs +39 -3
- package/dist/utils/remove-unlisted-directories.d.mts +1 -9
- package/dist/utils/remove-unlisted-directories.mjs +60 -21
- package/dist/utils/resolve-absolute-module-path.mjs +1 -2
- package/dist/utils/update-config-property.mjs +1 -1
- package/package.json +23 -30
- package/client-templates/js-base/index.d.ts.ejs +0 -21
- package/client-templates/js-base/index.js.ejs +0 -18
- package/client-templates/openapi-js/openapi.d.ts.ejs +0 -4
- package/client-templates/openapi-js/openapi.js.ejs +0 -4
- package/client-templates/schema-js/schema.d.ts.ejs +0 -10
- package/client-templates/schema-js/schema.js.ejs +0 -29
package/dist/init/index.mjs
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import { confirm, select, checkbox } from '@inquirer/prompts';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import fs from 'node:fs/promises';
|
|
4
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { checkbox, confirm, select } from '@inquirer/prompts';
|
|
5
4
|
import NPMCliPackageJson from '@npmcli/package-json';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
6
|
import { getConfigAbsolutePaths } from '../get-project-info/get-config/get-config-absolute-paths.mjs';
|
|
7
|
+
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
7
8
|
import { getFileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
|
|
8
|
-
import { installDependencies, getPackageManager } from './install-dependencies.mjs';
|
|
9
9
|
import { getLogger } from '../utils/get-logger.mjs';
|
|
10
|
-
import { createConfig } from './create-config.mjs';
|
|
11
|
-
import { updateNPMScripts, getDevScript } from './update-npm-scripts.mjs';
|
|
12
10
|
import { checkTSConfigForExperimentalDecorators } from './check-tsconfig-for-experimental-decorators.mjs';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
11
|
+
import { createConfig } from './create-config.mjs';
|
|
12
|
+
import { getPackageManager, installDependencies } from './install-dependencies.mjs';
|
|
15
13
|
import { logUpdateDependenciesError } from './log-update-dependencies-error.mjs';
|
|
16
|
-
import {
|
|
14
|
+
import { updateDependenciesWithoutInstalling } from './update-dependencies-without-installing.mjs';
|
|
15
|
+
import { updateGitignore } from './update-gitignore.mjs';
|
|
16
|
+
import { getDevScript, updateNPMScripts } from './update-npm-scripts.mjs';
|
|
17
|
+
import { updateTypeScriptConfig } from './update-typescript-config.mjs';
|
|
18
|
+
const VALIDATION_LIBRARIES = ['zod', 'valibot', 'arktype', 'none'];
|
|
19
|
+
const LANGS = ['py', 'rs'];
|
|
20
|
+
const UPDATE_SCRIPTS_MODES = ['implicit', 'explicit'];
|
|
21
|
+
const CHANNELS = ['latest', 'beta', 'draft'];
|
|
17
22
|
export class Init {
|
|
18
23
|
root;
|
|
19
24
|
log;
|
|
20
25
|
async #init({ configPaths, pkgJson, }, { useNpm, useYarn, usePnpm, useBun, skipInstall, updateTsConfig, updateScripts, validationLibrary, bundle, lang, dryRun, channel, }) {
|
|
21
26
|
const { log, root } = this;
|
|
22
|
-
const dependencies = ['vovk', 'vovk-
|
|
27
|
+
const dependencies = ['vovk', 'vovk-ajv'];
|
|
23
28
|
const devDependencies = ['vovk-cli'];
|
|
24
29
|
if (lang?.includes('py')) {
|
|
25
30
|
devDependencies.push('vovk-python');
|
|
@@ -29,8 +34,9 @@ export class Init {
|
|
|
29
34
|
}
|
|
30
35
|
// delete older config files
|
|
31
36
|
if (configPaths.length) {
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
if (!dryRun)
|
|
38
|
+
await Promise.all(configPaths.map((configPath) => fs.rm(configPath)));
|
|
39
|
+
log.debug(`${dryRun ? 'Dry run: would delete' : 'Deleted'} existing config file${configPaths.length > 1 ? 's' : ''} at ${configPaths.join(', ')}`);
|
|
34
40
|
}
|
|
35
41
|
if (validationLibrary) {
|
|
36
42
|
dependencies.push(...({
|
|
@@ -40,14 +46,14 @@ export class Init {
|
|
|
40
46
|
}[validationLibrary] ?? []));
|
|
41
47
|
}
|
|
42
48
|
if (bundle) {
|
|
43
|
-
const TSDOWN_VERSION = '0.
|
|
49
|
+
const TSDOWN_VERSION = '0.22.14';
|
|
44
50
|
devDependencies.push(`tsdown@${TSDOWN_VERSION}`);
|
|
45
51
|
}
|
|
46
52
|
if (updateScripts) {
|
|
47
53
|
try {
|
|
48
54
|
if (!dryRun && pkgJson)
|
|
49
55
|
await updateNPMScripts({ pkgJson, root, bundle, updateScriptsMode: updateScripts });
|
|
50
|
-
log.info('Updated scripts at package.json
|
|
56
|
+
log.info(`${dryRun ? 'Dry run: would update' : 'Updated'} scripts at package.json`);
|
|
51
57
|
}
|
|
52
58
|
catch (error) {
|
|
53
59
|
log.error(`Failed to update scripts at package.json: ${error.message}`);
|
|
@@ -63,7 +69,7 @@ export class Init {
|
|
|
63
69
|
};
|
|
64
70
|
if (!dryRun)
|
|
65
71
|
await updateTypeScriptConfig(root, compilerOptions);
|
|
66
|
-
log.info(
|
|
72
|
+
log.info(`${dryRun ? 'Dry run: would add' : 'Added'} ${Object.keys(compilerOptions)
|
|
67
73
|
.map((k) => `"${k}"`)
|
|
68
74
|
.join(' and ')} to tsconfig.json`);
|
|
69
75
|
}
|
|
@@ -73,7 +79,7 @@ export class Init {
|
|
|
73
79
|
}
|
|
74
80
|
if (!dryRun && pkgJson) {
|
|
75
81
|
let depsUpdated = false;
|
|
76
|
-
const packageManager = getPackageManager({ useNpm, useYarn, usePnpm, useBun, pkgJson });
|
|
82
|
+
const packageManager = getPackageManager({ useNpm, useYarn, usePnpm, useBun, pkgJson, log });
|
|
77
83
|
try {
|
|
78
84
|
await updateDependenciesWithoutInstalling({
|
|
79
85
|
log,
|
|
@@ -114,13 +120,23 @@ export class Init {
|
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
}
|
|
123
|
+
try {
|
|
124
|
+
if (!dryRun) {
|
|
125
|
+
const gitignoreEntry = await updateGitignore(root);
|
|
126
|
+
if (gitignoreEntry)
|
|
127
|
+
log.info(`Added ${chalkHighlightThing(gitignoreEntry)} to .gitignore`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
log.error(`Failed to update .gitignore: ${error.message}`);
|
|
132
|
+
}
|
|
117
133
|
try {
|
|
118
134
|
const { configAbsolutePath } = await createConfig({
|
|
119
135
|
root,
|
|
120
136
|
log,
|
|
121
137
|
options: { validationLibrary, channel, bundle, lang, dryRun },
|
|
122
138
|
});
|
|
123
|
-
log.info(`Config created successfully at ${chalkHighlightThing(configAbsolutePath)}`);
|
|
139
|
+
log.info(`Config ${dryRun ? 'would be created (dry run)' : 'created successfully'} at ${chalkHighlightThing(configAbsolutePath)}`);
|
|
124
140
|
log.info(`You can now create a root segment with ${chalkHighlightThing('npx vovk new segment')} command`);
|
|
125
141
|
}
|
|
126
142
|
catch (error) {
|
|
@@ -131,6 +147,22 @@ export class Init {
|
|
|
131
147
|
const cwd = process.cwd();
|
|
132
148
|
const root = path.resolve(cwd, prefix ?? '.');
|
|
133
149
|
const log = getLogger(logLevel ?? 'info');
|
|
150
|
+
validationLibrary = validationLibrary?.toLowerCase();
|
|
151
|
+
lang = lang?.map((l) => l.toLowerCase());
|
|
152
|
+
if (validationLibrary && !VALIDATION_LIBRARIES.includes(validationLibrary)) {
|
|
153
|
+
throw new Error(`Unknown --validation-library value "${validationLibrary}". Supported values: ${VALIDATION_LIBRARIES.join(', ')}`);
|
|
154
|
+
}
|
|
155
|
+
for (const l of lang ?? []) {
|
|
156
|
+
if (!LANGS.includes(l)) {
|
|
157
|
+
throw new Error(`Unknown --lang value "${l}". Supported values: ${LANGS.join(', ')}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (updateScripts && !UPDATE_SCRIPTS_MODES.includes(updateScripts)) {
|
|
161
|
+
throw new Error(`Unknown --update-scripts value "${updateScripts}". Supported values: ${UPDATE_SCRIPTS_MODES.join(', ')}`);
|
|
162
|
+
}
|
|
163
|
+
if (channel && !CHANNELS.includes(channel)) {
|
|
164
|
+
throw new Error(`Unknown --channel value "${channel}". Supported values: ${CHANNELS.join(', ')}`);
|
|
165
|
+
}
|
|
134
166
|
const pkgJson = await NPMCliPackageJson.load(root).catch(() => null);
|
|
135
167
|
this.root = root;
|
|
136
168
|
this.log = log;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type NPMCliPackageJson from '@npmcli/package-json';
|
|
2
|
-
import type { getLogger } from '../utils/get-logger.mjs';
|
|
3
2
|
import type { InitOptions } from '../types.mjs';
|
|
3
|
+
import type { getLogger } from '../utils/get-logger.mjs';
|
|
4
4
|
export type PackageManager = 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
5
5
|
export declare function getPackageManager(options: Pick<InitOptions, 'useNpm' | 'useYarn' | 'usePnpm' | 'useBun'> & {
|
|
6
6
|
pkgJson: NPMCliPackageJson;
|
|
7
|
+
log?: ReturnType<typeof getLogger>;
|
|
7
8
|
}): PackageManager;
|
|
8
9
|
export declare function installDependencies({ log, cwd, packageManager, }: {
|
|
9
10
|
log: ReturnType<typeof getLogger>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
3
|
+
const KNOWN_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'bun'];
|
|
3
4
|
export function getPackageManager(options) {
|
|
4
5
|
if (options.useNpm)
|
|
5
6
|
return 'npm';
|
|
@@ -9,8 +10,15 @@ export function getPackageManager(options) {
|
|
|
9
10
|
return 'pnpm';
|
|
10
11
|
if (options.useBun)
|
|
11
12
|
return 'bun';
|
|
12
|
-
const packageManager = options.pkgJson.content?.packageManager;
|
|
13
|
-
|
|
13
|
+
const packageManager = options.pkgJson.content?.packageManager?.split('@')[0];
|
|
14
|
+
if (!packageManager)
|
|
15
|
+
return 'npm'; // Default to npm if no options are true
|
|
16
|
+
// this name gets spawned, so an unknown one from package.json is not executed
|
|
17
|
+
if (!KNOWN_PACKAGE_MANAGERS.includes(packageManager)) {
|
|
18
|
+
options.log?.warn(`Unknown "packageManager" ${JSON.stringify(packageManager)} in package.json, using ${chalkHighlightThing('npm')} instead`);
|
|
19
|
+
return 'npm';
|
|
20
|
+
}
|
|
21
|
+
return packageManager;
|
|
14
22
|
}
|
|
15
23
|
export async function installDependencies({ log, cwd, packageManager, }) {
|
|
16
24
|
log.info(`Installing dependencies at ${chalkHighlightThing(cwd)} using ${chalkHighlightThing(packageManager)}...`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { getLogger } from '../utils/get-logger.mjs';
|
|
2
1
|
import type { InitOptions } from '../types.mjs';
|
|
2
|
+
import type { getLogger } from '../utils/get-logger.mjs';
|
|
3
3
|
export declare function updateDependenciesWithoutInstalling({ log, dir, dependencyNames, devDependencyNames, channel, }: {
|
|
4
4
|
log: ReturnType<typeof getLogger>;
|
|
5
5
|
dir: string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getRelativeSrcRoot } from '../get-project-info/get-config/get-relative-src-root.mjs';
|
|
4
|
+
/** Adds the default composed client outDir to .gitignore. Returns the added entry or null if already present. */
|
|
5
|
+
export async function updateGitignore(root) {
|
|
6
|
+
const srcRoot = await getRelativeSrcRoot({ cwd: root });
|
|
7
|
+
const outDir = path.join(srcRoot ?? '.', 'client');
|
|
8
|
+
const entry = `/${outDir}`;
|
|
9
|
+
const gitignorePath = path.join(root, '.gitignore');
|
|
10
|
+
const content = await fs.readFile(gitignorePath, 'utf-8').catch(() => '');
|
|
11
|
+
const hasEntry = content.split('\n').some((line) => {
|
|
12
|
+
const trimmed = line.trim().replace(/\/$/, '');
|
|
13
|
+
return trimmed === entry || trimmed === outDir;
|
|
14
|
+
});
|
|
15
|
+
if (hasEntry)
|
|
16
|
+
return null;
|
|
17
|
+
const updated = `${content.length && !content.endsWith('\n') ? `${content}\n` : content}\n# vovk generated client\n${entry}\n`;
|
|
18
|
+
await fs.writeFile(gitignorePath, updated, 'utf-8');
|
|
19
|
+
return entry;
|
|
20
|
+
}
|
package/dist/new/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { NewOptions } from '../types.mjs';
|
|
2
1
|
import type { ProjectInfo } from '../get-project-info/index.mts';
|
|
2
|
+
import type { NewOptions } from '../types.mjs';
|
|
3
3
|
export declare function newComponents(components: string[], projectInfo: ProjectInfo, { dryRun, outDir, templates, overwrite, noSegmentUpdate, empty, static: isStaticSegment }: NewOptions): Promise<void>;
|
package/dist/new/new-module.mjs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
-
import _ from 'lodash';
|
|
5
4
|
import { getTsconfig } from 'get-tsconfig';
|
|
6
|
-
import { render } from './render.mjs';
|
|
7
|
-
import { addClassToSegmentCode } from './add-class-to-segment-code.mjs';
|
|
8
5
|
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
9
6
|
import { formatLoggedSegmentName } from '../utils/format-logged-segment-name.mjs';
|
|
10
7
|
import { getFileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
|
|
8
|
+
import { locateSegments } from '../utils/locate-segments.mjs';
|
|
11
9
|
import { prettify } from '../utils/prettify.mjs';
|
|
12
10
|
import { resolveAbsoluteModulePath } from '../utils/resolve-absolute-module-path.mjs';
|
|
13
|
-
import {
|
|
11
|
+
import { addClassToSegmentCode } from './add-class-to-segment-code.mjs';
|
|
12
|
+
import { render } from './render.mjs';
|
|
14
13
|
function splitByLast(str, delimiter = '/') {
|
|
15
14
|
const index = str.lastIndexOf(delimiter);
|
|
16
15
|
if (index === -1) {
|
|
@@ -82,16 +81,6 @@ export async function newModule({ projectInfo, what, moduleNameWithOptionalSegme
|
|
|
82
81
|
}
|
|
83
82
|
const absoluteModuleDir = path.join(cwd, outDir);
|
|
84
83
|
const absoluteModulePath = path.join(absoluteModuleDir, fileName);
|
|
85
|
-
// Pre-kebab-case projects keep modules at e.g. userProfile/UserProfileController.ts; scaffolding
|
|
86
|
-
// user-profile/user-profile-controller.ts next to it would register a duplicate class in the segment.
|
|
87
|
-
const legacySuffix = type === 'controller' ? 'Controller' : type === 'service' ? 'Service' : null;
|
|
88
|
-
if (!outDirFlag && legacySuffix && path.basename(absoluteModuleDir) === _.kebabCase(moduleName)) {
|
|
89
|
-
const legacyModulePath = path.join(path.dirname(absoluteModuleDir), _.camelCase(moduleName), `${_.upperFirst(_.camelCase(moduleName))}${legacySuffix}.ts`);
|
|
90
|
-
if (await getFileSystemEntryType(legacyModulePath)) {
|
|
91
|
-
log.error(`Found ${chalkHighlightThing(legacyModulePath)} created with the pre-kebab-case naming convention. Rename it to ${chalkHighlightThing(absoluteModulePath)} manually or pass --out-dir to scaffold elsewhere.`);
|
|
92
|
-
return process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
84
|
const prettiedCode = await prettify(code, absoluteModulePath);
|
|
96
85
|
if (!dryRun) {
|
|
97
86
|
if (!overwrite && (await getFileSystemEntryType(absoluteModulePath))) {
|
|
@@ -104,6 +93,9 @@ export async function newModule({ projectInfo, what, moduleNameWithOptionalSegme
|
|
|
104
93
|
log.info(`${chalk.green('Created')}${empty ? ' empty' : ''} ${chalkHighlightThing(absoluteModulePath)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
|
|
105
94
|
}
|
|
106
95
|
}
|
|
96
|
+
else {
|
|
97
|
+
log.info(`Dry run: would create${empty ? ' empty' : ''} ${chalkHighlightThing(absoluteModulePath)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
|
|
98
|
+
}
|
|
107
99
|
if (type === 'controller') {
|
|
108
100
|
if (!sourceName) {
|
|
109
101
|
throw new Error(`The template for "${type}" does not provide a sourceName`);
|
|
@@ -123,9 +115,12 @@ export async function newModule({ projectInfo, what, moduleNameWithOptionalSegme
|
|
|
123
115
|
}), routeFilePath);
|
|
124
116
|
if (!dryRun) {
|
|
125
117
|
await fs.writeFile(routeFilePath, newSegmentCode);
|
|
118
|
+
log.info(`${chalk.green('Added')} ${chalkHighlightThing(sourceName)} ${type} as ${chalkHighlightThing(compiledName)} to ${formatLoggedSegmentName(segmentName)}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
log.info(`Dry run: would add ${chalkHighlightThing(sourceName)} ${type} as ${chalkHighlightThing(compiledName)} to ${formatLoggedSegmentName(segmentName)}`);
|
|
126
122
|
}
|
|
127
123
|
}
|
|
128
|
-
log.info(`${chalk.green('Added')} ${chalkHighlightThing(sourceName)} ${type} as ${chalkHighlightThing(compiledName)} to ${formatLoggedSegmentName(segmentName)}`);
|
|
129
124
|
}
|
|
130
125
|
}
|
|
131
126
|
}
|
package/dist/new/new-segment.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
3
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
4
|
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
5
5
|
import { formatLoggedSegmentName } from '../utils/format-logged-segment-name.mjs';
|
|
6
|
+
import { getFileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
|
|
6
7
|
import { prettify } from '../utils/prettify.mjs';
|
|
7
|
-
import chalk from 'chalk';
|
|
8
8
|
export async function newSegment({ projectInfo, segmentName, isStaticSegment, overwrite, dryRun, }) {
|
|
9
9
|
const { apiDirAbsolutePath, log, config } = projectInfo;
|
|
10
10
|
if (!apiDirAbsolutePath) {
|
|
@@ -22,6 +22,8 @@ const controllers = {};
|
|
|
22
22
|
export type Controllers = typeof controllers;
|
|
23
23
|
${isStaticSegment
|
|
24
24
|
? `
|
|
25
|
+
export const dynamic = 'force-static';
|
|
26
|
+
|
|
25
27
|
export function generateStaticParams() {
|
|
26
28
|
return controllersToStaticParams(controllers);
|
|
27
29
|
}
|
|
@@ -35,8 +37,11 @@ ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
|
|
|
35
37
|
if (!dryRun) {
|
|
36
38
|
await fs.mkdir(path.dirname(absoluteSegmentRoutePath), { recursive: true });
|
|
37
39
|
await fs.writeFile(absoluteSegmentRoutePath, code);
|
|
40
|
+
log.info(`${chalk.green('Created')} ${formatLoggedSegmentName(segmentName, { isStatic: isStaticSegment })} at ${absoluteSegmentRoutePath}.`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
log.info(`Dry run: would create ${formatLoggedSegmentName(segmentName, { isStatic: isStaticSegment })} at ${absoluteSegmentRoutePath}.`);
|
|
38
44
|
}
|
|
39
|
-
log.info(`${chalk.green('Created')} ${formatLoggedSegmentName(segmentName, { isStatic: isStaticSegment })} at ${absoluteSegmentRoutePath}.`);
|
|
40
45
|
const dir = chalk.cyanBright([segmentName, 'thing'].filter(Boolean).join('/'));
|
|
41
46
|
log.info(`Run ${chalkHighlightThing(`npx vovk new service controller ${dir}`)} to create a new controller with a service at ./${path.join(config.modulesDir, dir)}/ directory for this segment`);
|
|
42
47
|
}
|
package/dist/new/render.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
1
2
|
import ejs from 'ejs';
|
|
2
3
|
import matter from 'gray-matter';
|
|
3
4
|
import _ from 'lodash';
|
|
4
5
|
import pluralize from 'pluralize';
|
|
5
6
|
import { addCommonTerms } from './add-common-terms.mjs';
|
|
6
|
-
import path from 'node:path';
|
|
7
7
|
addCommonTerms();
|
|
8
8
|
export async function render(codeTemplate, { cwd, config, withService, segmentName, moduleName, empty, templateFileName, isNodeNextResolution, srcRoot, }) {
|
|
9
9
|
const defaultOutDir = [config.modulesDir, segmentName || config.rootSegmentModulesDirName, _.kebabCase(moduleName)]
|
package/dist/types.d.mts
CHANGED
|
@@ -88,7 +88,7 @@ export type VovkEnv = {
|
|
|
88
88
|
VOVK_API_ENTRY_POINT?: string;
|
|
89
89
|
__VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
|
|
90
90
|
__VOVK_SCHEMA_OUT_FLAG__?: string;
|
|
91
|
-
__VOVK_DEV_HTTPS_FLAG__?: 'true' | '
|
|
91
|
+
__VOVK_DEV_HTTPS_FLAG__?: 'true' | '';
|
|
92
92
|
__VOVK_EXIT__?: 'true' | 'false';
|
|
93
93
|
__VOVK_LOG_LEVEL__?: LogLevelNames;
|
|
94
94
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
2
1
|
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
3
3
|
export declare function compileJSONSchemaToTypeScriptType(schema: JSONSchema7, typeName: string, components?: NonNullable<OpenAPIObject['components']>, options?: {
|
|
4
4
|
dontCreateRefTypes?: boolean;
|
|
5
5
|
}): string;
|
|
@@ -10,14 +10,5 @@ interface GenerateFnNameOptions {
|
|
|
10
10
|
/** Segments to strip out (e.g. ['api','v1']) */
|
|
11
11
|
ignoreSegments?: string[];
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
15
|
-
*
|
|
16
|
-
* Examples:
|
|
17
|
-
* generateFnName('GET', '/users') // "listUsers"
|
|
18
|
-
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
19
|
-
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
20
|
-
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
21
|
-
*/
|
|
22
13
|
export declare function generateFnName(method: HttpMethod, rawPath: string, opts?: GenerateFnNameOptions): string;
|
|
23
14
|
export {};
|
|
@@ -15,15 +15,8 @@ export function capitalize(str) {
|
|
|
15
15
|
const DEFAULT_OPTIONS = {
|
|
16
16
|
ignoreSegments: ['api'],
|
|
17
17
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* Examples:
|
|
22
|
-
* generateFnName('GET', '/users') // "listUsers"
|
|
23
|
-
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
24
|
-
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
25
|
-
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
26
|
-
*/
|
|
18
|
+
// HTTP method + OpenAPI path to a camelCased fn name,
|
|
19
|
+
// e.g. GET /users -> "listUsers", GET /users/{id} -> "getUsersById", POST /v1/api/orders -> "createOrders"
|
|
27
20
|
export function generateFnName(method, rawPath, opts = {}) {
|
|
28
21
|
const { ignoreSegments } = {
|
|
29
22
|
...DEFAULT_OPTIONS,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const GENERATED_BANNER_PREFIX = "Generated by vovk-cli";
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Finds an available port starting from a given port.
|
|
3
|
-
* @param {number} startPort - The port to start checking from.
|
|
4
|
-
* @param {number} maxAttempts - The maximum number of attempts to find an available port.
|
|
5
|
-
* @param {number} attempt - The current attempt number.
|
|
6
|
-
* @param {(failedPort: number, tryingPort: number) => void} onWarning - The callback function for warnings.
|
|
7
|
-
* @returns {Promise<string>}
|
|
8
|
-
*/
|
|
9
1
|
export declare function getAvailablePort(startPort: number, maxAttempts: number, attempt: number, onWarning: (failedPort: number, tryingPort: number) => void): Promise<string>;
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import net from 'node:net';
|
|
2
|
-
//
|
|
3
|
-
/**
|
|
4
|
-
* Checks if a port is available.
|
|
5
|
-
* @param {number} port - The port to check.
|
|
6
|
-
* @param {(isAvailable: boolean) => void} callback - The callback function.
|
|
7
|
-
*/
|
|
2
|
+
// checks if a port is free
|
|
8
3
|
function checkPort(port, callback) {
|
|
9
4
|
const server = net.createServer();
|
|
10
5
|
server.listen(port, () => {
|
|
@@ -16,14 +11,7 @@ function checkPort(port, callback) {
|
|
|
16
11
|
callback(false);
|
|
17
12
|
});
|
|
18
13
|
}
|
|
19
|
-
|
|
20
|
-
* Finds an available port starting from a given port.
|
|
21
|
-
* @param {number} startPort - The port to start checking from.
|
|
22
|
-
* @param {number} maxAttempts - The maximum number of attempts to find an available port.
|
|
23
|
-
* @param {number} attempt - The current attempt number.
|
|
24
|
-
* @param {(failedPort: number, tryingPort: number) => void} onWarning - The callback function for warnings.
|
|
25
|
-
* @returns {Promise<string>}
|
|
26
|
-
*/
|
|
14
|
+
// finds a free port starting from startPort, up to maxAttempts
|
|
27
15
|
export function getAvailablePort(startPort, maxAttempts, attempt, onWarning) {
|
|
28
16
|
return new Promise((resolve, reject) => {
|
|
29
17
|
checkPort(startPort, (isAvailable) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
FILE
|
|
3
|
-
DIRECTORY
|
|
4
|
-
}
|
|
1
|
+
export declare const FileSystemEntryType: {
|
|
2
|
+
readonly FILE: "FILE";
|
|
3
|
+
readonly DIRECTORY: "DIRECTORY";
|
|
4
|
+
};
|
|
5
|
+
export type FileSystemEntryType = (typeof FileSystemEntryType)[keyof typeof FileSystemEntryType];
|
|
5
6
|
export declare function getFileSystemEntryType(filePath: string): Promise<FileSystemEntryType | null>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
2
|
+
// const object instead of enum, node 26 runs ts with erasable syntax only
|
|
3
|
+
export const FileSystemEntryType = {
|
|
4
|
+
FILE: 'FILE',
|
|
5
|
+
DIRECTORY: 'DIRECTORY',
|
|
6
|
+
};
|
|
7
7
|
export async function getFileSystemEntryType(filePath) {
|
|
8
8
|
try {
|
|
9
9
|
const stats = await fs.stat(filePath);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
1
2
|
import type { HttpMethod, VovkConfig } from 'vovk';
|
|
2
3
|
import type { VovkOperationObject, VovkStrictConfig } from 'vovk/internal';
|
|
3
|
-
import type { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
4
4
|
import type { ProjectInfo } from '../get-project-info/index.mjs';
|
|
5
5
|
export type GetOpenAPINameFn = (config: {
|
|
6
6
|
operationObject: VovkOperationObject;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import camelCase from 'lodash/camelCase.js';
|
|
3
4
|
import * as YAML from 'yaml';
|
|
4
5
|
import { chalkHighlightThing } from './chalk-highlight-thing.mjs';
|
|
5
|
-
import camelCase from 'lodash/camelCase.js';
|
|
6
6
|
import { generateFnName } from './generate-fn-name.mjs';
|
|
7
7
|
const normalizeGetModuleName = (getModuleName) => {
|
|
8
|
+
getModuleName = getModuleName ?? 'api';
|
|
8
9
|
if (typeof getModuleName === 'string') {
|
|
9
10
|
const moduleName = getModuleName;
|
|
10
11
|
getModuleName = () => moduleName;
|
|
@@ -15,6 +16,7 @@ const normalizeGetModuleName = (getModuleName) => {
|
|
|
15
16
|
return getModuleName;
|
|
16
17
|
};
|
|
17
18
|
const normalizeGetMethodName = (getMethodName) => {
|
|
19
|
+
getMethodName = getMethodName ?? 'auto';
|
|
18
20
|
if (getMethodName === 'camel-case-operation-id') {
|
|
19
21
|
getMethodName = ({ operationObject }) => {
|
|
20
22
|
const operationId = operationObject.operationId;
|
|
@@ -47,8 +49,21 @@ async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
|
47
49
|
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
48
50
|
}
|
|
49
51
|
async function getOpenApiSpecRemote({ cwd, url, fallback, log, }) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
let resp;
|
|
53
|
+
let text;
|
|
54
|
+
try {
|
|
55
|
+
resp = await fetch(url);
|
|
56
|
+
text = await resp.text();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// network failures (DNS, offline, timeouts) should use the fallback too
|
|
60
|
+
const message = `Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${error.message}`;
|
|
61
|
+
if (fallback) {
|
|
62
|
+
log.warn(`${message}. Falling back to ${chalkHighlightThing(fallback)}`);
|
|
63
|
+
return getOpenApiSpecLocal(fallback, cwd);
|
|
64
|
+
}
|
|
65
|
+
throw new Error(message);
|
|
66
|
+
}
|
|
52
67
|
if (!resp.ok) {
|
|
53
68
|
if (fallback) {
|
|
54
69
|
log.warn(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}. Falling back to ${chalkHighlightThing(fallback)}`);
|
|
@@ -1 +1,5 @@
|
|
|
1
|
+
import type { getLogger } from './get-logger.mjs';
|
|
2
|
+
/** Formats with the project-installed prettier; returns the code unchanged when it's not installed. */
|
|
1
3
|
export declare function prettify(code: string, absoluteFilePath: string): Promise<string>;
|
|
4
|
+
/** Warns once per run when prettifyClient is enabled but prettier is not installed. */
|
|
5
|
+
export declare function warnIfPrettierMissing(log: ReturnType<typeof getLogger>): Promise<void>;
|
package/dist/utils/prettify.mjs
CHANGED
|
@@ -1,10 +1,46 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
let prettierPromise;
|
|
5
|
+
let warned = false;
|
|
6
|
+
/** Prettier is not a CLI dependency; it's resolved from the user's project if installed. */
|
|
7
|
+
function getPrettier() {
|
|
8
|
+
prettierPromise ??= (async () => {
|
|
9
|
+
try {
|
|
10
|
+
const require = createRequire(path.join(process.cwd(), 'noop.js'));
|
|
11
|
+
const mod = (await import(pathToFileURL(require.resolve('prettier')).href));
|
|
12
|
+
return mod.default ?? mod;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
})();
|
|
18
|
+
return prettierPromise;
|
|
19
|
+
}
|
|
20
|
+
/** Formats with the project-installed prettier; returns the code unchanged when it's not installed. */
|
|
2
21
|
export async function prettify(code, absoluteFilePath) {
|
|
22
|
+
const prettier = await getPrettier();
|
|
23
|
+
if (!prettier)
|
|
24
|
+
return code;
|
|
3
25
|
const options = await prettier.resolveConfig(absoluteFilePath);
|
|
4
26
|
const finalOptions = {
|
|
5
27
|
...options,
|
|
6
28
|
filepath: absoluteFilePath, // for selecting the correct parser
|
|
7
29
|
};
|
|
8
|
-
|
|
9
|
-
|
|
30
|
+
try {
|
|
31
|
+
return await prettier.format(code, finalOptions);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// no parser for this file type (.rs, .toml, .py etc)
|
|
35
|
+
if (error.name === 'UndefinedParserError')
|
|
36
|
+
return code;
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Warns once per run when prettifyClient is enabled but prettier is not installed. */
|
|
41
|
+
export async function warnIfPrettierMissing(log) {
|
|
42
|
+
if (warned || (await getPrettier()))
|
|
43
|
+
return;
|
|
44
|
+
warned = true;
|
|
45
|
+
log.warn('prettifyClient is enabled but prettier is not installed. Either install it or set prettifyClient to false to suppress this warning.');
|
|
10
46
|
}
|
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Removes all directories in a folder that aren't in the provided allowlist
|
|
3
|
-
* Supports nested directory paths like 'foo/bar/baz'
|
|
4
|
-
*
|
|
5
|
-
* @param folderPath - The path to the folder to process
|
|
6
|
-
* @param allowedDirs - Array of relative directory paths to keep
|
|
7
|
-
* @returns Promise that resolves when all operations are complete
|
|
8
|
-
*/
|
|
9
|
-
export declare function removeUnlistedDirectories(folderPath: string, allowedDirs: string[]): Promise<void>;
|
|
1
|
+
export declare function removeUnlistedDirectories(folderPath: string, allowedDirs: string[], generatedRelPaths?: string[]): Promise<string[]>;
|