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.
Files changed (73) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/client-templates/schema-ts/schema.ts.ejs +1 -1
  3. package/client-templates/ts-base/index.ts.ejs +1 -1
  4. package/dist/bundle/index.mjs +64 -42
  5. package/dist/dev/diff-segment-schema.d.mts +0 -17
  6. package/dist/dev/diff-segment-schema.mjs +1 -17
  7. package/dist/dev/ensure-schema-files.mjs +28 -10
  8. package/dist/dev/index.mjs +19 -13
  9. package/dist/dev/log-diff-result.d.mts +1 -1
  10. package/dist/dev/log-diff-result.mjs +1 -1
  11. package/dist/dev/write-meta-json.mjs +2 -2
  12. package/dist/dev/write-one-segment-schema-file.mjs +7 -2
  13. package/dist/generate/ensure-client.mjs +1 -1
  14. package/dist/generate/generate.mjs +38 -25
  15. package/dist/generate/get-client-template-files.mjs +8 -4
  16. package/dist/generate/get-project-full-schema.d.mts +1 -1
  17. package/dist/generate/get-project-full-schema.mjs +2 -2
  18. package/dist/generate/index.d.mts +2 -2
  19. package/dist/generate/index.mjs +46 -34
  20. package/dist/generate/write-one-client-file.d.mts +3 -3
  21. package/dist/generate/write-one-client-file.mjs +8 -6
  22. package/dist/get-project-info/get-config/get-relative-src-root.mjs +1 -1
  23. package/dist/get-project-info/get-config/get-template-defs.d.mts +20 -23
  24. package/dist/get-project-info/get-config/get-template-defs.mjs +21 -47
  25. package/dist/get-project-info/get-config/get-user-config.mjs +1 -1
  26. package/dist/get-project-info/get-config/index.d.mts +1 -1
  27. package/dist/get-project-info/get-config/index.mjs +15 -10
  28. package/dist/get-project-info/get-meta-schema.d.mts +1 -1
  29. package/dist/get-project-info/get-meta-schema.mjs +1 -1
  30. package/dist/get-project-info/index.mjs +2 -2
  31. package/dist/index.mjs +25 -16
  32. package/dist/init/check-tsconfig-for-experimental-decorators.mjs +1 -1
  33. package/dist/init/create-config.d.mts +1 -1
  34. package/dist/init/create-config.mjs +5 -5
  35. package/dist/init/index.d.mts +1 -1
  36. package/dist/init/index.mjs +49 -17
  37. package/dist/init/install-dependencies.d.mts +2 -1
  38. package/dist/init/install-dependencies.mjs +10 -2
  39. package/dist/init/update-dependencies-without-installing.d.mts +1 -1
  40. package/dist/init/update-dependencies-without-installing.mjs +0 -1
  41. package/dist/init/update-gitignore.d.mts +2 -0
  42. package/dist/init/update-gitignore.mjs +20 -0
  43. package/dist/init/update-typescript-config.mjs +1 -1
  44. package/dist/new/index.d.mts +1 -1
  45. package/dist/new/new-module.mjs +11 -16
  46. package/dist/new/new-segment.mjs +9 -4
  47. package/dist/new/render.mjs +1 -1
  48. package/dist/types.d.mts +1 -1
  49. package/dist/utils/compile-json-schema-to-typescript-type.d.mts +1 -1
  50. package/dist/utils/compile-ts.mjs +1 -1
  51. package/dist/utils/generate-fn-name.d.mts +0 -9
  52. package/dist/utils/generate-fn-name.mjs +2 -9
  53. package/dist/utils/generated-banner.d.mts +1 -0
  54. package/dist/utils/generated-banner.mjs +3 -0
  55. package/dist/utils/get-available-port.d.mts +0 -8
  56. package/dist/utils/get-available-port.mjs +2 -14
  57. package/dist/utils/get-file-system-entry-type.d.mts +5 -4
  58. package/dist/utils/get-file-system-entry-type.mjs +5 -5
  59. package/dist/utils/normalize-openapi-mixin.d.mts +1 -1
  60. package/dist/utils/normalize-openapi-mixin.mjs +18 -3
  61. package/dist/utils/prettify.d.mts +4 -0
  62. package/dist/utils/prettify.mjs +39 -3
  63. package/dist/utils/remove-unlisted-directories.d.mts +1 -9
  64. package/dist/utils/remove-unlisted-directories.mjs +60 -21
  65. package/dist/utils/resolve-absolute-module-path.mjs +1 -2
  66. package/dist/utils/update-config-property.mjs +1 -1
  67. package/package.json +23 -30
  68. package/client-templates/js-base/index.d.ts.ejs +0 -21
  69. package/client-templates/js-base/index.js.ejs +0 -18
  70. package/client-templates/openapi-js/openapi.d.ts.ejs +0 -4
  71. package/client-templates/openapi-js/openapi.js.ejs +0 -4
  72. package/client-templates/schema-js/schema.d.ts.ejs +0 -10
  73. package/client-templates/schema-js/schema.js.ejs +0 -29
