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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `vovk-cli` are documented here. This file is the canonical record: noteworthy changes only, one short line each, linked to the pull request or commit that made them. GitHub Releases are not used.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## 0.3.0-beta.0 - unreleased
|
|
8
|
+
|
|
9
|
+
A cleanup release. `vovk-cli` is pre-1.0, so this minor carries breaking changes: `^0.2.0` does not match `0.3.0`.
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- The `vovk-client` package: the composed client generates into your source tree (`src/client`, or `client/` without a `src` folder) and is imported as `@/client`, so it survives `npm ci` and works under pnpm and Yarn PnP ([#29](https://github.com/finom/vovk/pull/29))
|
|
14
|
+
- The `js` template family (`js`, `jsBase`, `schemaJs`, `openapiJs`): use `ts` ([#29](https://github.com/finom/vovk/pull/29))
|
|
15
|
+
- The `prettier` dependency: prettifying resolves prettier from your project and warns once when it is missing ([#29](https://github.com/finom/vovk/pull/29))
|
|
16
|
+
- Unused runtime deps `clone-deep`, `inflection` and `tar-stream` ([25a2f20](https://github.com/finom/vovk/commit/25a2f203)), the pinned linux rolldown binding ([d0cae19](https://github.com/finom/vovk/commit/d0cae19c)), and the pre-kebab collision guard in `vovk new` ([b7dc4b2](https://github.com/finom/vovk/commit/b7dc4b2a))
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Composed client defaults: `fromTemplates: ['ts']`, `outDir: src/client`, `prettifyClient: true`; `vovk init` gitignores the client dir and no longer installs `vovk-client` ([#29](https://github.com/finom/vovk/pull/29))
|
|
21
|
+
- Broken input fails fast, and the CLI reports what it actually did ([0ad9872](https://github.com/finom/vovk/commit/0ad9872e))
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- The pruner only deletes files carrying the generated banner, so user files named like generated ones survive ([e9f269c](https://github.com/finom/vovk/commit/e9f269ce), [88065d0](https://github.com/finom/vovk/commit/88065d0c))
|
|
26
|
+
- Schema output cleanup only deletes JSON carrying vovk's own `$schema` id ([9816dda](https://github.com/finom/vovk/commit/9816dda4))
|
|
27
|
+
- A fetched schema's `segmentName` is no longer trusted as a write path ([d93a240](https://github.com/finom/vovk/commit/d93a240c))
|
|
28
|
+
- `vovk init` only spawns a known package manager, never a path from `packageManager` ([053cb1a](https://github.com/finom/vovk/commit/053cb1a8))
|
|
29
|
+
- `--prebundle-out-dir` takes effect and stays inside the project ([22f505c](https://github.com/finom/vovk/commit/22f505c6))
|
|
30
|
+
- `vovk new segment` scaffolds `force-static` for static segments ([#25](https://github.com/finom/vovk/pull/25))
|
|
31
|
+
|
|
32
|
+
### Upgrading from 0.2.x
|
|
33
|
+
|
|
34
|
+
1. Remove `vovk-client` from your dependencies and import from `@/client` (or a relative path to the configured `outDir`).
|
|
35
|
+
2. Replace `fromTemplates: ['js']` and friends with `['ts']`.
|
|
36
|
+
3. Install `prettier` yourself if you want the client formatted, or set `prettifyClient: false`.
|
|
37
|
+
4. Add the composed client output directory to `.gitignore`; `vovk init` does this for new projects.
|
|
38
|
+
5. A stale `node_modules/.vovk-client` is inert and can be deleted.
|
package/dist/bundle/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
3
|
import groupBy from 'lodash/groupBy.js';
|
|
4
4
|
import { generate } from '../generate/generate.mjs';
|
|
5
5
|
import { BuiltInTemplateName } from '../get-project-info/get-config/get-template-defs.mjs';
|
|
@@ -9,46 +9,32 @@ export async function bundle({ projectInfo, fullSchema, cliBundleOptions, }) {
|
|
|
9
9
|
const { config, log, cwd, apiDirAbsolutePath } = projectInfo;
|
|
10
10
|
const locatedSegments = await locateSegments({ dir: apiDirAbsolutePath, config, log });
|
|
11
11
|
const { bundle: bundleConfig } = config;
|
|
12
|
+
if (bundleConfig.build.isMissingBuild) {
|
|
13
|
+
throw new Error('No bundle.build function specified in the config. See https://vovk.dev/bundle for details.');
|
|
14
|
+
}
|
|
12
15
|
const keepPrebundleDir = cliBundleOptions?.keepPrebundleDir ?? bundleConfig?.keepPrebundleDir ?? false;
|
|
13
|
-
const
|
|
16
|
+
const prebundleOutDir = cliBundleOptions?.prebundleOutDir ?? bundleConfig.prebundleOutDir;
|
|
17
|
+
const prebundleOutDirAbsolute = path.resolve(cwd, prebundleOutDir);
|
|
18
|
+
// this directory gets removed recursively, so it must be a directory of our own inside the project
|
|
19
|
+
const relativeToCwd = path.relative(cwd, prebundleOutDirAbsolute);
|
|
20
|
+
const escapesCwd = relativeToCwd === '..' || relativeToCwd.startsWith(`..${path.sep}`);
|
|
21
|
+
if (!relativeToCwd || escapesCwd || path.isAbsolute(relativeToCwd)) {
|
|
22
|
+
throw new Error(`Invalid prebundle output directory ${JSON.stringify(prebundleOutDir)}. It is deleted after bundling, so it must be a subdirectory of the project, such as "tmp_prebundle".`);
|
|
23
|
+
}
|
|
14
24
|
const entry = path.join(prebundleOutDirAbsolute, 'index.ts');
|
|
15
25
|
const outDir = cliBundleOptions?.outDir ?? bundleConfig.outDir;
|
|
16
26
|
if (!outDir) {
|
|
17
27
|
throw new Error('No output directory specified for bundling');
|
|
18
28
|
}
|
|
29
|
+
// CLI options win as a pair so config exclude cannot conflict with CLI include
|
|
30
|
+
const [includeSegments, excludeSegments] = cliBundleOptions.includeSegments?.length || cliBundleOptions.excludeSegments?.length
|
|
31
|
+
? [cliBundleOptions.includeSegments, cliBundleOptions.excludeSegments]
|
|
32
|
+
: [bundleConfig.includeSegments, bundleConfig.excludeSegments];
|
|
33
|
+
if (includeSegments?.length && excludeSegments?.length) {
|
|
34
|
+
throw new Error('Both includeSegments and excludeSegments are set for the bundle. Please use only one of them.');
|
|
35
|
+
}
|
|
19
36
|
const outDirAbsolute = path.resolve(cwd, outDir);
|
|
20
|
-
|
|
21
|
-
isEnsuringClient: false,
|
|
22
|
-
isBundle: true,
|
|
23
|
-
projectInfo,
|
|
24
|
-
forceNothingWrittenLog: true,
|
|
25
|
-
fullSchema,
|
|
26
|
-
locatedSegments,
|
|
27
|
-
cliGenerateOptions: {
|
|
28
|
-
schemaPath: cliBundleOptions?.schemaPath,
|
|
29
|
-
origin: cliBundleOptions?.origin,
|
|
30
|
-
openapiSpec: cliBundleOptions?.openapiSpec,
|
|
31
|
-
openapiGetModuleName: cliBundleOptions?.openapiGetModuleName,
|
|
32
|
-
openapiGetMethodName: cliBundleOptions?.openapiGetMethodName,
|
|
33
|
-
openapiRootUrl: cliBundleOptions?.openapiRootUrl,
|
|
34
|
-
openapiMixinName: cliBundleOptions?.openapiMixinName,
|
|
35
|
-
openapiFallback: cliBundleOptions?.openapiFallback,
|
|
36
|
-
composedFrom: [BuiltInTemplateName.tsBase],
|
|
37
|
-
composedOut: prebundleOutDirAbsolute,
|
|
38
|
-
composedOnly: true,
|
|
39
|
-
composedIncludeSegments: cliBundleOptions.includeSegments ?? bundleConfig.includeSegments,
|
|
40
|
-
composedExcludeSegments: cliBundleOptions.excludeSegments ?? bundleConfig.excludeSegments,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
log.debug(`Bundling ${chalkHighlightThing(entry)} to ${chalkHighlightThing(outDirAbsolute)}`);
|
|
44
|
-
await bundleConfig.build({
|
|
45
|
-
outDir: outDirAbsolute,
|
|
46
|
-
prebundleDir: prebundleOutDirAbsolute,
|
|
47
|
-
entry,
|
|
48
|
-
});
|
|
49
|
-
log.debug(`Bundled index.ts to ${chalkHighlightThing(outDirAbsolute)}`);
|
|
50
|
-
const requiresGroup = groupBy(Object.entries(bundleConfig.requires), ([, relativePath]) => relativePath);
|
|
51
|
-
for (const [relativePath, group] of Object.entries(requiresGroup)) {
|
|
37
|
+
try {
|
|
52
38
|
await generate({
|
|
53
39
|
isEnsuringClient: false,
|
|
54
40
|
isBundle: true,
|
|
@@ -59,18 +45,54 @@ export async function bundle({ projectInfo, fullSchema, cliBundleOptions, }) {
|
|
|
59
45
|
cliGenerateOptions: {
|
|
60
46
|
schemaPath: cliBundleOptions?.schemaPath,
|
|
61
47
|
origin: cliBundleOptions?.origin,
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
openapiSpec: cliBundleOptions?.openapiSpec,
|
|
49
|
+
openapiGetModuleName: cliBundleOptions?.openapiGetModuleName,
|
|
50
|
+
openapiGetMethodName: cliBundleOptions?.openapiGetMethodName,
|
|
51
|
+
openapiRootUrl: cliBundleOptions?.openapiRootUrl,
|
|
52
|
+
openapiMixinName: cliBundleOptions?.openapiMixinName,
|
|
53
|
+
openapiFallback: cliBundleOptions?.openapiFallback,
|
|
54
|
+
composedFrom: [BuiltInTemplateName.tsBase],
|
|
55
|
+
composedOut: prebundleOutDirAbsolute,
|
|
64
56
|
composedOnly: true,
|
|
57
|
+
composedIncludeSegments: includeSegments,
|
|
58
|
+
composedExcludeSegments: excludeSegments,
|
|
65
59
|
},
|
|
66
60
|
});
|
|
61
|
+
log.debug(`Bundling ${chalkHighlightThing(entry)} to ${chalkHighlightThing(outDirAbsolute)}`);
|
|
62
|
+
await bundleConfig.build({
|
|
63
|
+
outDir: outDirAbsolute,
|
|
64
|
+
prebundleDir: prebundleOutDirAbsolute,
|
|
65
|
+
entry,
|
|
66
|
+
});
|
|
67
|
+
log.debug(`Bundled index.ts to ${chalkHighlightThing(outDirAbsolute)}`);
|
|
68
|
+
const requiresGroup = groupBy(Object.entries(bundleConfig.requires), ([, relativePath]) => relativePath);
|
|
69
|
+
for (const [relativePath, group] of Object.entries(requiresGroup)) {
|
|
70
|
+
await generate({
|
|
71
|
+
isEnsuringClient: false,
|
|
72
|
+
isBundle: true,
|
|
73
|
+
projectInfo,
|
|
74
|
+
forceNothingWrittenLog: true,
|
|
75
|
+
fullSchema,
|
|
76
|
+
locatedSegments,
|
|
77
|
+
cliGenerateOptions: {
|
|
78
|
+
schemaPath: cliBundleOptions?.schemaPath,
|
|
79
|
+
origin: cliBundleOptions?.origin,
|
|
80
|
+
composedFrom: group.map(([templateName]) => templateName),
|
|
81
|
+
composedOut: path.resolve(outDirAbsolute, relativePath),
|
|
82
|
+
composedOnly: true,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
67
86
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
finally {
|
|
88
|
+
// clean up the prebundle dir even when generation or build fails
|
|
89
|
+
if (!keepPrebundleDir) {
|
|
90
|
+
await fs.rm(prebundleOutDirAbsolute, { recursive: true, force: true });
|
|
91
|
+
log.debug(`Deleted temporary TypeScript client output directory: ${chalkHighlightThing(prebundleOutDirAbsolute)}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
log.debug(`Temporary TypeScript client output directory not deleted because it is marked to keep: ${chalkHighlightThing(prebundleOutDirAbsolute)}`);
|
|
95
|
+
}
|
|
74
96
|
}
|
|
75
97
|
log.info(`Bundled TypeScript client to ${chalkHighlightThing(outDirAbsolute)}`);
|
|
76
98
|
}
|
|
@@ -15,22 +15,5 @@ export interface DiffResult {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function diffHandlers<T extends VovkControllerSchema['handlers']>(oldHandlers: T, newHandlers: T, nameOfClass: string): HandlersDiff;
|
|
17
17
|
export declare function diffControllers<T extends VovkSegmentSchema['controllers']>(oldItems: T, newItems: T): ControllersDiff;
|
|
18
|
-
/**
|
|
19
|
-
example output:
|
|
20
|
-
{
|
|
21
|
-
controllers: {
|
|
22
|
-
added: ["ControllerC"],
|
|
23
|
-
removed: ["ControllerB"],
|
|
24
|
-
handlers: [
|
|
25
|
-
{
|
|
26
|
-
nameOfClass: "ControllerA",
|
|
27
|
-
added: ["handlerF"],
|
|
28
|
-
removed: [],
|
|
29
|
-
changed: ["handlerD"]
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
*/
|
|
35
18
|
export declare function diffSegmentSchema(oldJson: VovkSegmentSchema, newJson: VovkSegmentSchema): DiffResult;
|
|
36
19
|
export {};
|
|
@@ -40,23 +40,7 @@ export function diffControllers(oldItems, newItems) {
|
|
|
40
40
|
}
|
|
41
41
|
return { added, removed, handlers: handlersDiff };
|
|
42
42
|
}
|
|
43
|
-
|
|
44
|
-
example output:
|
|
45
|
-
{
|
|
46
|
-
controllers: {
|
|
47
|
-
added: ["ControllerC"],
|
|
48
|
-
removed: ["ControllerB"],
|
|
49
|
-
handlers: [
|
|
50
|
-
{
|
|
51
|
-
nameOfClass: "ControllerA",
|
|
52
|
-
added: ["handlerF"],
|
|
53
|
-
removed: [],
|
|
54
|
-
changed: ["handlerD"]
|
|
55
|
-
}
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
*/
|
|
43
|
+
// returns e.g. { controllers: { added: [...], removed: [...], handlers: [{ nameOfClass, added, removed, changed }] } }
|
|
60
44
|
export function diffSegmentSchema(oldJson, newJson) {
|
|
61
45
|
return {
|
|
62
46
|
controllers: diffControllers(oldJson.controllers ?? {}, newJson.controllers ?? {}),
|
|
@@ -2,9 +2,9 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import debounce from 'lodash/debounce.js';
|
|
4
4
|
import { VovkSchemaIdEnum } from 'vovk/internal';
|
|
5
|
-
import { writeOneSegmentSchemaFile, META_FILE_NAME, ROOT_SEGMENT_FILE_NAME } from './write-one-segment-schema-file.mjs';
|
|
6
5
|
import { formatLoggedSegmentName } from '../utils/format-logged-segment-name.mjs';
|
|
7
6
|
import { writeMetaJson } from './write-meta-json.mjs';
|
|
7
|
+
import { META_FILE_NAME, ROOT_SEGMENT_FILE_NAME, writeOneSegmentSchemaFile } from './write-one-segment-schema-file.mjs';
|
|
8
8
|
/**
|
|
9
9
|
* Ensure that the schema files are created to avoid any import errors.
|
|
10
10
|
*/
|
|
@@ -31,20 +31,34 @@ export async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePath, segm
|
|
|
31
31
|
hasChanged = true;
|
|
32
32
|
}
|
|
33
33
|
}));
|
|
34
|
-
//
|
|
34
|
+
// only a file carrying the vovk $schema id is ours to delete
|
|
35
|
+
async function isVovkSchemaFile(absolutePath) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(await fs.readFile(absolutePath, 'utf-8'));
|
|
38
|
+
return typeof parsed.$schema === 'string' && parsed.$schema.startsWith('https://vovk.dev/api/schema/');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Recursive function to delete unnecessary JSON files and folders, returns true if anything was deleted
|
|
35
45
|
async function deleteUnnecessaryJsonFiles(dirPath, allow = [`${META_FILE_NAME}.json`]) {
|
|
36
46
|
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
47
|
+
let hasDeleted = false;
|
|
37
48
|
await Promise.all(entries.map(async (entry) => {
|
|
38
49
|
const absolutePath = path.join(dirPath, entry.name);
|
|
39
50
|
if (entry.isDirectory()) {
|
|
40
51
|
// Recursively delete unnecessary files and folders within nested directories
|
|
41
|
-
await deleteUnnecessaryJsonFiles(absolutePath);
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
52
|
+
const deletedInside = await deleteUnnecessaryJsonFiles(absolutePath);
|
|
53
|
+
// remove the directory only when this cleanup emptied it, a pre-existing empty dir is not ours
|
|
54
|
+
if (deletedInside) {
|
|
55
|
+
const remainingEntries = await fs.readdir(absolutePath);
|
|
56
|
+
if (remainingEntries.length === 0) {
|
|
57
|
+
await fs.rmdir(absolutePath);
|
|
58
|
+
projectInfo?.log.debug(`Deleted unnecessary schema directory "${absolutePath}"`);
|
|
59
|
+
hasChanged = true;
|
|
60
|
+
hasDeleted = true;
|
|
61
|
+
}
|
|
48
62
|
}
|
|
49
63
|
}
|
|
50
64
|
else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
@@ -52,13 +66,17 @@ export async function ensureSchemaFiles(projectInfo, schemaOutAbsolutePath, segm
|
|
|
52
66
|
const segmentName = relativePath.replace(/\\/g, '/').slice(0, -5); // Remove '.json' extension
|
|
53
67
|
if (!segmentNames.includes(segmentName) &&
|
|
54
68
|
!segmentNames.includes(segmentName.replace(ROOT_SEGMENT_FILE_NAME, '')) &&
|
|
55
|
-
!allow.includes(entry.name)
|
|
69
|
+
!allow.includes(entry.name) &&
|
|
70
|
+
// never delete a json file vovk did not write, schemaOutDir may point at user dirs
|
|
71
|
+
(await isVovkSchemaFile(absolutePath))) {
|
|
56
72
|
await fs.unlink(absolutePath);
|
|
57
73
|
projectInfo?.log.debug(`Deleted unnecessary schema file for ${formatLoggedSegmentName(segmentName)}`);
|
|
58
74
|
hasChanged = true;
|
|
75
|
+
hasDeleted = true;
|
|
59
76
|
}
|
|
60
77
|
}
|
|
61
78
|
}));
|
|
79
|
+
return hasDeleted;
|
|
62
80
|
}
|
|
63
81
|
await deleteUnnecessaryJsonFiles(schemaOutAbsolutePath);
|
|
64
82
|
if (hasChanged)
|
package/dist/dev/index.mjs
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { VovkSchemaIdEnum } from 'vovk/internal';
|
|
4
3
|
import * as chokidar from 'chokidar';
|
|
5
|
-
import { Agent, setGlobalDispatcher } from 'undici';
|
|
6
|
-
import keyBy from 'lodash/keyBy.js';
|
|
7
4
|
import capitalize from 'lodash/capitalize.js';
|
|
8
5
|
import debounce from 'lodash/debounce.js';
|
|
9
|
-
import once from 'lodash/once.js';
|
|
10
6
|
import isEmpty from 'lodash/isEmpty.js';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
7
|
+
import keyBy from 'lodash/keyBy.js';
|
|
8
|
+
import once from 'lodash/once.js';
|
|
9
|
+
import { Agent, setGlobalDispatcher } from 'undici';
|
|
10
|
+
import { VovkSchemaIdEnum } from 'vovk/internal';
|
|
14
11
|
import { ensureClient } from '../generate/ensure-client.mjs';
|
|
15
|
-
import { getProjectInfo } from '../get-project-info/index.mjs';
|
|
16
12
|
import { generate } from '../generate/generate.mjs';
|
|
17
|
-
import {
|
|
13
|
+
import { getMetaSchema } from '../get-project-info/get-meta-schema.mjs';
|
|
14
|
+
import { getProjectInfo } from '../get-project-info/index.mjs';
|
|
15
|
+
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
18
16
|
import { debounceWithArgs } from '../utils/debounce-with-args.mjs';
|
|
19
17
|
import { formatLoggedSegmentName } from '../utils/format-logged-segment-name.mjs';
|
|
18
|
+
import { locateSegments } from '../utils/locate-segments.mjs';
|
|
19
|
+
import { debouncedEnsureSchemaFiles, ensureSchemaFiles } from './ensure-schema-files.mjs';
|
|
20
|
+
import { logDiffResult } from './log-diff-result.mjs';
|
|
20
21
|
import { writeMetaJson } from './write-meta-json.mjs';
|
|
21
|
-
import {
|
|
22
|
-
import { getMetaSchema } from '../get-project-info/get-meta-schema.mjs';
|
|
22
|
+
import { writeOneSegmentSchemaFile } from './write-one-segment-schema-file.mjs';
|
|
23
23
|
export class VovkDev {
|
|
24
24
|
#projectInfo;
|
|
25
25
|
#segments = [];
|
|
@@ -33,7 +33,8 @@ export class VovkDev {
|
|
|
33
33
|
#logLevel;
|
|
34
34
|
constructor({ schemaOut, devHttps, logLevel }) {
|
|
35
35
|
this.#schemaOut = schemaOut || null;
|
|
36
|
-
|
|
36
|
+
// null when the flag is omitted so config.devHttps can take effect
|
|
37
|
+
this.#devHttps = devHttps ?? null;
|
|
37
38
|
this.#logLevel = logLevel || 'info';
|
|
38
39
|
}
|
|
39
40
|
#watchSegments = (callback) => {
|
|
@@ -304,6 +305,11 @@ export class VovkDev {
|
|
|
304
305
|
return;
|
|
305
306
|
}
|
|
306
307
|
log.debug(`Handling received schema from ${formatLoggedSegmentName(segmentName)}`);
|
|
308
|
+
// the write path is built from segmentName, an http response must not name a different segment
|
|
309
|
+
if ((segmentSchema.segmentName ?? '') !== segmentName) {
|
|
310
|
+
log.error(`Schema for ${formatLoggedSegmentName(segmentName)} reported a different segment name ${JSON.stringify(segmentSchema.segmentName)}, ignoring it`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
307
313
|
const schemaOutAbsolutePath = path.resolve(cwd, this.#schemaOut ?? config.schemaOutDir);
|
|
308
314
|
const segment = this.#segments.find((s) => s.segmentName === segmentName);
|
|
309
315
|
if (!segment) {
|
|
@@ -402,7 +408,7 @@ const env = process.env;
|
|
|
402
408
|
if (env.__VOVK_START_WATCHER_IN_STANDALONE_MODE__ === 'true') {
|
|
403
409
|
void new VovkDev({
|
|
404
410
|
schemaOut: env.__VOVK_SCHEMA_OUT_FLAG__ || undefined,
|
|
405
|
-
devHttps: env.__VOVK_DEV_HTTPS_FLAG__ === 'true',
|
|
411
|
+
devHttps: env.__VOVK_DEV_HTTPS_FLAG__ === 'true' || undefined,
|
|
406
412
|
logLevel: env.__VOVK_LOG_LEVEL__,
|
|
407
413
|
}).start({
|
|
408
414
|
exit: env.__VOVK_EXIT__ === 'true',
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { DiffResult } from './diff-segment-schema.mjs';
|
|
2
1
|
import type { ProjectInfo } from '../get-project-info/index.mjs';
|
|
2
|
+
import type { DiffResult } from './diff-segment-schema.mjs';
|
|
3
3
|
export declare function logDiffResult(segmentName: string, diffResult: DiffResult, projectInfo: ProjectInfo): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { formatLoggedSegmentName } from '../utils/format-logged-segment-name.mjs';
|
|
3
2
|
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
3
|
+
import { formatLoggedSegmentName } from '../utils/format-logged-segment-name.mjs';
|
|
4
4
|
export function logDiffResult(segmentName, diffResult, projectInfo) {
|
|
5
5
|
const diffNormalized = [];
|
|
6
6
|
diffResult.controllers.added.forEach((name) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { META_FILE_NAME } from './write-one-segment-schema-file.mjs';
|
|
4
|
-
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
5
3
|
import { getMetaSchema } from '../get-project-info/get-meta-schema.mjs';
|
|
4
|
+
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
5
|
+
import { META_FILE_NAME } from './write-one-segment-schema-file.mjs';
|
|
6
6
|
export async function writeMetaJson(schemaOutAbsolutePath, projectInfo) {
|
|
7
7
|
const metaJsonPath = path.join(schemaOutAbsolutePath, `${META_FILE_NAME}.json`);
|
|
8
8
|
const metaStr = JSON.stringify(getMetaSchema({
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
3
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
4
3
|
import { getFileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
|
|
4
|
+
import { diffSegmentSchema } from './diff-segment-schema.mjs';
|
|
5
5
|
export const ROOT_SEGMENT_FILE_NAME = 'root';
|
|
6
6
|
export const META_FILE_NAME = '_meta';
|
|
7
7
|
export async function writeOneSegmentSchemaFile({ schemaOutAbsolutePath, segmentSchema, skipIfExists = false, }) {
|
|
8
8
|
const segmentPath = path.join(schemaOutAbsolutePath, `${segmentSchema.segmentName || ROOT_SEGMENT_FILE_NAME}.json`);
|
|
9
|
+
// segmentName may come from an http response, keep the write inside the schema out dir
|
|
10
|
+
const relativeToOut = path.relative(schemaOutAbsolutePath, segmentPath);
|
|
11
|
+
if (relativeToOut.startsWith(`..${path.sep}`) || relativeToOut === '..' || path.isAbsolute(relativeToOut)) {
|
|
12
|
+
throw new Error(`Refusing to write schema outside ${schemaOutAbsolutePath} for segment ${JSON.stringify(segmentSchema.segmentName)}`);
|
|
13
|
+
}
|
|
9
14
|
if (skipIfExists && (await getFileSystemEntryType(segmentPath))) {
|
|
10
15
|
try {
|
|
11
16
|
await fs.stat(segmentPath);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VovkSchemaIdEnum } from 'vovk/internal';
|
|
2
|
-
import { generate } from './generate.mjs';
|
|
3
2
|
import { getMetaSchema } from '../get-project-info/get-meta-schema.mjs';
|
|
3
|
+
import { generate } from './generate.mjs';
|
|
4
4
|
const getEmptySegmentRecordSchema = (segmentNames) => {
|
|
5
5
|
const result = {};
|
|
6
6
|
for (const segmentName of segmentNames) {
|
|
@@ -1,40 +1,48 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
3
4
|
import matter from 'gray-matter';
|
|
4
5
|
import _ from 'lodash';
|
|
5
6
|
import { openAPIToVovkSchema, vovkSchemaToOpenAPI } from 'vovk/internal';
|
|
6
|
-
import {
|
|
7
|
+
import { ROOT_SEGMENT_FILE_NAME } from '../dev/write-one-segment-schema-file.mjs';
|
|
8
|
+
import { BuiltInTemplateName } from '../get-project-info/get-config/get-template-defs.mjs';
|
|
7
9
|
import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
|
|
10
|
+
import { normalizeOpenAPIMixin } from '../utils/normalize-openapi-mixin.mjs';
|
|
8
11
|
import { pickSegmentFullSchema } from '../utils/pick-segment-full-schema.mjs';
|
|
9
12
|
import { removeUnlistedDirectories } from '../utils/remove-unlisted-directories.mjs';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { getTsconfig } from 'get-tsconfig';
|
|
13
|
-
import { normalizeOpenAPIMixin } from '../utils/normalize-openapi-mixin.mjs';
|
|
14
|
-
import { BuiltInTemplateName } from '../get-project-info/get-config/get-template-defs.mjs';
|
|
13
|
+
import { getClientTemplateFiles } from './get-client-template-files.mjs';
|
|
14
|
+
import { normalizeOutTemplatePath, writeOneClientFile } from './write-one-client-file.mjs';
|
|
15
15
|
const getIncludedSegmentNames = (config, fullSchema, configKey, cliGenerateOptions) => {
|
|
16
16
|
const segments = Object.values(fullSchema.segments);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const cliIncludeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedIncludeSegments' : 'composedIncludeSegments'];
|
|
18
|
+
const cliExcludeSegments = cliGenerateOptions?.[configKey === 'segmentedClient' ? 'segmentedExcludeSegments' : 'composedExcludeSegments'];
|
|
19
|
+
// CLI options win as a pair so config exclude cannot conflict with CLI include
|
|
20
|
+
const isFromCli = !!(cliIncludeSegments?.length || cliExcludeSegments?.length);
|
|
21
|
+
const includeSegments = isFromCli ? cliIncludeSegments : config[configKey].includeSegments;
|
|
22
|
+
const excludeSegments = isFromCli ? cliExcludeSegments : config[configKey].excludeSegments;
|
|
21
23
|
if (includeSegments?.length && excludeSegments?.length) {
|
|
22
|
-
throw new Error(`Both includeSegments and excludeSegments are set in "${configKey}" config. Please use only one of them.`);
|
|
24
|
+
throw new Error(`Both includeSegments and excludeSegments are set ${isFromCli ? 'as CLI options' : `in "${configKey}" config`}. Please use only one of them.`);
|
|
23
25
|
}
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (!
|
|
26
|
+
const segmentExists = (segmentName) => segments.some(({ segmentName: sName }) => sName === segmentName);
|
|
27
|
+
if (includeSegments?.length) {
|
|
28
|
+
for (const segmentName of includeSegments) {
|
|
29
|
+
if (!segmentExists(segmentName)) {
|
|
28
30
|
throw new Error(`Segment "${segmentName}" not found in the config for "${configKey}"`);
|
|
29
31
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
}
|
|
33
|
+
return includeSegments;
|
|
34
|
+
}
|
|
35
|
+
if (excludeSegments?.length) {
|
|
36
|
+
for (const segmentName of excludeSegments) {
|
|
37
|
+
if (!segmentExists(segmentName)) {
|
|
38
|
+
throw new Error(`Segment "${segmentName}" from excludeSegments not found in the config for "${configKey}"`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return segments
|
|
42
|
+
.filter(({ segmentName }) => !excludeSegments.includes(segmentName))
|
|
43
|
+
.map(({ segmentName }) => segmentName);
|
|
44
|
+
}
|
|
45
|
+
return segments.map(({ segmentName }) => segmentName);
|
|
38
46
|
};
|
|
39
47
|
function logClientGenerationResults({ results, log, isEnsuringClient = false, forceNothingWrittenLog = false, clientType = 'Composed', startTime, fromTemplates, }) {
|
|
40
48
|
const writtenResults = results.filter(({ written }) => written);
|
|
@@ -221,6 +229,8 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
221
229
|
cliGenerateOptions,
|
|
222
230
|
configKey: 'segmentedClient',
|
|
223
231
|
});
|
|
232
|
+
// what a generated file may look like inside a segment directory, used to spare user files when pruning
|
|
233
|
+
const generatedRelPaths = segmentedClientTemplateFiles.map(({ templateFilePath, relativeDir }) => path.join(relativeDir, path.basename(templateFilePath).replace(/\.ejs$/, '')));
|
|
224
234
|
const segmentedClientResults = await Promise.all(segmentedClientTemplateFiles.map(async (clientTemplateFile) => {
|
|
225
235
|
const { templateFilePath, templateName, templateDef, outCwdRelativeDir } = clientTemplateFile;
|
|
226
236
|
const templateContent = await fs.readFile(templateFilePath, 'utf-8');
|
|
@@ -280,7 +290,10 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
|
|
|
280
290
|
}));
|
|
281
291
|
const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
|
|
282
292
|
// Remove unlisted directories in the output directory
|
|
283
|
-
await removeUnlistedDirectories(outAbsoluteDir, segmentNames.map((s) => s || ROOT_SEGMENT_FILE_NAME));
|
|
293
|
+
const skippedDirs = await removeUnlistedDirectories(outAbsoluteDir, segmentNames.map((s) => s || ROOT_SEGMENT_FILE_NAME), generatedRelPaths);
|
|
294
|
+
for (const skippedDir of skippedDirs) {
|
|
295
|
+
log.warn(`Directory ${chalkHighlightThing(skippedDir)} is not a known segment but holds files that were not generated, so it is left untouched.`);
|
|
296
|
+
}
|
|
284
297
|
return {
|
|
285
298
|
written: results.filter((result) => !!result).some(({ written }) => written),
|
|
286
299
|
templateName,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { glob } from 'glob';
|
|
3
|
-
import { resolveAbsoluteModulePath } from '../utils/resolve-absolute-module-path.mjs';
|
|
4
|
-
import { getFileSystemEntryType, FileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
|
|
5
|
-
import { getPublicModuleNameFromPath } from '../utils/get-public-module-name-from-path.mjs';
|
|
6
|
-
import omit from 'lodash/omit.js';
|
|
7
3
|
import merge from 'lodash/merge.js';
|
|
4
|
+
import omit from 'lodash/omit.js';
|
|
5
|
+
import { FileSystemEntryType, getFileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
|
|
6
|
+
import { getPublicModuleNameFromPath } from '../utils/get-public-module-name-from-path.mjs';
|
|
7
|
+
import { resolveAbsoluteModulePath } from '../utils/resolve-absolute-module-path.mjs';
|
|
8
8
|
export async function getClientTemplateFiles({ config, cwd, log, configKey, cliGenerateOptions, }) {
|
|
9
9
|
const usedTemplateDefs = {};
|
|
10
10
|
const fromTemplates = configKey === 'composedClient'
|
|
@@ -18,6 +18,10 @@ export async function getClientTemplateFiles({ config, cwd, log, configKey, cliG
|
|
|
18
18
|
const configOutDir = config[configKey].outDir;
|
|
19
19
|
for (const templateName of fromTemplates) {
|
|
20
20
|
if (!(templateName in config.clientTemplateDefs)) {
|
|
21
|
+
// js family removed in v4
|
|
22
|
+
if (['js', 'jsBase', 'schemaJs', 'openapiJs'].includes(templateName)) {
|
|
23
|
+
throw new Error(`The "${templateName}" template was removed in v4. Use "ts" instead.`);
|
|
24
|
+
}
|
|
21
25
|
throw new Error(`Unknown template name: ${templateName}`);
|
|
22
26
|
}
|
|
23
27
|
usedTemplateDefs[templateName] = config.clientTemplateDefs[templateName];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import type { ProjectInfo } from '../get-project-info/index.mjs';
|
|
3
2
|
import { type VovkStrictConfig } from 'vovk/internal';
|
|
3
|
+
import type { ProjectInfo } from '../get-project-info/index.mjs';
|
|
4
4
|
export declare function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, config, }: {
|
|
5
5
|
schemaOutAbsolutePath: string;
|
|
6
6
|
isNextInstalled: boolean;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { glob } from 'glob';
|
|
4
|
+
import { deepExtend, VovkSchemaIdEnum } from 'vovk/internal';
|
|
4
5
|
import { META_FILE_NAME, ROOT_SEGMENT_FILE_NAME } from '../dev/write-one-segment-schema-file.mjs';
|
|
5
6
|
import { getMetaSchema } from '../get-project-info/get-meta-schema.mjs';
|
|
6
|
-
import { deepExtend, VovkSchemaIdEnum } from 'vovk/internal';
|
|
7
7
|
export async function getProjectFullSchema({ schemaOutAbsolutePath, isNextInstalled, log, config, }) {
|
|
8
8
|
const result = {
|
|
9
9
|
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VovkSchema } from 'vovk';
|
|
2
|
-
import type { GenerateOptions } from '../types.mjs';
|
|
3
2
|
import type { ProjectInfo } from '../get-project-info/index.mjs';
|
|
3
|
+
import type { GenerateOptions } from '../types.mjs';
|
|
4
4
|
export declare class VovkGenerate {
|
|
5
5
|
#private;
|
|
6
6
|
constructor({ cliGenerateOptions, projectInfo, forceNothingWrittenLog, }: {
|
|
@@ -8,7 +8,7 @@ export declare class VovkGenerate {
|
|
|
8
8
|
projectInfo: ProjectInfo;
|
|
9
9
|
forceNothingWrittenLog?: boolean;
|
|
10
10
|
});
|
|
11
|
-
start(): void;
|
|
11
|
+
start(): Promise<void> | undefined;
|
|
12
12
|
generate(): Promise<void>;
|
|
13
13
|
getFullSchema(): Promise<VovkSchema>;
|
|
14
14
|
watch({ throttleDelay }: {
|