vovk-cli 0.0.1-draft.20 → 0.0.1-draft.201

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 (122) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -1
  3. package/client-templates/cjs/index.cjs.ejs +14 -0
  4. package/client-templates/cjs/index.d.cts.ejs +15 -0
  5. package/client-templates/mjs/index.d.mts.ejs +15 -0
  6. package/client-templates/mjs/index.mjs.ejs +20 -0
  7. package/client-templates/packageJson/package.json.ejs +1 -0
  8. package/client-templates/readme/README.md.ejs +35 -0
  9. package/client-templates/schemaCjs/schema.cjs.ejs +15 -0
  10. package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
  11. package/client-templates/schemaJson/schema.json.ejs +1 -0
  12. package/client-templates/schemaTs/schema.ts.ejs +17 -0
  13. package/client-templates/ts/index.ts.ejs +22 -0
  14. package/dist/bundle/index.d.mts +8 -0
  15. package/dist/bundle/index.mjs +82 -0
  16. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  17. package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +4 -12
  18. package/dist/dev/ensureSchemaFiles.d.mts +3 -0
  19. package/dist/dev/ensureSchemaFiles.mjs +14 -31
  20. package/dist/dev/index.d.mts +5 -2
  21. package/dist/dev/index.mjs +168 -75
  22. package/dist/dev/logDiffResult.d.mts +1 -1
  23. package/dist/dev/logDiffResult.mjs +6 -43
  24. package/dist/dev/writeMetaJson.d.mts +2 -0
  25. package/dist/dev/writeMetaJson.mjs +17 -0
  26. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  27. package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
  28. package/dist/generate/ensureClient.d.mts +3 -0
  29. package/dist/generate/ensureClient.mjs +31 -0
  30. package/dist/generate/generate.d.mts +12 -0
  31. package/dist/generate/generate.mjs +214 -0
  32. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  33. package/dist/generate/getClientTemplateFiles.mjs +77 -0
  34. package/dist/generate/getProjectFullSchema.d.mts +3 -0
  35. package/dist/generate/getProjectFullSchema.mjs +64 -0
  36. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  37. package/dist/generate/getTemplateClientImports.mjs +38 -0
  38. package/dist/generate/index.d.mts +40 -0
  39. package/dist/generate/index.mjs +216 -0
  40. package/dist/generate/mergePackages.d.mts +9 -0
  41. package/dist/generate/mergePackages.mjs +78 -0
  42. package/dist/generate/writeOneClientFile.d.mts +31 -0
  43. package/dist/generate/writeOneClientFile.mjs +95 -0
  44. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  45. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
  46. package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
  47. package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
  48. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +14 -0
  49. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +85 -0
  50. package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
  51. package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +6 -4
  52. package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -5
  53. package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -1
  54. package/dist/getProjectInfo/getConfig/index.d.mts +17 -0
  55. package/dist/getProjectInfo/getConfig/index.mjs +83 -0
  56. package/dist/getProjectInfo/index.d.mts +6 -8
  57. package/dist/getProjectInfo/index.mjs +12 -21
  58. package/dist/index.d.mts +2 -2
  59. package/dist/index.mjs +88 -38
  60. package/dist/init/createConfig.d.mts +2 -2
  61. package/dist/init/createConfig.mjs +18 -12
  62. package/dist/init/getTemplateFilesFromPackage.mjs +7 -5
  63. package/dist/init/index.d.mts +2 -2
  64. package/dist/init/index.mjs +89 -53
  65. package/dist/init/installDependencies.mjs +4 -2
  66. package/dist/init/logUpdateDependenciesError.d.mts +3 -1
  67. package/dist/init/logUpdateDependenciesError.mjs +7 -1
  68. package/dist/init/updateDependenciesWithoutInstalling.mjs +41 -13
  69. package/dist/init/updateNPMScripts.d.mts +3 -1
  70. package/dist/init/updateNPMScripts.mjs +10 -7
  71. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  72. package/dist/init/updateTypeScriptConfig.mjs +11 -7
  73. package/dist/initProgram.d.mts +1 -1
  74. package/dist/initProgram.mjs +17 -16
  75. package/dist/locateSegments.d.mts +8 -1
  76. package/dist/locateSegments.mjs +12 -4
  77. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  78. package/dist/new/addClassToSegmentCode.mjs +3 -3
  79. package/dist/new/addCommonTerms.mjs +1 -0
  80. package/dist/new/index.d.mts +1 -1
  81. package/dist/new/index.mjs +3 -2
  82. package/dist/new/newModule.d.mts +2 -1
  83. package/dist/new/newModule.mjs +18 -16
  84. package/dist/new/newSegment.d.mts +2 -1
  85. package/dist/new/newSegment.mjs +17 -9
  86. package/dist/new/render.d.mts +7 -3
  87. package/dist/new/render.mjs +29 -8
  88. package/dist/types.d.mts +40 -41
  89. package/dist/utils/debounceWithArgs.d.mts +2 -2
  90. package/dist/utils/debounceWithArgs.mjs +24 -9
  91. package/dist/utils/formatLoggedSegmentName.d.mts +2 -1
  92. package/dist/utils/formatLoggedSegmentName.mjs +2 -2
  93. package/dist/utils/getAvailablePort.mjs +1 -1
  94. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  95. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  96. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  97. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  98. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  99. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  100. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  101. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  102. package/module-templates/Controller.ts.ejs +50 -0
  103. package/module-templates/Service.ts.ejs +28 -0
  104. package/package.json +37 -21
  105. package/dist/dev/diffSchema.d.mts +0 -43
  106. package/dist/dev/ensureClient.d.mts +0 -5
  107. package/dist/dev/ensureClient.mjs +0 -31
  108. package/dist/dev/isMetadataEmpty.d.mts +0 -2
  109. package/dist/dev/isMetadataEmpty.mjs +0 -4
  110. package/dist/dev/writeOneSchemaFile.d.mts +0 -11
  111. package/dist/generateClient.d.mts +0 -7
  112. package/dist/generateClient.mjs +0 -97
  113. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  114. package/dist/getProjectInfo/getConfig.mjs +0 -29
  115. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  116. package/dist/postinstall.d.mts +0 -1
  117. package/dist/postinstall.mjs +0 -24
  118. package/templates/controller.ejs +0 -51
  119. package/templates/service.ejs +0 -27
  120. package/templates/worker.ejs +0 -24
  121. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  122. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