@@ -1,9 +1,9 @@
1
1
  import path from 'node:path';
2
2
  import * as chokidar from 'chokidar';
3
- import { getProjectFullSchema } from './get-project-full-schema.mjs';
4
- import { generate } from './generate.mjs';
5
- import { locateSegments } from '../utils/locate-segments.mjs';
6
3
  import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
4
+ import { locateSegments } from '../utils/locate-segments.mjs';
5
+ import { generate } from './generate.mjs';
6
+ import { getProjectFullSchema } from './get-project-full-schema.mjs';
7
7
  const THROTTLE_DELAY = 5000;
8
8
  export class VovkGenerate {
9
9
  #cliGenerateOptions;
@@ -19,10 +19,10 @@ export class VovkGenerate {
19
19
  if (watch) {
20
20
  const throttleDelay = typeof watch === 'boolean' ? THROTTLE_DELAY : parseFloat(watch) * 1e3 || THROTTLE_DELAY;
21
21
  this.watch({ throttleDelay });
22
+ return;
22
23
  }
23
- else {
24
- this.generate();
25
- }
24
+ // return the promise so one-shot generate errors reach the CLI error handler
25
+ return this.generate();
26
26
  }
27
27
  async generate() {
28
28
  const fullSchema = await this.getFullSchema();
@@ -63,6 +63,17 @@ export class VovkGenerate {
63
63
  watchSchema({ schemaPath, throttleDelay }) {
64
64
  const { log } = this.#projectInfo;
65
65
  let lastGenerationTime = 0;
66
+ let pendingTimer = null;
67
+ const generateCode = async () => {
68
+ try {
69
+ lastGenerationTime = Date.now();
70
+ await this.generate();
71
+ log.debug(`Regenerated from schema changes`);
72
+ }
73
+ catch (error) {
74
+ log.error(`Failed to regenerate from schema: ${error instanceof Error ? error.message : String(error)}`);
75
+ }
76
+ };
66
77
  chokidar
67
78
  .watch(schemaPath, {
68
79
  persistent: true,
@@ -72,20 +83,15 @@ export class VovkGenerate {
72
83
  if (event === 'change' || event === 'add' || event === 'ready' || event === 'unlink') {
73
84
  log.debug(`Schema file ${event}: ${path}`);
74
85
  const now = Date.now();
75
- const shouldGenerateImmediately = now - lastGenerationTime > throttleDelay;
76
- const generateCode = async () => {
77
- try {
78
- lastGenerationTime = Date.now();
79
- await this.generate();
80
- log.debug(`Regenerated from schema changes`);
81
- }
82
- catch (error) {
83
- log.error(`Failed to regenerate from schema: ${error instanceof Error ? error.message : String(error)}`);
84
- }
85
- };
86
- // Generate immediately if it's been a while since the last generation
87
- if (shouldGenerateImmediately) {
88
- generateCode();
86
+ // generate immediately outside the throttle window, otherwise defer to the end of it
87
+ if (now - lastGenerationTime > throttleDelay) {
88
+ void generateCode();
89
+ }
90
+ else if (!pendingTimer) {
91
+ pendingTimer = setTimeout(() => {
92
+ pendingTimer = null;
93
+ void generateCode();
94
+ }, throttleDelay - (now - lastGenerationTime));
89
95
  }
90
96
  }
91
97
  });
@@ -111,6 +117,17 @@ export class VovkGenerate {
111
117
  watchOpenApiSpecLocal({ openApiSpecPaths, throttleDelay }) {
112
118
  const { log, cwd } = this.#projectInfo;
113
119
  let lastGenerationTime = 0;
120
+ let pendingTimer = null;
121
+ const generateCode = async () => {
122
+ try {
123
+ lastGenerationTime = Date.now();
124
+ await this.generate();
125
+ log.debug(`Regenerated from OpenAPI spec changes`);
126
+ }
127
+ catch (error) {
128
+ log.error(`Failed to regenerate from OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
129
+ }
130
+ };
114
131
  chokidar
115
132
  .watch(openApiSpecPaths, {
116
133
  cwd,
@@ -121,20 +138,15 @@ export class VovkGenerate {
121
138
  if (event === 'change' || event === 'add' || event === 'ready' || event === 'unlink') {
122
139
  log.debug(`OpenAPI spec file changed: ${path}`);
123
140
  const now = Date.now();
124
- const shouldGenerateImmediately = now - lastGenerationTime > throttleDelay;
125
- const generateCode = async () => {
126
- try {
127
- lastGenerationTime = Date.now();
128
- await this.generate();
129
- log.debug(`Regenerated from OpenAPI spec changes`);
130
- }
131
- catch (error) {
132
- log.error(`Failed to regenerate from OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`);
133
- }
134
- };
135
- // Generate immediately if it's been a while since the last generation
136
- if (shouldGenerateImmediately) {
137
- generateCode();
141
+ // generate immediately outside the throttle window, otherwise defer to the end of it
142
+ if (now - lastGenerationTime > throttleDelay) {
143
+ void generateCode();
144
+ }
145
+ else if (!pendingTimer) {
146
+ pendingTimer = setTimeout(() => {
147
+ pendingTimer = null;
148
+ void generateCode();
149
+ }, throttleDelay - (now - lastGenerationTime));
138
150
  }
139
151
  }
140
152
  });
@@ -1,10 +1,10 @@
1
+ import type { OpenAPIObject } from 'openapi3-ts/oas31';
2
+ import type { PackageJson } from 'type-fest';
1
3
  import type { VovkSchema } from 'vovk';
2
4
  import { type VovkReadmeConfig, type VovkSamplesConfig, type VovkStrictConfig } from 'vovk/internal';
3
- import type { PackageJson } from 'type-fest';
4
- import type { OpenAPIObject } from 'openapi3-ts/oas31';
5
5
  import type { ProjectInfo } from '../get-project-info/index.mjs';
6
- import type { ClientTemplateFile } from './get-client-template-files.mjs';
7
6
  import type { Segment } from '../utils/locate-segments.mjs';
7
+ import type { ClientTemplateFile } from './get-client-template-files.mjs';
8
8
  export declare function normalizeOutTemplatePath(out: string, packageJson: PackageJson): string;
9
9
  export declare function writeOneClientFile({ cwd, projectInfo, clientTemplateFile, fullSchema, prettifyClient, segmentName, templateContent, matterResult: { data, content }, openAPIObject, package: packageJson, readme, samples, reExports, isEnsuringClient, outCwdRelativeDir, templateDef, locatedSegments, isNodeNextResolution, hasMixins, isVovkProject, vovkCliPackage, isBundle, origin, configKey, cliSchemaPath, projectConfig, }: {
10
10
  cwd: string;
@@ -1,15 +1,16 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import TOML from '@iarna/toml';
3
4
  import ejs from 'ejs';
4
5
  import _ from 'lodash';
5
- import * as YAML from 'yaml';
6
- import TOML from '@iarna/toml';
7
6
  import { createCodeSamples, reattachMixinDefs, VovkSchemaIdEnum, } from 'vovk/internal';
7
+ import * as YAML from 'yaml';
8
+ import { ROOT_SEGMENT_FILE_NAME } from '../dev/write-one-segment-schema-file.mjs';
9
+ import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
8
10
  import { compileJSONSchemaToTypeScriptType } from '../utils/compile-json-schema-to-typescript-type.mjs';
11
+ import { GENERATED_BANNER_PREFIX } from '../utils/generated-banner.mjs';
12
+ import { prettify, warnIfPrettierMissing } from '../utils/prettify.mjs';
9
13
  import { getTemplateClientImports } from './get-template-client-imports.mjs';
10
- import { chalkHighlightThing } from '../utils/chalk-highlight-thing.mjs';
11
- import { ROOT_SEGMENT_FILE_NAME } from '../dev/write-one-segment-schema-file.mjs';
12
- import { prettify } from '../utils/prettify.mjs';
13
14
  export function normalizeOutTemplatePath(out, packageJson) {
14
15
  return out.replace('[package_name]', packageJson.name?.replace(/-/g, '_') ?? 'my_package_name');
15
16
  }
@@ -27,7 +28,7 @@ templateContent, matterResult: { data, content }, openAPIObject, package: packag
27
28
  // Feel free to report an issue at https://github.com/finom/vovk/issues`;
28
29
  placeholder = outPath.endsWith('.py') ? placeholder.replace(/\/\//g, '#') : placeholder;
29
30
  const getFirstLineBanner = (type = 'c') => {
30
- const text = `Generated by vovk-cli v${vovkCliPackage.version} at ${new Date().toISOString()}`;
31
+ const text = `${GENERATED_BANNER_PREFIX} v${vovkCliPackage.version} at ${new Date().toISOString()}`;
31
32
  switch (type) {
32
33
  case 'html':
33
34
  return `<!-- ${text} -->`;
@@ -130,6 +131,7 @@ templateContent, matterResult: { data, content }, openAPIObject, package: packag
130
131
  : templateContent;
131
132
  // Optionally prettify
132
133
  if (prettifyClient) {
134
+ await warnIfPrettierMissing(log);
133
135
  rendered = await prettify(rendered, outPath);
134
136
  }
135
137
  if (isEnsuringClient) {
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { getFileSystemEntryType, FileSystemEntryType } from '../../utils/get-file-system-entry-type.mjs';
2
+ import { FileSystemEntryType, getFileSystemEntryType } from '../../utils/get-file-system-entry-type.mjs';
3
3
  export async function getRelativeSrcRoot({ cwd }) {
4
4
  // Next.js Docs: src/app or src/pages will be ignored if app or pages are present in the root directory.
5
5
  if ((await getFileSystemEntryType(path.join(cwd, 'app'))) === FileSystemEntryType.DIRECTORY) {
@@ -1,25 +1,22 @@
1
1
  import type { VovkStrictConfig } from 'vovk/internal';
2
- export declare enum BuiltInTemplateName {
3
- tsBase = "tsBase",
4
- jsBase = "jsBase",
5
- ts = "ts",
6
- js = "js",
7
- schemaTs = "schemaTs",
8
- schemaJs = "schemaJs",
9
- schemaJson = "schemaJson",
10
- openapiTs = "openapiTs",
11
- openapiJs = "openapiJs",
12
- openapiJson = "openapiJson",
13
- readme = "readme",
14
- packageJson = "packageJson",
15
- mixins = "mixins",
16
- rsSrc = "rsSrc",
17
- rsPkg = "rsPkg",
18
- rsReadme = "rsReadme",
19
- rs = "rs",
20
- pySrc = "pySrc",
21
- pyPkg = "pyPkg",
22
- pyReadme = "pyReadme",
23
- py = "py"
24
- }
2
+ export declare const BuiltInTemplateName: {
3
+ readonly tsBase: "tsBase";
4
+ readonly ts: "ts";
5
+ readonly schemaTs: "schemaTs";
6
+ readonly schemaJson: "schemaJson";
7
+ readonly openapiTs: "openapiTs";
8
+ readonly openapiJson: "openapiJson";
9
+ readonly readme: "readme";
10
+ readonly packageJson: "packageJson";
11
+ readonly mixins: "mixins";
12
+ readonly rsSrc: "rsSrc";
13
+ readonly rsPkg: "rsPkg";
14
+ readonly rsReadme: "rsReadme";
15
+ readonly rs: "rs";
16
+ readonly pySrc: "pySrc";
17
+ readonly pyPkg: "pyPkg";
18
+ readonly pyReadme: "pyReadme";
19
+ readonly py: "py";
20
+ };
21
+ export type BuiltInTemplateName = (typeof BuiltInTemplateName)[keyof typeof BuiltInTemplateName];
25
22
  export declare function getTemplateDefs(userTemplateDefs?: VovkStrictConfig['clientTemplateDefs']): VovkStrictConfig['clientTemplateDefs'];
@@ -1,32 +1,28 @@
1
- export var BuiltInTemplateName;
2
- (function (BuiltInTemplateName) {
3
- // ts/js
4
- BuiltInTemplateName["tsBase"] = "tsBase";
5
- BuiltInTemplateName["jsBase"] = "jsBase";
6
- BuiltInTemplateName["ts"] = "ts";
7
- BuiltInTemplateName["js"] = "js";
1
+ // const object instead of enum, node 26 runs ts with erasable syntax only
2
+ export const BuiltInTemplateName = {
3
+ // ts
4
+ tsBase: 'tsBase',
5
+ ts: 'ts',
8
6
  // schema
9
- BuiltInTemplateName["schemaTs"] = "schemaTs";
10
- BuiltInTemplateName["schemaJs"] = "schemaJs";
11
- BuiltInTemplateName["schemaJson"] = "schemaJson";
7
+ schemaTs: 'schemaTs',
8
+ schemaJson: 'schemaJson',
12
9
  // openapi
13
- BuiltInTemplateName["openapiTs"] = "openapiTs";
14
- BuiltInTemplateName["openapiJs"] = "openapiJs";
15
- BuiltInTemplateName["openapiJson"] = "openapiJson";
10
+ openapiTs: 'openapiTs',
11
+ openapiJson: 'openapiJson',
16
12
  // misc
17
- BuiltInTemplateName["readme"] = "readme";
18
- BuiltInTemplateName["packageJson"] = "packageJson";
19
- BuiltInTemplateName["mixins"] = "mixins";
13
+ readme: 'readme',
14
+ packageJson: 'packageJson',
15
+ mixins: 'mixins',
20
16
  // other languages (packages installed separately)
21
- BuiltInTemplateName["rsSrc"] = "rsSrc";
22
- BuiltInTemplateName["rsPkg"] = "rsPkg";
23
- BuiltInTemplateName["rsReadme"] = "rsReadme";
24
- BuiltInTemplateName["rs"] = "rs";
25
- BuiltInTemplateName["pySrc"] = "pySrc";
26
- BuiltInTemplateName["pyPkg"] = "pyPkg";
27
- BuiltInTemplateName["pyReadme"] = "pyReadme";
28
- BuiltInTemplateName["py"] = "py";
29
- })(BuiltInTemplateName || (BuiltInTemplateName = {}));
17
+ rsSrc: 'rsSrc',
18
+ rsPkg: 'rsPkg',
19
+ rsReadme: 'rsReadme',
20
+ rs: 'rs',
21
+ pySrc: 'pySrc',
22
+ pyPkg: 'pyPkg',
23
+ pyReadme: 'pyReadme',
24
+ py: 'py',
25
+ };
30
26
  export function getTemplateDefs(userTemplateDefs = {}) {
31
27
  const defs = {};
32
28
  const builtInDefs = {
@@ -36,12 +32,6 @@ export function getTemplateDefs(userTemplateDefs = {}) {
36
32
  [BuiltInTemplateName.openapiJson]: './',
37
33
  },
38
34
  },
39
- [BuiltInTemplateName.openapiJs]: {
40
- templatePath: 'vovk-cli/client-templates/openapi-js/',
41
- requires: {
42
- [BuiltInTemplateName.openapiJson]: './',
43
- },
44
- },
45
35
  [BuiltInTemplateName.openapiJson]: {
46
36
  templatePath: 'vovk-cli/client-templates/openapi-json/',
47
37
  },
@@ -52,31 +42,15 @@ export function getTemplateDefs(userTemplateDefs = {}) {
52
42
  [BuiltInTemplateName.mixins]: './', // used conditionally if OpenAPI mixins are used
53
43
  },
54
44
  },
55
- [BuiltInTemplateName.jsBase]: {
56
- templatePath: 'vovk-cli/client-templates/js-base/',
57
- requires: {
58
- [BuiltInTemplateName.schemaJs]: './',
59
- [BuiltInTemplateName.mixins]: './', // used conditionally if OpenAPI mixins are used
60
- },
61
- },
62
45
  [BuiltInTemplateName.ts]: {
63
46
  requires: {
64
47
  [BuiltInTemplateName.tsBase]: './',
65
48
  [BuiltInTemplateName.openapiTs]: './',
66
49
  },
67
50
  },
68
- [BuiltInTemplateName.js]: {
69
- requires: {
70
- [BuiltInTemplateName.jsBase]: './',
71
- [BuiltInTemplateName.openapiJs]: './',
72
- },
73
- },
74
51
  [BuiltInTemplateName.schemaTs]: {
75
52
  templatePath: 'vovk-cli/client-templates/schema-ts/',
76
53
  },
77
- [BuiltInTemplateName.schemaJs]: {
78
- templatePath: 'vovk-cli/client-templates/schema-js/',
79
- },
80
54
  [BuiltInTemplateName.schemaJson]: {
81
55
  templatePath: 'vovk-cli/client-templates/schema-json/',
82
56
  },
@@ -1,7 +1,7 @@
1
- import { pathToFileURL } from 'node:url';
2
1
  import { readFile } from 'node:fs/promises';
3
2
  import { createRequire } from 'node:module';
4
3
  import { dirname, extname } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
5
  import vm from 'node:vm';
6
6
  import { getConfigAbsolutePaths } from './get-config-absolute-paths.mjs';
7
7
  const isVMModulesEnabled = typeof vm.SourceTextModule !== 'undefined';
@@ -1,6 +1,6 @@
1
+ import type { LogLevelNames } from 'loglevel';
1
2
  import type { VovkConfig } from 'vovk';
2
3
  import { type VovkStrictConfig } from 'vovk/internal';
3
- import type { LogLevelNames } from 'loglevel';
4
4
  import { getLogger } from '../../utils/get-logger.mjs';
5
5
  export declare function getConfig({ configPath, cwd, logLevel, }: {
6
6
  configPath?: string;
@@ -1,16 +1,20 @@
1
1
  import path from 'node:path';
2
2
  import { VovkSchemaIdEnum } from 'vovk/internal';
3
+ import { chalkHighlightThing } from '../../utils/chalk-highlight-thing.mjs';
3
4
  import { getLogger } from '../../utils/get-logger.mjs';
4
- import { getUserConfig } from './get-user-config.mjs';
5
- import { getRelativeSrcRoot } from './get-relative-src-root.mjs';
6
- import { getTemplateDefs, BuiltInTemplateName } from './get-template-defs.mjs';
7
5
  import { normalizeOpenAPIMixin } from '../../utils/normalize-openapi-mixin.mjs';
8
- import { chalkHighlightThing } from '../../utils/chalk-highlight-thing.mjs';
6
+ import { getRelativeSrcRoot } from './get-relative-src-root.mjs';
7
+ import { BuiltInTemplateName, getTemplateDefs } from './get-template-defs.mjs';
8
+ import { getUserConfig } from './get-user-config.mjs';
9
9
  export async function getConfig({ configPath, cwd, logLevel, }) {
10
10
  const { configAbsolutePaths, error, userConfig } = await getUserConfig({
11
11
  configPath,
12
12
  cwd,
13
13
  });
14
+ // a config that exists but fails to load must not be silently replaced by defaults
15
+ if (!userConfig && configAbsolutePaths.length) {
16
+ throw new Error(`Failed to load config file at ${chalkHighlightThing(configAbsolutePaths[0])}. ${error?.message ?? 'Unknown error'}`);
17
+ }
14
18
  const conf = userConfig ?? {};
15
19
  logLevel = logLevel ?? conf.logLevel ?? 'info';
16
20
  const log = getLogger(logLevel);
@@ -24,9 +28,9 @@ export async function getConfig({ configPath, cwd, logLevel, }) {
24
28
  composedClient: {
25
29
  ...conf.composedClient,
26
30
  enabled: conf.composedClient?.enabled ?? true,
27
- fromTemplates: conf.composedClient?.fromTemplates ?? [BuiltInTemplateName.js],
28
- outDir: conf.composedClient?.outDir ?? './node_modules/.vovk-client',
29
- prettifyClient: conf.composedClient?.prettifyClient ?? false,
31
+ fromTemplates: conf.composedClient?.fromTemplates ?? [BuiltInTemplateName.ts],
32
+ outDir: conf.composedClient?.outDir ?? path.join(srcRoot ?? '.', 'client'),
33
+ prettifyClient: conf.composedClient?.prettifyClient ?? true,
30
34
  },
31
35
  segmentedClient: {
32
36
  ...conf.segmentedClient,
@@ -45,9 +49,10 @@ export async function getConfig({ configPath, cwd, logLevel, }) {
45
49
  },
46
50
  outputConfig: {},
47
51
  build: conf.bundle?.build ??
48
- (() => {
52
+ // isMissingBuild lets the bundle command fail fast before doing any work
53
+ Object.assign(() => {
49
54
  throw new Error('No bundle.build function specified');
50
- }),
55
+ }, { isMissingBuild: true }),
51
56
  ...conf.bundle,
52
57
  },
53
58
  modulesDir: conf.modulesDir ?? path.join(srcRoot ?? '.', 'modules'),
@@ -86,7 +91,7 @@ export async function getConfig({ configPath, cwd, logLevel, }) {
86
91
  config.exposeConfigKeys = conf.exposeConfigKeys;
87
92
  } // else it's false and exposeConfigKeys already is []
88
93
  if (!userConfig) {
89
- log.warn(`Unable to load config at ${chalkHighlightThing(`${cwd}/`)}. Using default values. ${error ?? ''}`);
94
+ log.warn(`No config file found at ${chalkHighlightThing(`${cwd}/`)}. Using default values.`);
90
95
  }
91
96
  return { config, srcRoot, configAbsolutePaths, userConfig, log };
92
97
  }
@@ -1,4 +1,4 @@
1
- import { type VovkStrictConfig, type VovkMetaSchema } from 'vovk/internal';
1
+ import { type VovkMetaSchema, type VovkStrictConfig } from 'vovk/internal';
2
2
  export declare function getMetaSchema({ config }: {
3
3
  config: VovkStrictConfig;
4
4
  }): VovkMetaSchema;
@@ -1,5 +1,5 @@
1
- import { VovkSchemaIdEnum } from 'vovk/internal';
2
1
  import pick from 'lodash/pick.js';
2
+ import { VovkSchemaIdEnum } from 'vovk/internal';
3
3
  export function getMetaSchema({ config }) {
4
4
  return {
5
5
  $schema: VovkSchemaIdEnum.META,
@@ -1,7 +1,7 @@
1
+ import { readFile } from 'node:fs/promises';
1
2
  import path from 'node:path';
2
- import { getConfig } from './get-config/index.mjs';
3
3
  import { getPackageJson } from '../utils/get-package-json.mjs';
4
- import { readFile } from 'node:fs/promises';
4
+ import { getConfig } from './get-config/index.mjs';
5
5
  export async function getProjectInfo({ port: givenPort, cwd = process.cwd(), configPath, srcRootRequired = true, logLevel, } = {
6
6
  logLevel: 'info',
7
7
  }) {
package/dist/index.mjs CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env -S node --experimental-vm-modules --disable-warning=ExperimentalWarning
2
- import path from 'node:path';
3
2
  import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
4
  import 'dotenv/config';
5
5
  import { Command } from 'commander';
6
6
  import concurrently from 'concurrently';
7
- import { getAvailablePort } from './utils/get-available-port.mjs';
8
- import { getProjectInfo } from './get-project-info/index.mjs';
9
- import { VovkGenerate } from './generate/index.mjs';
10
7
  import { bundle } from './bundle/index.mjs';
11
8
  import { VovkDev } from './dev/index.mjs';
12
- import { newComponents } from './new/index.mjs';
13
9
  import { getProjectFullSchema } from './generate/get-project-full-schema.mjs';
10
+ import { VovkGenerate } from './generate/index.mjs';
11
+ import { getProjectInfo } from './get-project-info/index.mjs';
12
+ import { newComponents } from './new/index.mjs';
13
+ import { getAvailablePort } from './utils/get-available-port.mjs';
14
14
  import { Init } from './init/index.mjs';
15
15
  const program = new Command();
16
16
  const vovkCliPackage = JSON.parse(readFileSync(path.join(import.meta.dirname, '../package.json'), 'utf-8'));
@@ -31,13 +31,11 @@ program
31
31
  const PORT = !nextDev
32
32
  ? process.env.PORT
33
33
  : process.env.PORT ||
34
- (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) =>
35
- // eslint-disable-next-line no-console
36
- console.warn(`🐺 Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
37
- throw new Error(`🐺 ❌ Failed to find an available port after ${portAttempts} attempts`);
34
+ (await getAvailablePort(3000, portAttempts, 0, (failedPort, tryingPort) => console.warn(`🐺 Port ${failedPort} is in use, trying ${tryingPort} instead.`)).catch(() => {
35
+ throw new Error(`Failed to find an available port after ${portAttempts} attempts`);
38
36
  }));
39
37
  if (!PORT) {
40
- throw new Error('🐺 ❌ PORT env variable is required');
38
+ throw new Error('PORT env variable is required');
41
39
  }
42
40
  if (nextDev) {
43
41
  const { result } = concurrently([
@@ -54,7 +52,7 @@ program
54
52
  __VOVK_START_WATCHER_IN_STANDALONE_MODE__: 'true',
55
53
  // TODO: Pass these as flags
56
54
  __VOVK_SCHEMA_OUT_FLAG__: schemaOut ?? '',
57
- __VOVK_DEV_HTTPS_FLAG__: devHttps ? 'true' : 'false',
55
+ __VOVK_DEV_HTTPS_FLAG__: devHttps ? 'true' : '',
58
56
  __VOVK_EXIT__: exit ? 'true' : 'false',
59
57
  __VOVK_LOG_LEVEL__: logLevel ?? undefined,
60
58
  },
@@ -67,12 +65,16 @@ program
67
65
  try {
68
66
  await result;
69
67
  }
70
- finally {
71
- // do nothing, all processes are killed
68
+ catch (closeEvents) {
69
+ // concurrently rejects with child close events on shutdown; children already logged the reason
70
+ const hasFailure = Array.isArray(closeEvents) &&
71
+ closeEvents.some((event) => typeof event?.exitCode === 'number' && event.exitCode !== 0);
72
+ if (hasFailure)
73
+ process.exit(1);
72
74
  }
73
75
  }
74
76
  else {
75
- void new VovkDev({ schemaOut, devHttps, logLevel }).start({ exit });
77
+ await new VovkDev({ schemaOut, devHttps, logLevel }).start({ exit });
76
78
  }
77
79
  });
78
80
  program
@@ -120,7 +122,8 @@ program
120
122
  .option('--out, --out-dir <path>', 'path to output directory for bundle')
121
123
  .option('--include, --include-segments <segments...>', 'include segments')
122
124
  .option('--exclude, --exclude-segments <segments...>', 'exclude segments')
123
- .option('--prebundle-out-dir, --prebundle-out <path>', 'path to output directory for prebundle')
125
+ // the last long flag names the option, it has to stay prebundleOutDir
126
+ .option('--prebundle-out, --prebundle-out-dir <path>', 'path to output directory for prebundle')
124
127
  .option('--keep-prebundle-dir', 'do not delete prebundle directory after bundling')
125
128
  .option('--schema, --schema-path <path>', 'path to schema folder (default: .vovk-schema)')
126
129
  .option('--config, --config-path <config>', 'path to config file')
@@ -189,7 +192,13 @@ program
189
192
  .command('help')
190
193
  .description('Show help message')
191
194
  .action(() => program.help());
192
- program.parse(process.argv);
195
+ program.parseAsync(process.argv).catch((error) => {
196
+ const logLevelFlagIndex = process.argv.indexOf('--log-level');
197
+ const isDebug = ['debug', 'trace'].includes(process.argv[logLevelFlagIndex + 1] ?? '');
198
+ const err = error instanceof Error ? error : new Error(String(error));
199
+ console.error(`🐺 ❌ ${isDebug ? (err.stack ?? err.message) : err.message}`);
200
+ process.exit(1);
201
+ });
193
202
  if (!process.argv.slice(2).length) {
194
203
  program.outputHelp();
195
204
  }
@@ -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 * as jsonc from 'jsonc-parser';
4
4
  export async function checkTSConfigForExperimentalDecorators(root) {
5
5
  const tsconfigPath = path.resolve(root, 'tsconfig.json');
@@ -1,6 +1,6 @@
1
1
  import type { VovkStrictConfig } from 'vovk/internal';
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 declare const BUNDLE_BUILD_TSDOWN: ({ entry, outDir }: Parameters<VovkStrictConfig["bundle"]["build"]>[0]) => Promise<void>;
5
5
  export declare function createConfig({ root, options: { validationLibrary, bundle, lang, dryRun }, }: {
6
6
  root: string;
@@ -1,7 +1,7 @@
1
- import path from 'node:path';
2
1
  import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { FileSystemEntryType, getFileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
3
4
  import { prettify } from '../utils/prettify.mjs';
4
- import { getFileSystemEntryType, FileSystemEntryType } from '../utils/get-file-system-entry-type.mjs';
5
5
  import { updateConfigProperty } from '../utils/update-config-property.mjs';
6
6
  export const BUNDLE_BUILD_TSDOWN = async ({ entry, outDir }) => {
7
7
  const { build } = await import('tsdown');
@@ -16,14 +16,14 @@ export const BUNDLE_BUILD_TSDOWN = async ({ entry, outDir }) => {
16
16
  platform: 'neutral',
17
17
  outExtensions: () => ({ js: '.js', dts: '.d.ts' }),
18
18
  outputOptions: {
19
- inlineDynamicImports: true,
19
+ codeSplitting: false,
20
20
  },
21
21
  inputOptions: {
22
22
  resolve: {
23
23
  mainFields: ['module', 'main'],
24
24
  },
25
25
  },
26
- noExternal: ['!next/**'],
26
+ deps: { alwaysBundle: ['!next/**'] },
27
27
  });
28
28
  };
29
29
  export async function createConfig({ root, options: { validationLibrary, bundle, lang, dryRun }, }) {
@@ -55,7 +55,7 @@ export async function createConfig({ root, options: { validationLibrary, bundle,
55
55
  config.outputConfig.imports.validateOnClient = 'vovk-ajv';
56
56
  if (lang?.length) {
57
57
  config.composedClient ??= {};
58
- config.composedClient.fromTemplates = ['js', ...lang];
58
+ config.composedClient.fromTemplates = ['ts', ...lang];
59
59
  }
60
60
  config.moduleTemplates = moduleTemplates;
61
61
  if (bundle) {
@@ -1,5 +1,5 @@
1
- import { getLogger } from '../utils/get-logger.mjs';
2
1
  import type { InitOptions } from '../types.mjs';
2
+ import { getLogger } from '../utils/get-logger.mjs';
3
3
  export declare class Init {
4
4
  #private;
5
5
  root: string;