vovk-cli 0.0.1-draft.3 → 0.0.1-draft.300

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 (152) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -1
  3. package/client-templates/cjs/index.cjs.ejs +19 -0
  4. package/client-templates/cjs/index.d.cts.ejs +25 -0
  5. package/client-templates/mixins/mixins.d.ts.ejs +64 -0
  6. package/client-templates/mixins/mixins.json.ejs +1 -0
  7. package/client-templates/mjs/index.d.mts.ejs +25 -0
  8. package/client-templates/mjs/index.mjs.ejs +23 -0
  9. package/client-templates/packageJson/package.json.ejs +1 -0
  10. package/client-templates/readme/README.md.ejs +38 -0
  11. package/client-templates/schemaCjs/schema.cjs.ejs +26 -0
  12. package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
  13. package/client-templates/schemaJson/schema.json.ejs +1 -0
  14. package/client-templates/schemaTs/schema.ts.ejs +35 -0
  15. package/client-templates/ts/index.ts.ejs +33 -0
  16. package/dist/bundle/index.d.mts +8 -0
  17. package/dist/bundle/index.mjs +90 -0
  18. package/dist/dev/diffSegmentSchema.d.mts +36 -0
  19. package/dist/{watcher/diffSchema.mjs → dev/diffSegmentSchema.mjs} +4 -12
  20. package/dist/{watcher → dev}/ensureSchemaFiles.d.mts +3 -0
  21. package/dist/{watcher → dev}/ensureSchemaFiles.mjs +17 -21
  22. package/dist/dev/index.d.mts +9 -0
  23. package/dist/dev/index.mjs +388 -0
  24. package/dist/dev/logDiffResult.d.mts +3 -0
  25. package/dist/dev/logDiffResult.mjs +57 -0
  26. package/dist/dev/writeMetaJson.d.mts +2 -0
  27. package/dist/dev/writeMetaJson.mjs +17 -0
  28. package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
  29. package/dist/dev/writeOneSegmentSchemaFile.mjs +32 -0
  30. package/dist/generate/ensureClient.d.mts +3 -0
  31. package/dist/generate/ensureClient.mjs +32 -0
  32. package/dist/generate/generate.d.mts +15 -0
  33. package/dist/generate/generate.mjs +291 -0
  34. package/dist/generate/getClientTemplateFiles.d.mts +20 -0
  35. package/dist/generate/getClientTemplateFiles.mjs +89 -0
  36. package/dist/generate/getProjectFullSchema.d.mts +7 -0
  37. package/dist/generate/getProjectFullSchema.mjs +65 -0
  38. package/dist/generate/getTemplateClientImports.d.mts +18 -0
  39. package/dist/generate/getTemplateClientImports.mjs +38 -0
  40. package/dist/generate/index.d.mts +33 -0
  41. package/dist/generate/index.mjs +189 -0
  42. package/dist/generate/mergePackages.d.mts +7 -0
  43. package/dist/generate/mergePackages.mjs +55 -0
  44. package/dist/generate/writeOneClientFile.d.mts +36 -0
  45. package/dist/generate/writeOneClientFile.mjs +120 -0
  46. package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
  47. package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +6 -3
  48. package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
  49. package/dist/getProjectInfo/getConfig/getRelativeSrcRoot.mjs +12 -0
  50. package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +16 -0
  51. package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +98 -0
  52. package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
  53. package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +7 -5
  54. package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -5
  55. package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -1
  56. package/dist/getProjectInfo/getConfig/index.d.mts +120 -0
  57. package/dist/getProjectInfo/getConfig/index.mjs +94 -0
  58. package/dist/getProjectInfo/index.d.mts +12 -9
  59. package/dist/getProjectInfo/index.mjs +22 -23
  60. package/dist/index.d.mts +2 -24
  61. package/dist/index.mjs +106 -69
  62. package/dist/init/checkTSConfigForExperimentalDecorators.mjs +2 -2
  63. package/dist/init/createConfig.d.mts +3 -4
  64. package/dist/init/createConfig.mjs +22 -16
  65. package/dist/init/getTemplateFilesFromPackage.d.mts +2 -1
  66. package/dist/init/getTemplateFilesFromPackage.mjs +13 -9
  67. package/dist/init/index.d.mts +2 -3
  68. package/dist/init/index.mjs +119 -138
  69. package/dist/init/installDependencies.d.mts +4 -1
  70. package/dist/init/installDependencies.mjs +6 -4
  71. package/dist/init/logUpdateDependenciesError.d.mts +13 -0
  72. package/dist/init/logUpdateDependenciesError.mjs +51 -0
  73. package/dist/init/updateDependenciesWithoutInstalling.d.mts +3 -2
  74. package/dist/init/updateDependenciesWithoutInstalling.mjs +50 -15
  75. package/dist/init/updateNPMScripts.d.mts +3 -1
  76. package/dist/init/updateNPMScripts.mjs +10 -7
  77. package/dist/init/updateTypeScriptConfig.d.mts +4 -1
  78. package/dist/init/updateTypeScriptConfig.mjs +13 -9
  79. package/dist/initProgram.d.mts +2 -0
  80. package/dist/initProgram.mjs +22 -0
  81. package/dist/locateSegments.d.mts +8 -1
  82. package/dist/locateSegments.mjs +16 -6
  83. package/dist/new/addClassToSegmentCode.d.mts +1 -2
  84. package/dist/new/addClassToSegmentCode.mjs +9 -5
  85. package/dist/new/addCommonTerms.mjs +1 -0
  86. package/dist/new/index.d.mts +2 -2
  87. package/dist/new/index.mjs +14 -3
  88. package/dist/new/newModule.d.mts +7 -2
  89. package/dist/new/newModule.mjs +61 -35
  90. package/dist/new/newSegment.d.mts +4 -2
  91. package/dist/new/newSegment.mjs +22 -13
  92. package/dist/new/render.d.mts +9 -9
  93. package/dist/new/render.mjs +38 -13
  94. package/dist/types.d.mts +73 -28
  95. package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
  96. package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
  97. package/dist/utils/compileTs.d.mts +12 -0
  98. package/dist/utils/compileTs.mjs +261 -0
  99. package/dist/utils/debounceWithArgs.d.mts +2 -2
  100. package/dist/utils/debounceWithArgs.mjs +24 -9
  101. package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
  102. package/dist/utils/formatLoggedSegmentName.mjs +4 -3
  103. package/dist/utils/getAvailablePort.mjs +3 -2
  104. package/dist/utils/getFileSystemEntryType.mjs +1 -1
  105. package/dist/utils/getPackageJson.d.mts +3 -0
  106. package/dist/utils/getPackageJson.mjs +22 -0
  107. package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
  108. package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
  109. package/dist/utils/normalizeOpenAPIMixins.d.mts +7 -0
  110. package/dist/utils/normalizeOpenAPIMixins.mjs +67 -0
  111. package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
  112. package/dist/utils/pickSegmentFullSchema.mjs +15 -0
  113. package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
  114. package/dist/utils/removeUnlistedDirectories.mjs +61 -0
  115. package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
  116. package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
  117. package/module-templates/controller.ts.ejs +56 -0
  118. package/module-templates/service.ts.ejs +28 -0
  119. package/package.json +42 -21
  120. package/dist/generateClient.d.mts +0 -7
  121. package/dist/generateClient.mjs +0 -97
  122. package/dist/getProjectInfo/directoryExists.d.mts +0 -1
  123. package/dist/getProjectInfo/directoryExists.mjs +0 -10
  124. package/dist/getProjectInfo/getConfig.d.mts +0 -11
  125. package/dist/getProjectInfo/getConfig.mjs +0 -29
  126. package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
  127. package/dist/getProjectInfo/getRelativeSrcRoot.mjs +0 -12
  128. package/dist/postinstall.d.mts +0 -1
  129. package/dist/postinstall.mjs +0 -22
  130. package/dist/watcher/diffSchema.d.mts +0 -43
  131. package/dist/watcher/index.d.mts +0 -6
  132. package/dist/watcher/index.mjs +0 -295
  133. package/dist/watcher/isMetadataEmpty.d.mts +0 -2
  134. package/dist/watcher/isMetadataEmpty.mjs +0 -4
  135. package/dist/watcher/logDiffResult.d.mts +0 -3
  136. package/dist/watcher/logDiffResult.mjs +0 -90
  137. package/dist/watcher/writeOneSchemaFile.d.mts +0 -11
  138. package/dist/watcher/writeOneSchemaFile.mjs +0 -27
  139. package/templates/controller.ejs +0 -50
  140. package/templates/service.ejs +0 -7
  141. package/templates/worker.ejs +0 -1
  142. package/templates_old/MyThingController.c.only.template.ts +0 -32
  143. package/templates_old/MyThingController.c.template.ts +0 -34
  144. package/templates_old/MyThingService.s.template.ts +0 -18
  145. package/templates_old/controller.ejs +0 -85
  146. package/templates_old/service.ejs +0 -9
  147. package/templates_old/worker.ejs +0 -9
  148. package/templates_old/zod/MyThingController.c.only.template.ts +0 -32
  149. package/templates_old/zod/MyThingController.c.template.ts +0 -39
  150. package/templates_old/zod/MyThingService.s.template.ts +0 -18
  151. /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
  152. /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