@@ -1,13 +1,15 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs/promises';
3
+ import { getTsconfig } from 'get-tsconfig';
3
4
  import render from './render.mjs';
4
5
  import addClassToSegmentCode from './addClassToSegmentCode.mjs';
5
6
  import getProjectInfo from '../getProjectInfo/index.mjs';
6
- import locateSegments from '../locateSegments.mjs';
7
7
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
8
8
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
9
9
  import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
10
10
  import prettify from '../utils/prettify.mjs';
11
+ import resolveAbsoluteModulePath from '../utils/resolveAbsoluteModulePath.mjs';
12
+ import { locateSegments } from '../locateSegments.mjs';
11
13
  function splitByLast(str, delimiter = '/') {
12
14
  const index = str.lastIndexOf(delimiter);
13
15
  if (index === -1) {
@@ -18,19 +20,19 @@ function splitByLast(str, delimiter = '/') {
18
20
  const after = str.substring(index + delimiter.length);
19
21
  return [before, after];
20
22
  }
21
- export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, }) {
22
- const { config, log, apiDir, cwd } = await getProjectInfo();
23
- let templates = config.templates;
23
+ export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
24
+ const { config, log, cwd, apiDir } = await getProjectInfo();
25
+ const segments = await locateSegments({ dir: path.join(cwd, apiDir), config, log });
26
+ const isNodeNextResolution = ['node16', 'nodenext'].includes((await getTsconfig(cwd)?.config?.compilerOptions?.moduleResolution?.toLowerCase()) ?? '');
27
+ let templates = config.moduleTemplates;
24
28
  const [segmentName, moduleName] = splitByLast(moduleNameWithOptionalSegment);
25
- // replace c by controller, s by service, w by worker, everything else keeps the same
29
+ // replace c by controller, s by service, everything else keeps the same
26
30
  what = what.map((s) => {
27
31
  switch (s) {
28
32
  case 'c':
29
33
  return 'controller';
30
34
  case 's':
31
35
  return 'service';
32
- case 'w':
33
- return 'worker';
34
36
  default:
35
37
  return s;
36
38
  }
@@ -49,16 +51,13 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
49
51
  throw new Error(`Template for "${type}" not found`);
50
52
  }
51
53
  }
52
- const segments = await locateSegments(apiDir);
53
54
  const segment = segments.find((s) => s.segmentName === segmentName);
54
55
  if (!segment) {
55
56
  throw new Error(`Unable to create module. Segment "${segmentName}" not found. Run "vovk new segment ${segmentName}" to create it`);
56
57
  }
57
58
  for (const type of what) {
58
59
  const templatePath = templates[type];
59
- const templateAbsolutePath = templatePath.startsWith('/') || templatePath.startsWith('.')
60
- ? path.resolve(cwd, templatePath)
61
- : path.resolve(cwd, './node_modules', templatePath);
60
+ const templateAbsolutePath = resolveAbsoluteModulePath(templatePath, cwd);
62
61
  const templateCode = await fs.readFile(templateAbsolutePath, 'utf-8');
63
62
  const { dir: renderedDir, fileName, sourceName, compiledName, code, } = await render(templateCode, {
64
63
  cwd,
@@ -66,6 +65,9 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
66
65
  withService: what.includes('service'),
67
66
  segmentName,
68
67
  moduleName,
68
+ empty,
69
+ templateFileName: templateAbsolutePath,
70
+ isNodeNextResolution,
69
71
  });
70
72
  const dir = dirFlag || renderedDir;
71
73
  if (!dir) {
@@ -84,24 +86,24 @@ export default async function newModule({ what, moduleNameWithOptionalSegment, d
84
86
  else {
85
87
  await fs.mkdir(absoluteModuleDir, { recursive: true });
86
88
  await fs.writeFile(absoluteModulePath, prettiedCode);
87
- log.info(`Created ${chalkHighlightThing(fileName)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
89
+ log.info(`Created${empty ? ' empty' : ''} ${chalkHighlightThing(absoluteModulePath)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
88
90
  }
89
91
  }
90
- if (type === 'controller' || type === 'worker') {
92
+ if (type === 'controller') {
91
93
  if (!sourceName) {
92
94
  throw new Error(`The template for "${type}" does not provide a sourceName`);
93
95
  }
94
96
  if (!compiledName) {
95
- throw new Error('The template for "${type}" does not provide a compiledName');
97
+ throw new Error(`The template for "${type}" does not provide a compiledName`);
96
98
  }
97
99
  const { routeFilePath } = segment;
98
100
  const segmentSourceCode = await fs.readFile(routeFilePath, 'utf-8');
99
- const importPath = path.relative(path.dirname(routeFilePath), absoluteModulePath).replace(/\.(ts|tsx)$/, '');
101
+ let importPath = path.relative(path.dirname(routeFilePath) + '/', absoluteModulePath).replace(/\.(ts|tsx)$/, '');
102
+ importPath += isNodeNextResolution ? '.ts' : '';
100
103
  if (!noSegmentUpdate) {
101
104
  const newSegmentCode = await prettify(addClassToSegmentCode(segmentSourceCode, {
102
105
  sourceName,
103
106
  compiledName,
104
- type,
105
107
  importPath,
106
108
  }), routeFilePath);
107
109
  if (!dryRun) {
@@ -1,5 +1,6 @@
1
- export default function newSegment({ segmentName, overwrite, dryRun, }: {
1
+ export default function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }: {
2
2
  segmentName: string;
3
+ isStaticSegment?: boolean;
3
4
  overwrite?: boolean;
4
5
  dryRun?: boolean;
5
6
  }): Promise<void>;
@@ -5,23 +5,29 @@ import getFileSystemEntryType from '../utils/getFileSystemEntryType.mjs';
5
5
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
6
6
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
7
7
  import prettify from '../utils/prettify.mjs';
8
- export default async function newSegment({ segmentName, overwrite, dryRun, }) {
9
- const { apiDir, cwd, log } = await getProjectInfo();
8
+ import chalk from 'chalk';
9
+ export default async function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }) {
10
+ const { apiDir, cwd, log, config } = await getProjectInfo();
10
11
  const absoluteSegmentRoutePath = path.join(cwd, apiDir, segmentName, '[[...vovk]]/route.ts');
11
12
  if (!overwrite && (await getFileSystemEntryType(absoluteSegmentRoutePath))) {
12
13
  throw new Error(`Unable to create new segment. ${formatLoggedSegmentName(segmentName, { upperFirst: true })} already exists.`);
13
14
  }
14
- const code = await prettify(`import { initVovk } from 'vovk';
15
+ const code = await prettify(`import { initVovk${isStaticSegment ? ', generateStaticAPI' : ''} } from 'vovk';
16
+
17
+ export const runtime = 'edge';
15
18
 
16
19
  const controllers = {};
17
- const workers = {};
18
20
 
19
21
  export type Controllers = typeof controllers;
20
- export type Workers = typeof workers;
21
-
22
- export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initVovk({
22
+ ${isStaticSegment
23
+ ? `
24
+ export function generateStaticParams() {
25
+ return generateStaticAPI(controllers);
26
+ }
27
+ `
28
+ : ''}
29
+ export const { GET${isStaticSegment ? '' : ', POST, PATCH, PUT, HEAD, OPTIONS, DELETE'} } = initVovk({
23
30
  ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
24
- workers,
25
31
  controllers,
26
32
  });
27
33
  `, absoluteSegmentRoutePath);
@@ -29,5 +35,7 @@ ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
29
35
  await fs.mkdir(path.dirname(absoluteSegmentRoutePath), { recursive: true });
30
36
  await fs.writeFile(absoluteSegmentRoutePath, code);
31
37
  }
32
- log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} created at ${absoluteSegmentRoutePath}. Run ${chalkHighlightThing(`vovk new controller ${[segmentName, 'someName'].filter(Boolean).join('/')}`)} to create a new controller`);
38
+ log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true, isStatic: isStaticSegment })} created at ${absoluteSegmentRoutePath}.`);
39
+ const dir = chalk.cyanBright([segmentName, 'thing'].filter(Boolean).join('/'));
40
+ log.info(`Run ${chalkHighlightThing(`npx vovk new service controller ${dir}`)} to create a new controller with a service at ${path.join(config.modulesDir, dir)} folder for this segment`);
33
41
  }
@@ -1,8 +1,12 @@
1
- import type { VovkConfig, VovkModuleRenderResult } from '../types.mjs';
2
- export default function render(codeTemplate: string, { config, withService, segmentName, moduleName, }: {
1
+ import type { VovkStrictConfig } from 'vovk';
2
+ import type { VovkModuleRenderResult } from '../types.mjs';
3
+ export default function render(codeTemplate: string, { config, withService, segmentName, moduleName, empty, templateFileName, isNodeNextResolution, }: {
3
4
  cwd: string;
4
- config: VovkConfig;
5
+ config: VovkStrictConfig;
5
6
  withService: boolean;
6
7
  segmentName: string;
7
8
  moduleName: string;
9
+ empty?: boolean;
10
+ templateFileName: string;
11
+ isNodeNextResolution: boolean;
8
12
  }): Promise<VovkModuleRenderResult>;
@@ -4,25 +4,46 @@ import _ from 'lodash';
4
4
  import pluralize from 'pluralize';
5
5
  import addCommonTerms from './addCommonTerms.mjs';
6
6
  addCommonTerms();
7
- export default async function render(codeTemplate, { config, withService, segmentName, moduleName, }) {
7
+ export default async function render(codeTemplate, { config, withService, segmentName, moduleName, empty, templateFileName, isNodeNextResolution, }) {
8
8
  const getModuleDirName = (givenSegmentName, givenModuleName) => [config.modulesDir, givenSegmentName || config.rootSegmentModulesDirName, _.camelCase(givenModuleName)]
9
9
  .filter(Boolean)
10
10
  .join('/');
11
- const templateVars = {
12
- // input
11
+ const theThing = _.camelCase(moduleName);
12
+ const TheThing = _.upperFirst(theThing);
13
+ const the_thing = _.snakeCase(moduleName);
14
+ const THE_THING = _.toUpper(the_thing);
15
+ const the__thing = _.kebabCase(moduleName);
16
+ const t = {
17
+ // module name variations
18
+ moduleName,
19
+ theThing,
20
+ theThings: pluralize(theThing),
21
+ TheThing,
22
+ TheThings: pluralize(TheThing),
23
+ the_thing,
24
+ the_things: pluralize(the_thing),
25
+ THE_THING,
26
+ THE_THINGS: pluralize(THE_THING),
27
+ 'the-thing': the__thing,
28
+ 'the-things': pluralize(the__thing),
29
+ // data
13
30
  config,
14
31
  withService,
15
32
  segmentName,
16
- moduleName,
17
- // utils
18
- getModuleDirName,
33
+ nodeNextResolutionExt: {
34
+ ts: isNodeNextResolution ? '.ts' : '',
35
+ js: isNodeNextResolution ? '.js' : '',
36
+ cjs: isNodeNextResolution ? '.cjs' : '',
37
+ mjs: isNodeNextResolution ? '.mjs' : '',
38
+ },
39
+ defaultDir: getModuleDirName(segmentName, theThing),
19
40
  // libraries
20
41
  _, // lodash
21
42
  pluralize,
22
43
  };
23
- const parsed = matter((await ejs.render(codeTemplate, templateVars, { async: true })).trim());
44
+ const parsed = matter((await ejs.render(codeTemplate, { t }, { async: true, filename: templateFileName })).trim());
24
45
  const { dir, fileName, sourceName, compiledName } = parsed.data;
25
- const code = parsed.content;
46
+ const code = empty ? (sourceName ? `export default class ${sourceName} {}` : '') : parsed.content;
26
47
  return {
27
48
  dir,
28
49
  fileName,
package/dist/types.d.mts CHANGED
@@ -1,42 +1,5 @@
1
1
  import type { LogLevelNames } from 'loglevel';
2
- export type KnownAny = any;
3
- export type VovkDevEnv = {
4
- PORT?: string;
5
- VOVK_CLIENT_OUT_DIR?: string;
6
- VOVK_SCHEMA_OUT_DIR?: string;
7
- VOVK_FETCHER?: string;
8
- VOVK_VALIDATE_ON_CLIENT?: string;
9
- VOVK_MODULES_DIR?: string;
10
- VOVK_VALIDATION_LIBRARY?: string;
11
- VOVK_ORIGIN?: string;
12
- VOVK_ROOT_ENTRY?: string;
13
- VOVK_API_ENTRY_POINT?: string;
14
- VOVK_ROOT_SEGMENT_MODULES_DIR_NAME?: string;
15
- VOVK_LOG_LEVEL?: LogLevelNames;
16
- VOVK_PRETTIFY_CLIENT?: string;
17
- VOVK_DEV_HTTPS?: string;
18
- __VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
19
- };
20
- export type VovkConfig = {
21
- clientOutDir?: string;
22
- schemaOutDir?: string;
23
- fetcher?: string;
24
- validateOnClient?: string | null;
25
- modulesDir?: string;
26
- validationLibrary?: string | null;
27
- rootEntry?: string;
28
- origin?: string;
29
- rootSegmentModulesDirName?: string;
30
- logLevel?: LogLevelNames;
31
- prettifyClient?: boolean;
32
- devHttps?: boolean;
33
- templates?: {
34
- service?: string;
35
- controller?: string;
36
- worker?: string;
37
- [key: string]: string | undefined;
38
- };
39
- };
2
+ import type { VovkStrictConfig } from 'vovk';
40
3
  export type VovkModuleRenderResult = {
41
4
  fileName: string;
42
5
  dir: string;
@@ -45,11 +8,31 @@ export type VovkModuleRenderResult = {
45
8
  code: string;
46
9
  };
47
10
  export interface DevOptions {
48
- clientOut?: string;
11
+ schemaOut?: string;
49
12
  nextDev?: boolean;
13
+ exit?: boolean;
50
14
  }
51
15
  export interface GenerateOptions {
52
- clientOut?: string;
16
+ prettify?: boolean;
17
+ configPath?: string;
18
+ schemaPath?: string;
19
+ openapiSpec?: string;
20
+ watch?: boolean | string;
21
+ composedFrom?: string[];
22
+ composedOut?: string;
23
+ composedOnly?: boolean;
24
+ composedIncludeSegments?: string[];
25
+ composedExcludeSegments?: string[];
26
+ segmentedFrom?: string[];
27
+ segmentedOut?: string;
28
+ segmentedOnly?: boolean;
29
+ segmentedIncludeSegments?: string[];
30
+ segmentedExcludeSegments?: string[];
31
+ }
32
+ export interface BundleOptions extends Partial<Omit<VovkStrictConfig['bundle'], 'requires'>> {
33
+ config?: string;
34
+ schema?: string;
35
+ openapiSpec?: string;
53
36
  }
54
37
  export interface InitOptions {
55
38
  yes?: boolean;
@@ -62,8 +45,9 @@ export interface InitOptions {
62
45
  updateTsConfig?: boolean;
63
46
  updateScripts?: 'implicit' | 'explicit';
64
47
  validationLibrary?: string | null;
65
- validateOnClient?: boolean;
48
+ reactQuery?: boolean;
66
49
  dryRun?: boolean;
50
+ lang?: string[];
67
51
  channel?: 'latest' | 'beta' | 'draft';
68
52
  }
69
53
  export interface NewOptions {
@@ -72,4 +56,19 @@ export interface NewOptions {
72
56
  dir?: string;
73
57
  overwrite?: boolean;
74
58
  noSegmentUpdate?: boolean;
59
+ empty?: boolean;
60
+ static?: boolean;
75
61
  }
62
+ export type VovkEnv = {
63
+ PORT?: string;
64
+ VOVK_SCHEMA_OUT_DIR?: string;
65
+ VOVK_ORIGIN?: string;
66
+ VOVK_ROOT_ENTRY?: string;
67
+ VOVK_API_ENTRY_POINT?: string;
68
+ VOVK_LOG_LEVEL?: LogLevelNames;
69
+ VOVK_PRETTIFY_CLIENT?: string;
70
+ VOVK_DEV_HTTPS?: string;
71
+ __VOVK_START_WATCHER_IN_STANDALONE_MODE__?: 'true';
72
+ __VOVK_SCHEMA_OUT_FLAG__?: string;
73
+ __VOVK_EXIT__?: 'true' | 'false';
74
+ };
@@ -1,2 +1,2 @@
1
- import { KnownAny } from '../types.mjs';
2
- export default function debounceWithArgs<T extends (...args: KnownAny[]) => KnownAny>(fn: T, wait: number): (...args: Parameters<T>) => void | Promise<void>;
1
+ import type { KnownAny } from 'vovk';
2
+ export default function debounceWithArgs<Callback extends (...args: KnownAny[]) => KnownAny>(callback: Callback, wait: number): (...args: Parameters<Callback>) => Promise<Awaited<ReturnType<Callback>>>;
@@ -1,14 +1,29 @@
1
- import debounce from 'lodash/debounce.js';
2
- export default function debounceWithArgs(fn, wait) {
3
- const debouncedFunctions = new Map();
1
+ export default function debounceWithArgs(callback, wait) {
2
+ // Stores timeouts keyed by the stringified arguments
3
+ const timeouts = new Map();
4
4
  return (...args) => {
5
+ // Convert arguments to a JSON string (or any other stable key generation)
5
6
  const key = JSON.stringify(args);
6
- if (!debouncedFunctions.has(key)) {
7
- debouncedFunctions.set(key, debounce(fn, wait));
8
- }
9
- const debouncedFn = debouncedFunctions.get(key);
10
- if (debouncedFn) {
11
- debouncedFn(...args);
7
+ // Clear any existing timer for this specific key
8
+ if (timeouts.has(key)) {
9
+ clearTimeout(timeouts.get(key));
12
10
  }
11
+ // Return a promise that resolves/rejects after the debounce delay
12
+ return new Promise((resolve, reject) => {
13
+ const timeoutId = setTimeout(async () => {
14
+ try {
15
+ const result = await callback(...args);
16
+ resolve(result);
17
+ }
18
+ catch (error) {
19
+ reject(error);
20
+ }
21
+ finally {
22
+ // Remove the entry once the callback is invoked
23
+ timeouts.delete(key);
24
+ }
25
+ }, wait);
26
+ timeouts.set(key, timeoutId);
27
+ });
13
28
  };
14
29
  }
@@ -1,4 +1,5 @@
1
- export default function formatLoggedSegmentName(segmentName: string, { withChalk, upperFirst }?: {
1
+ export default function formatLoggedSegmentName(segmentName: string, { withChalk, upperFirst, isStatic, }?: {
2
2
  withChalk?: boolean;
3
3
  upperFirst?: boolean;
4
+ isStatic?: boolean;
4
5
  }): string;
@@ -1,7 +1,7 @@
1
1
  import upperFirstLodash from 'lodash/upperFirst.js';
2
2
  import chalkHighlightThing from './chalkHighlightThing.mjs';
3
- export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false } = {}) {
4
- let text = segmentName ? `segment "${segmentName}"` : 'the root segment';
3
+ export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false, isStatic = false, } = {}) {
4
+ let text = segmentName ? `${isStatic ? 'static ' : ''}segment "${segmentName}"` : 'the root segment';
5
5
  text = upperFirst ? upperFirstLodash(text) : text;
6
6
  return withChalk ? chalkHighlightThing(text) : text;
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import net from 'node:net';
2
- // TODO check comments
2
+ // Created with AI
3
3
  /**
4
4
  * Checks if a port is available.
5
5
  * @param {number} port - The port to check.
@@ -0,0 +1,4 @@
1
+ export default function getPublicModuleNameFromPath(modulePath: string): {
2
+ moduleName: string | null;
3
+ restPath: string;
4
+ };
@@ -0,0 +1,9 @@
1
+ export default function getPublicModuleNameFromPath(modulePath) {
2
+ if (modulePath && !modulePath.startsWith('.') && !modulePath.startsWith('/')) {
3
+ const pathParts = modulePath.split('/');
4
+ const moduleName = pathParts[0].startsWith('@') ? `${pathParts[0]}/${pathParts[1]}` : pathParts[0];
5
+ const restPath = pathParts.slice(pathParts[0].startsWith('@') ? 2 : 1).join('/');
6
+ return { moduleName, restPath };
7
+ }
8
+ return { moduleName: null, restPath: modulePath };
9
+ }
@@ -0,0 +1,3 @@
1
+ import { type VovkSchema } from 'vovk';
2
+ export default function pickSegmentFullSchema(schema: VovkSchema, segmentNames: string[]): VovkSchema;
3
+ export declare function omitSegmentFullSchema(schema: VovkSchema, segmentNames: string[]): VovkSchema;
@@ -0,0 +1,15 @@
1
+ import { VovkSchemaIdEnum } from 'vovk';
2
+ export default function pickSegmentFullSchema(schema, segmentNames) {
3
+ return {
4
+ $schema: VovkSchemaIdEnum.SCHEMA,
5
+ meta: schema.meta,
6
+ segments: Object.fromEntries(segmentNames.map((segmentName) => [segmentName, schema.segments[segmentName]])),
7
+ };
8
+ }
9
+ export function omitSegmentFullSchema(schema, segmentNames) {
10
+ return {
11
+ $schema: VovkSchemaIdEnum.SCHEMA,
12
+ meta: schema.meta,
13
+ segments: Object.fromEntries(Object.entries(schema.segments).filter(([segmentName]) => !segmentNames.includes(segmentName))),
14
+ };
15
+ }
@@ -0,0 +1,10 @@
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
+ declare function removeUnlistedDirectories(folderPath: string, allowedDirs: string[]): Promise<void>;
10
+ export default removeUnlistedDirectories;
@@ -0,0 +1,61 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import getFileSystemEntryType, { FileSystemEntryType } from './getFileSystemEntryType.mjs';
4
+ /**
5
+ * Removes all directories in a folder that aren't in the provided allowlist
6
+ * Supports nested directory paths like 'foo/bar/baz'
7
+ *
8
+ * @param folderPath - The path to the folder to process
9
+ * @param allowedDirs - Array of relative directory paths to keep
10
+ * @returns Promise that resolves when all operations are complete
11
+ */
12
+ async function removeUnlistedDirectories(folderPath, allowedDirs) {
13
+ // Normalize all allowed paths to use the system-specific separator
14
+ const normalizedAllowedDirs = allowedDirs.map((dir) => dir.split('/').join(path.sep));
15
+ // Process the directory tree recursively
16
+ await processDirectory(folderPath, '', normalizedAllowedDirs);
17
+ }
18
+ /**
19
+ * Recursively processes directories to determine which should be kept or removed
20
+ *
21
+ * @param basePath - The absolute base path being processed
22
+ * @param relativePath - The current relative path from the base
23
+ * @param allowedDirs - Normalized list of allowed directory paths
24
+ */
25
+ async function processDirectory(basePath, relativePath, allowedDirs) {
26
+ const currentDirPath = path.join(basePath, relativePath);
27
+ // check if the current path is a directory
28
+ const type = await getFileSystemEntryType(currentDirPath);
29
+ if (type !== FileSystemEntryType.DIRECTORY) {
30
+ // If it's not a directory, return early
31
+ return;
32
+ }
33
+ // Read all entries in the current directory
34
+ const entries = await fs.readdir(currentDirPath, { withFileTypes: true });
35
+ // Process only directories
36
+ const dirEntries = entries.filter((entry) => entry.isDirectory());
37
+ // Check each directory
38
+ for (const dir of dirEntries) {
39
+ // Calculate the new relative path
40
+ const newRelativePath = relativePath ? path.join(relativePath, dir.name) : dir.name;
41
+ // Check if this directory or any of its subdirectories should be kept
42
+ const shouldKeep = allowedDirs.some((allowedDir) => {
43
+ // Direct match
44
+ if (allowedDir === newRelativePath)
45
+ return true;
46
+ // Check if it's a parent path of an allowed directory
47
+ // e.g. "foo" is a parent of "foo/bar/baz"
48
+ return allowedDir.startsWith(newRelativePath + path.sep);
49
+ });
50
+ if (shouldKeep) {
51
+ // Recursively process this directory's contents
52
+ await processDirectory(basePath, newRelativePath, allowedDirs);
53
+ }
54
+ else {
55
+ // Remove this directory since it's not in the allowed list
56
+ const fullPath = path.join(basePath, newRelativePath);
57
+ await fs.rm(fullPath, { recursive: true, force: true });
58
+ }
59
+ }
60
+ }
61
+ export default removeUnlistedDirectories;
@@ -0,0 +1,2 @@
1
+ export declare function getPathUpToModule(moduleName: string, fullPath: string): string;
2
+ export default function resolveAbsoluteModulePath(modulePath: string, cwd: string): string;
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+ import { createRequire } from 'node:module';
3
+ import getPublicModuleNameFromPath from './getPublicModuleNameFromPath.mjs';
4
+ // Returns the path up to and including the last occurrence of the given module name
5
+ export function getPathUpToModule(moduleName, fullPath) {
6
+ const idx = fullPath.lastIndexOf(moduleName);
7
+ if (idx === -1)
8
+ return moduleName;
9
+ return fullPath.slice(0, idx + moduleName.length);
10
+ }
11
+ export default function resolveAbsoluteModulePath(modulePath, cwd) {
12
+ // If it's an absolute path or starts with '.' (relative), resolve it directly
13
+ if (modulePath.startsWith('/') || modulePath.startsWith('.')) {
14
+ return path.resolve(cwd, modulePath);
15
+ }
16
+ // For npm package names, use Node's module resolution algorithm
17
+ try {
18
+ const { moduleName, restPath } = getPublicModuleNameFromPath(modulePath);
19
+ if (!moduleName) {
20
+ throw new Error(`Invalid module path: ${modulePath}`);
21
+ }
22
+ const require = createRequire(import.meta.url);
23
+ const resolved = require.resolve(moduleName);
24
+ return path.resolve(getPathUpToModule(moduleName, path.dirname(resolved)), restPath);
25
+ }
26
+ catch (e) {
27
+ // eslint-disable-next-line no-console
28
+ console.error(`Error resolving module path: ${modulePath}`, e);
29
+ // If resolution fails, fall back to the original behavior
30
+ return path.resolve(cwd, './node_modules', modulePath);
31
+ }
32
+ }
@@ -0,0 +1,50 @@
1
+ <% const vars = {
2
+ rpcModuleName: t.TheThing + 'Controller',
3
+ ServiceName: t.TheThing + 'Service',
4
+ }; %>
5
+ ---
6
+ dir: <%= t.defaultDir %>
7
+ fileName: <%= vars.rpcModuleName + '.ts' %>
8
+ sourceName: <%= vars.rpcModuleName %>
9
+ compiledName: <%= t.TheThing + 'RPC' %>
10
+ ---
11
+
12
+ import { prefix, get, put, post, del, type VovkRequest } from 'vovk';
13
+ <% if(t.withService) { %>
14
+ import <%= vars.ServiceName %> from './<%= vars.ServiceName %><%= t.resolutionExt %>';
15
+ <% } %>
16
+
17
+ @prefix('<%= t['the-things'] %>')
18
+ export default class <%= vars.rpcModuleName %> {
19
+ @get()
20
+ static get<%= t.TheThings %> = async (req: VovkRequest<null, { search: string }>) => {
21
+ const search = req.nextUrl.searchParams.get('search');
22
+ <% if(t.withService) { %>
23
+ return <%= vars.ServiceName %>.get<%= t.TheThings %>(search);
24
+ <% } else { %>
25
+ return { results: [], search };
26
+ <% } %>
27
+ }
28
+
29
+ @put(':id')
30
+ static update<%= t.TheThing %> = async (req: VovkRequest<{ foo: 'bar' | 'baz' }, { q: string }>, params: { id: string }) => {
31
+ const { id } = params;
32
+ const body = await req.json();
33
+ const q = req.nextUrl.searchParams.get('q');
34
+ <% if(t.withService) { %>
35
+ return <%= vars.ServiceName %>.update<%= t.TheThing %>(id, q, body);
36
+ <% } else { %>
37
+ return { id, body, q };
38
+ <% } %>
39
+ };
40
+
41
+ @post()
42
+ static create<%= t.TheThing %> = () => {
43
+ // ...
44
+ };
45
+
46
+ @del(':id')
47
+ static delete<%= t.TheThing %> = () => {
48
+ // ...
49
+ };
50
+ }
@@ -0,0 +1,28 @@
1
+ <% const vars = {
2
+ rpcModuleName: t.TheThing + 'Controller',
3
+ ServiceName: t.TheThing + 'Service',
4
+ }; %>
5
+ ---
6
+ dir: <%= t.defaultDir %>
7
+ fileName: <%= vars.ServiceName + '.ts' %>
8
+ sourceName: <%= vars.ServiceName %>
9
+ ---
10
+
11
+ import type { VovkBody, VovkQuery } from 'vovk';
12
+ import type <%= vars.rpcModuleName %> from './<%= vars.rpcModuleName %><%= t.nodeNextResolutionTsExt %>';
13
+
14
+ export default class <%= vars.ServiceName %> {
15
+ static get<%= t.TheThings %> = (search: VovkQuery<typeof <%= vars.rpcModuleName %>.get<%= t.TheThings %>>['search']) => {
16
+ return { results: [], search };
17
+ };
18
+
19
+ static update<%= t.TheThing %> = (
20
+ id: string,
21
+ q: VovkQuery<typeof <%= vars.rpcModuleName %>.update<%= t.TheThing %>>['q'],
22
+ body: VovkBody<typeof <%= vars.rpcModuleName %>.update<%= t.TheThing %>>
23
+ ) => {
24
+ return { id, q, body };
25
+ };
26
+
27
+ // ...
28
+ }