@@ -1,12 +1,15 @@
1
- import NPMCliPackageJson from '@npmcli/package-json';
2
- export default async function updateNPMScripts(root, updateScriptsMode) {
3
- const pkgJson = await NPMCliPackageJson.load(root);
1
+ export function getDevScript(pkgJson, updateScriptsMode) {
2
+ const nextDev = pkgJson.content.scripts?.dev ?? 'next dev';
3
+ const nextDevFlags = nextDev.replace('next dev', '').trim();
4
+ return updateScriptsMode === 'explicit'
5
+ ? `PORT=3000 concurrently '${nextDev}' 'vovk dev' --kill-others`
6
+ : `vovk dev --next-dev${nextDevFlags ? ` -- ${nextDevFlags}` : ''}`;
7
+ }
8
+ export default async function updateNPMScripts(pkgJson, root, updateScriptsMode) {
4
9
  pkgJson.update({
5
10
  scripts: {
6
- generate: 'vovk generate',
7
- dev: updateScriptsMode === 'explicit'
8
- ? "PORT=3000 concurrently 'vovk dev' 'next dev' --kill-others"
9
- : 'vovk dev --next-dev',
11
+ ...pkgJson.content.scripts,
12
+ dev: getDevScript(pkgJson, updateScriptsMode),
10
13
  },
11
14
  });
12
15
  await pkgJson.save();
@@ -1 +1,4 @@
1
- export default function updateTypeScriptConfig(root: string): Promise<void>;
1
+ export default function updateTypeScriptConfig(root: string, compilerOptions: {
2
+ experimentalDecorators?: true;
3
+ emitDecoratorMetadata?: true;
4
+ }): Promise<void>;
@@ -1,15 +1,19 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import * as jsonc from 'jsonc-parser';
4
4
  import prettify from '../utils/prettify.mjs';
5
- export default async function updateTypeScriptConfig(root) {
5
+ export default async function updateTypeScriptConfig(root, compilerOptions) {
6
6
  const tsconfigPath = path.join(root, 'tsconfig.json');
7
7
  const tsconfigContent = await fs.readFile(tsconfigPath, 'utf8');
8
- // Use jsonc-parser to generate edits and modify the experimentalDecorators property
9
- const edits = jsonc.modify(tsconfigContent, ['compilerOptions', 'experimentalDecorators'], true, {
10
- formattingOptions: {},
11
- });
12
- // Apply the edits to the original content
13
- const updatedContent = await prettify(jsonc.applyEdits(tsconfigContent, edits), tsconfigPath);
8
+ let updatedContent = tsconfigContent;
9
+ // Apply each compiler option
10
+ for (const [key, value] of Object.entries(compilerOptions)) {
11
+ const edits = jsonc.modify(updatedContent, ['compilerOptions', key], value, {
12
+ formattingOptions: {},
13
+ });
14
+ updatedContent = jsonc.applyEdits(updatedContent, edits);
15
+ }
16
+ // Prettify the final content
17
+ updatedContent = await prettify(updatedContent, tsconfigPath);
14
18
  await fs.writeFile(tsconfigPath, updatedContent, 'utf8');
15
19
  }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function initProgram(program: Command): Command;
@@ -0,0 +1,22 @@
1
+ import { Init } from './init/index.mjs';
2
+ // reused at vovk-init
3
+ export function initProgram(program) {
4
+ return program
5
+ .argument('[prefix]', 'directory to initialize project in', '.')
6
+ .description('Initialize Vovk.ts project')
7
+ .option('-y, --yes', 'skip all prompts and use default values')
8
+ .option('--log-level <level>', 'set log level', 'info')
9
+ .option('--use-npm', 'use npm as package manager')
10
+ .option('--use-yarn', 'use yarn as package manager')
11
+ .option('--use-pnpm', 'use pnpm as package manager')
12
+ .option('--use-bun', 'use bun as package manager')
13
+ .option('--skip-install', 'skip installing dependencies')
14
+ .option('--update-ts-config', 'update tsconfig.json')
15
+ .option('--update-scripts <mode>', 'update package.json scripts ("implicit" or "explicit")')
16
+ .option('--lang <languages...>', 'generate client for other programming languages by default ("py" for Python and "rs" for Rust are supported)')
17
+ .option('--validation-library <library>', 'validation library to use ("vovk-zod", "vovk-dto" or another); set to "none" to skip')
18
+ .option('--react-query', 'use @tanstack/react-query for data fetching inside components')
19
+ .option('--channel <channel>', 'channel to use for fetching packages', 'latest')
20
+ .option('--dry-run', 'do not write files to disk')
21
+ .action((prefix = '.', options) => new Init().main(prefix, options));
22
+ }
@@ -1,5 +1,12 @@
1
+ import type { VovkStrictConfig } from 'vovk';
2
+ import type { ProjectInfo } from './getProjectInfo/index.mjs';
1
3
  export type Segment = {
2
4
  routeFilePath: string;
3
5
  segmentName: string;
4
6
  };
5
- export default function locateSegments(dir: string, rootDir?: string): Promise<Segment[]>;
7
+ export declare function locateSegments({ dir, rootDir, config, log, }: {
8
+ dir: string | null;
9
+ rootDir?: string;
10
+ config: VovkStrictConfig | null;
11
+ log: ProjectInfo['log'];
12
+ }): Promise<Segment[]>;
@@ -1,10 +1,20 @@
1
- import { promises as fs } from 'fs';
2
- import * as path from 'path';
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
3
  import getFileSystemEntryType from './utils/getFileSystemEntryType.mjs';
4
- export default async function locateSegments(dir, rootDir = dir) {
4
+ export async function locateSegments({ dir, rootDir, config, log, }) {
5
5
  let results = [];
6
+ if (!dir)
7
+ return results; // If dir is null, return empty results because this isn't a Next.js app
8
+ rootDir = rootDir ?? dir;
9
+ let list = [];
6
10
  // Read the contents of the directory
7
- const list = await fs.readdir(dir);
11
+ try {
12
+ list = (await fs.readdir(dir)).toSorted();
13
+ }
14
+ catch {
15
+ // do nothing
16
+ return results;
17
+ }
8
18
  // Iterate through each item in the directory
9
19
  for (const file of list) {
10
20
  const filePath = path.join(dir, file);
@@ -16,12 +26,12 @@ export default async function locateSegments(dir, rootDir = dir) {
16
26
  const routeFilePath = path.join(filePath, 'route.ts');
17
27
  if (await getFileSystemEntryType(routeFilePath)) {
18
28
  // Calculate the basePath relative to the root directory
19
- const segmentName = path.relative(rootDir, dir);
29
+ const segmentName = path.relative(rootDir, dir).replace(/\\/g, '/'); // windows fix
20
30
  results.push({ routeFilePath, segmentName });
21
31
  }
22
32
  }
23
33
  // Recursively search inside subdirectories
24
- const subDirResults = await locateSegments(filePath, rootDir);
34
+ const subDirResults = await locateSegments({ dir: filePath, rootDir, config, log });
25
35
  results = results.concat(subDirResults);
26
36
  }
27
37
  }
@@ -1,6 +1,5 @@
1
- export default function addClassToSegmentCode(segmentSourceCode: string, { sourceName, compiledName, type, importPath, }: {
1
+ export default function addClassToSegmentCode(segmentSourceCode: string, { sourceName, compiledName, importPath, }: {
2
2
  sourceName: string;
3
3
  compiledName: string;
4
- type: 'worker' | 'controller';
5
4
  importPath: string;
6
5
  }): string;
@@ -1,6 +1,10 @@
1
- import { Project, SyntaxKind } from 'ts-morph';
2
- export default function addClassToSegmentCode(segmentSourceCode, { sourceName, compiledName, type, importPath, }) {
3
- const project = new Project();
1
+ import { Project, QuoteKind, SyntaxKind } from 'ts-morph';
2
+ export default function addClassToSegmentCode(segmentSourceCode, { sourceName, compiledName, importPath, }) {
3
+ const project = new Project({
4
+ manipulationSettings: {
5
+ quoteKind: QuoteKind.Single,
6
+ },
7
+ });
4
8
  const sourceFile = project.createSourceFile('route.ts', segmentSourceCode, { overwrite: true });
5
9
  // Add the import if it doesn't exist
6
10
  let importDeclaration = sourceFile.getImportDeclaration((imp) => {
@@ -12,8 +16,8 @@ export default function addClassToSegmentCode(segmentSourceCode, { sourceName, c
12
16
  moduleSpecifier: importPath,
13
17
  });
14
18
  }
15
- // Get the variable declaration for controllers or workers
16
- const variableDeclaration = sourceFile.getVariableDeclaration(`${type}s`);
19
+ // Get the variable declaration for controllers
20
+ const variableDeclaration = sourceFile.getVariableDeclaration('controllers');
17
21
  if (variableDeclaration) {
18
22
  const initializer = variableDeclaration.getInitializer();
19
23
  if (initializer && initializer.getKind() === SyntaxKind.ObjectLiteralExpression) {
@@ -2,6 +2,7 @@ import pluralize from 'pluralize';
2
2
  // feel free to open a direct PR if you have more common terms to add
3
3
  const terms = [
4
4
  ['entity', 'entities'],
5
+ ['entry', 'entries'],
5
6
  ['regex', 'regexes'],
6
7
  ['index', 'indices'],
7
8
  ['matrix', 'matrices'],
@@ -1,2 +1,2 @@
1
- import type { NewOptions } from '../index.mjs';
2
- export default function newComponents(components: string[], options: NewOptions): Promise<void>;
1
+ import type { NewOptions } from '../types.mjs';
2
+ export declare function newComponents(components: string[], { dryRun, dir, templates, overwrite, noSegmentUpdate, empty, static: isStaticSegment }: NewOptions): Promise<void>;
@@ -1,7 +1,8 @@
1
1
  import newModule from './newModule.mjs';
2
2
  import newSegment from './newSegment.mjs';
3
- export default async function newComponents(components, options) {
3
+ export async function newComponents(components, { dryRun, dir, templates, overwrite, noSegmentUpdate, empty, static: isStaticSegment }) {
4
4
  if (components[0] === 'segment' || components[0] === 'segments') {
5
+ // vovk new segment [segmentName]
5
6
  let segmentNames = components
6
7
  .slice(1)
7
8
  .map((segmentName) => (segmentName === '""' || segmentName === "''" ? '' : segmentName));
@@ -9,10 +10,11 @@ export default async function newComponents(components, options) {
9
10
  segmentNames = [''];
10
11
  }
11
12
  for (const segmentName of segmentNames) {
12
- await newSegment({ segmentName, dryRun: options.dryRun });
13
+ await newSegment({ segmentName, isStaticSegment, overwrite, dryRun });
13
14
  }
14
15
  }
15
16
  else {
17
+ // vovk new [what...] [moduleNameWithOptionalSegment]
16
18
  if (components.length < 2) {
17
19
  throw new Error('Invalid command invocation. Please provide at least two arguments.');
18
20
  }
@@ -21,6 +23,15 @@ export default async function newComponents(components, options) {
21
23
  if (!moduleNameWithOptionalSegment) {
22
24
  throw new Error('A module name with an optional segment cannot be empty');
23
25
  }
24
- await newModule({ what, moduleNameWithOptionalSegment, dryRun: options.dryRun });
26
+ await newModule({
27
+ what,
28
+ moduleNameWithOptionalSegment,
29
+ dir,
30
+ templates,
31
+ overwrite,
32
+ noSegmentUpdate,
33
+ dryRun,
34
+ empty,
35
+ });
25
36
  }
26
37
  }
@@ -1,5 +1,10 @@
1
- export default function newModule({ what, moduleNameWithOptionalSegment, dryRun, }: {
1
+ export default function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }: {
2
2
  what: string[];
3
3
  moduleNameWithOptionalSegment: string;
4
- dryRun: boolean;
4
+ dryRun?: boolean;
5
+ dir?: string;
6
+ templates?: string[];
7
+ noSegmentUpdate?: boolean;
8
+ overwrite?: boolean;
9
+ empty?: boolean;
5
10
  }): Promise<void>;
@@ -1,13 +1,15 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
3
- import getProjectInfo from '../getProjectInfo/index.mjs';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { getTsconfig } from 'get-tsconfig';
4
4
  import render from './render.mjs';
5
+ import addClassToSegmentCode from './addClassToSegmentCode.mjs';
6
+ import getProjectInfo from '../getProjectInfo/index.mjs';
5
7
  import chalkHighlightThing from '../utils/chalkHighlightThing.mjs';
6
8
  import formatLoggedSegmentName from '../utils/formatLoggedSegmentName.mjs';
7
- import locateSegments from '../locateSegments.mjs';
8
- import addClassToSegmentCode from './addClassToSegmentCode.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,73 +20,97 @@ 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, }) {
22
- const { config, log, apiDir, cwd } = await getProjectInfo();
23
- const templates = config.templates;
23
+ export default async function newModule({ what, moduleNameWithOptionalSegment, dryRun, dir: dirFlag, templates: templatesFlag, noSegmentUpdate, overwrite, empty, }) {
24
+ const { config, log, cwd, apiDirAbsolutePath } = await getProjectInfo();
25
+ const segments = await locateSegments({ dir: apiDirAbsolutePath, 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
  }
37
39
  });
38
- // check if template exists
40
+ if (templatesFlag) {
41
+ if (templatesFlag.length > what.length) {
42
+ throw new Error('Too many templates provided');
43
+ }
44
+ templates = templatesFlag.reduce((acc, templatePath, index) => ({
45
+ ...acc,
46
+ [what[index]]: templatePath,
47
+ }), templates);
48
+ }
39
49
  for (const type of what) {
40
50
  if (!templates[type]) {
41
- throw new Error(`Template for ${type} not found in config`);
51
+ throw new Error(`Template for "${type}" not found`);
42
52
  }
43
53
  }
44
- const segments = await locateSegments(apiDir);
45
54
  const segment = segments.find((s) => s.segmentName === segmentName);
46
55
  if (!segment) {
47
- throw new Error(`Segment ${segmentName} not found`);
56
+ throw new Error(`Unable to create module. Segment "${segmentName}" not found. Run "vovk new segment ${segmentName}" to create it`);
48
57
  }
49
58
  for (const type of what) {
50
59
  const templatePath = templates[type];
51
- const templateAbsolutePath = templatePath.startsWith('/') || templatePath.startsWith('.')
52
- ? path.resolve(cwd, templatePath)
53
- : path.resolve(cwd, './node_modules', templatePath);
60
+ const templateAbsolutePath = resolveAbsoluteModulePath(templatePath, cwd);
54
61
  const templateCode = await fs.readFile(templateAbsolutePath, 'utf-8');
55
- const { filePath, sourceName, compiledName, code } = await render(templateCode, {
62
+ const { dir: renderedDir, fileName, sourceName, compiledName, code, } = await render(templateCode, {
63
+ cwd,
56
64
  config,
57
65
  withService: what.includes('service'),
58
66
  segmentName,
59
67
  moduleName,
68
+ empty,
69
+ templateFileName: templateAbsolutePath,
70
+ isNodeNextResolution,
60
71
  });
61
- const absoluteModulePath = path.join(cwd, config.modulesDir, filePath);
62
- const dirName = path.dirname(absoluteModulePath);
72
+ const dir = dirFlag || renderedDir;
73
+ if (!dir) {
74
+ throw new Error(`The template for "${type}" does not provide a dir`);
75
+ }
76
+ if (!fileName) {
77
+ throw new Error(`The template for "${type}" does not provide a fileName`);
78
+ }
79
+ const absoluteModuleDir = path.join(cwd, dir);
80
+ const absoluteModulePath = path.join(absoluteModuleDir, fileName);
63
81
  const prettiedCode = await prettify(code, absoluteModulePath);
64
82
  if (!dryRun) {
65
- if (await getFileSystemEntryType(absoluteModulePath)) {
83
+ if (!overwrite && (await getFileSystemEntryType(absoluteModulePath))) {
66
84
  log.warn(`File ${chalkHighlightThing(absoluteModulePath)} already exists, skipping this "${type}"`);
67
85
  }
68
86
  else {
69
- await fs.mkdir(dirName, { recursive: true });
87
+ await fs.mkdir(absoluteModuleDir, { recursive: true });
70
88
  await fs.writeFile(absoluteModulePath, prettiedCode);
89
+ log.info(`Created${empty ? ' empty' : ''} ${chalkHighlightThing(absoluteModulePath)} using ${chalkHighlightThing(`"${type}"`)} template for ${formatLoggedSegmentName(segmentName)}`);
71
90
  }
72
91
  }
73
- if (type === 'controller' || type === 'worker') {
92
+ if (type === 'controller') {
93
+ if (!sourceName) {
94
+ throw new Error(`The template for "${type}" does not provide a sourceName`);
95
+ }
96
+ if (!compiledName) {
97
+ throw new Error(`The template for "${type}" does not provide a compiledName`);
98
+ }
74
99
  const { routeFilePath } = segment;
75
100
  const segmentSourceCode = await fs.readFile(routeFilePath, 'utf-8');
76
- const importPath = path.relative(dirName, absoluteModulePath).replace(/\.(ts|tsx)$/, '');
77
- const newSegmentCode = addClassToSegmentCode(segmentSourceCode, {
78
- sourceName,
79
- compiledName,
80
- type,
81
- importPath,
82
- });
83
- if (!dryRun) {
84
- await fs.writeFile(routeFilePath, newSegmentCode);
101
+ let importPath = path.relative(path.dirname(routeFilePath) + '/', absoluteModulePath).replace(/\.(ts|tsx)$/, '');
102
+ importPath += isNodeNextResolution ? '.ts' : '';
103
+ if (!noSegmentUpdate) {
104
+ const newSegmentCode = await prettify(addClassToSegmentCode(segmentSourceCode, {
105
+ sourceName,
106
+ compiledName,
107
+ importPath,
108
+ }), routeFilePath);
109
+ if (!dryRun) {
110
+ await fs.writeFile(routeFilePath, newSegmentCode);
111
+ }
85
112
  }
86
- log.info(`Added ${chalkHighlightThing(sourceName)} ${type} to ${formatLoggedSegmentName(segmentName)} as ${chalkHighlightThing(compiledName)}`);
113
+ log.info(`Added ${chalkHighlightThing(sourceName)} ${type} as ${chalkHighlightThing(compiledName)} to ${formatLoggedSegmentName(segmentName)}`);
87
114
  }
88
- log.info(`Created ${chalkHighlightThing(sourceName)} with ${chalkHighlightThing(type)} template for ${formatLoggedSegmentName(segmentName)}`);
89
115
  }
90
116
  }
@@ -1,4 +1,6 @@
1
- export default function newSegment({ segmentName, dryRun }: {
1
+ export default function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }: {
2
2
  segmentName: string;
3
- dryRun: boolean;
3
+ isStaticSegment?: boolean;
4
+ overwrite?: boolean;
5
+ dryRun?: boolean;
4
6
  }): Promise<void>;
@@ -1,27 +1,34 @@
1
- import path from 'path';
2
- import fs from 'fs/promises';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
3
  import getProjectInfo from '../getProjectInfo/index.mjs';
4
4
  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, dryRun }) {
9
- const { apiDir, cwd, log } = await getProjectInfo();
10
- const absoluteSegmentRoutePath = path.join(cwd, apiDir, segmentName, '[[...vovk]]/route.ts');
11
- if (await getFileSystemEntryType(absoluteSegmentRoutePath)) {
8
+ import chalk from 'chalk';
9
+ export default async function newSegment({ segmentName, isStaticSegment, overwrite, dryRun, }) {
10
+ const { apiDirAbsolutePath, log, config } = await getProjectInfo();
11
+ if (!apiDirAbsolutePath) {
12
+ throw new Error('No API directory found. Please ensure you are in a Nest.js project.');
13
+ }
14
+ const absoluteSegmentRoutePath = path.join(apiDirAbsolutePath, segmentName, '[[...vovk]]/route.ts');
15
+ if (!overwrite && (await getFileSystemEntryType(absoluteSegmentRoutePath))) {
12
16
  throw new Error(`Unable to create new segment. ${formatLoggedSegmentName(segmentName, { upperFirst: true })} already exists.`);
13
17
  }
14
- const code = await prettify(`import { initVovk } from 'vovk';
18
+ const code = await prettify(`import { initSegment${isStaticSegment ? ', generateStaticAPI' : ''} } from 'vovk';
15
19
 
16
20
  const controllers = {};
17
- const workers = {};
18
21
 
19
22
  export type Controllers = typeof controllers;
20
- export type Workers = typeof workers;
21
-
22
- export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initVovk({
23
+ ${isStaticSegment
24
+ ? `
25
+ export function generateStaticParams() {
26
+ return generateStaticAPI(controllers);
27
+ }
28
+ `
29
+ : ''}
30
+ export const { GET${isStaticSegment ? '' : ', POST, PATCH, PUT, HEAD, OPTIONS, DELETE'} } = initSegment({
23
31
  ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
24
- workers,
25
32
  controllers,
26
33
  });
27
34
  `, absoluteSegmentRoutePath);
@@ -29,5 +36,7 @@ ${segmentName ? ` segmentName: '${segmentName}',\n` : ''} emitSchema: true,
29
36
  await fs.mkdir(path.dirname(absoluteSegmentRoutePath), { recursive: true });
30
37
  await fs.writeFile(absoluteSegmentRoutePath, code);
31
38
  }
32
- log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true })} created at ${absoluteSegmentRoutePath}. Run ${chalkHighlightThing(`vovk new controller ${segmentName}/someName`)} to create a controller or modify the segment file manually`);
39
+ log.info(`${formatLoggedSegmentName(segmentName, { upperFirst: true, isStatic: isStaticSegment })} created at ${absoluteSegmentRoutePath}.`);
40
+ const dir = chalk.cyanBright([segmentName, 'thing'].filter(Boolean).join('/'));
41
+ 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
42
  }
@@ -1,12 +1,12 @@
1
- import type { VovkConfig } from '../types.mjs';
2
- export default function render(codeTemplate: string, { config, withService, segmentName, moduleName, }: {
3
- config: VovkConfig;
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, }: {
4
+ cwd: string;
5
+ config: VovkStrictConfig;
4
6
  withService: boolean;
5
7
  segmentName: string;
6
8
  moduleName: string;
7
- }): Promise<{
8
- filePath: string;
9
- sourceName: string;
10
- compiledName: string;
11
- code: string;
12
- }>;
9
+ empty?: boolean;
10
+ templateFileName: string;
11
+ isNodeNextResolution: boolean;
12
+ }): Promise<VovkModuleRenderResult>;
@@ -4,26 +4,51 @@ 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, }) {
8
- const getModulePath = (givenSegmentName, givenModuleName, fileName) => [givenSegmentName || config.rootSegmentModulesDirName, _.camelCase(givenModuleName), fileName]
7
+ export default async function render(codeTemplate, { config, withService, segmentName, moduleName, empty, templateFileName, isNodeNextResolution, }) {
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
- getModulePath,
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
- // first, render the front matter because it can use ejs variables
24
- const parsed = matter((await ejs.render(codeTemplate, templateVars, { async: true })).trim());
25
- const { filePath, sourceName, compiledName } = parsed.data;
26
- const templateContent = parsed.content;
27
- const code = await ejs.render(templateContent, templateVars, { async: true });
28
- return { filePath, sourceName, compiledName, code };
44
+ const parsed = matter((await ejs.render(codeTemplate, { t }, { async: true, filename: templateFileName })).trim());
45
+ const { dir, fileName, sourceName, compiledName } = parsed.data;
46
+ const code = empty ? (sourceName ? `export default class ${sourceName} {}` : '') : parsed.content;
47
+ return {
48
+ dir,
49
+ fileName,
50
+ sourceName,
51
+ compiledName,
52
+ code,
53
+ };
29
54
  }