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,28 +1,62 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { getFileSystemEntryType, FileSystemEntryType } from './get-file-system-entry-type.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
- export async function removeUnlistedDirectories(folderPath, allowedDirs) {
3
+ import { GENERATED_BANNER_PREFIX } from './generated-banner.mjs';
4
+ import { FileSystemEntryType, getFileSystemEntryType } from './get-file-system-entry-type.mjs';
5
+ // removes all dirs in folderPath that aren't in allowedDirs, supports nested paths like 'foo/bar/baz'
6
+ // generatedRelPaths guards user files: a dir holding anything the generator wouldn't write is kept,
7
+ // returns the dirs that were kept for that reason
8
+ export async function removeUnlistedDirectories(folderPath, allowedDirs, generatedRelPaths) {
13
9
  // Normalize all allowed paths to use the system-specific separator
14
10
  const normalizedAllowedDirs = allowedDirs.map((dir) => dir.split('/').join(path.sep));
11
+ const skipped = [];
15
12
  // Process the directory tree recursively
16
- await processDirectory(folderPath, '', normalizedAllowedDirs);
13
+ await processDirectory(folderPath, '', normalizedAllowedDirs, generatedRelPaths, skipped);
14
+ return skipped;
17
15
  }
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) {
16
+ // "[package_name]" is substituted at write time, so treat it as a wildcard segment
17
+ function matchesGeneratedPath(relPath, generatedRelPaths) {
18
+ const segments = relPath.split(path.sep);
19
+ return generatedRelPaths.some((generated) => {
20
+ const generatedSegments = generated.split(path.sep);
21
+ if (generatedSegments.length > segments.length)
22
+ return false;
23
+ // a nested segment such as bar/baz adds leading directories, so match the tail
24
+ const offset = segments.length - generatedSegments.length;
25
+ return generatedSegments.every((segment, i) => segment === '[package_name]' || segment === segments[offset + i]);
26
+ });
27
+ }
28
+ // a matching name is not enough, a user file may be named like ours, so require our banner too.
29
+ // json holds no comment and therefore no banner, so there the name stays the only signal
30
+ async function isGeneratedFile(absolutePath, relPath, generatedRelPaths) {
31
+ if (!matchesGeneratedPath(relPath, generatedRelPaths))
32
+ return false;
33
+ if (path.extname(absolutePath) === '.json')
34
+ return true;
35
+ try {
36
+ const content = await fs.readFile(absolutePath, 'utf-8');
37
+ return content.slice(0, content.indexOf('\n') + 1 || undefined).includes(GENERATED_BANNER_PREFIX);
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ // true when every file below dirPath is something the generator wrote
44
+ async function containsOnlyGenerated(dirPath, generatedRelPaths, relativePath = '') {
45
+ const entries = await fs.readdir(path.join(dirPath, relativePath), { withFileTypes: true });
46
+ for (const entry of entries) {
47
+ const entryRelPath = relativePath ? path.join(relativePath, entry.name) : entry.name;
48
+ if (entry.isDirectory()) {
49
+ if (!(await containsOnlyGenerated(dirPath, generatedRelPaths, entryRelPath)))
50
+ return false;
51
+ }
52
+ else if (!(await isGeneratedFile(path.join(dirPath, entryRelPath), entryRelPath, generatedRelPaths))) {
53
+ return false;
54
+ }
55
+ }
56
+ return true;
57
+ }
58
+ // recursively decides which dirs to keep or remove
59
+ async function processDirectory(basePath, relativePath, allowedDirs, generatedRelPaths, skipped) {
26
60
  const currentDirPath = path.join(basePath, relativePath);
27
61
  // check if the current path is a directory
28
62
  const type = await getFileSystemEntryType(currentDirPath);
@@ -49,11 +83,16 @@ async function processDirectory(basePath, relativePath, allowedDirs) {
49
83
  });
50
84
  if (shouldKeep) {
51
85
  // Recursively process this directory's contents
52
- await processDirectory(basePath, newRelativePath, allowedDirs);
86
+ await processDirectory(basePath, newRelativePath, allowedDirs, generatedRelPaths, skipped);
53
87
  }
54
88
  else {
55
- // Remove this directory since it's not in the allowed list
56
89
  const fullPath = path.join(basePath, newRelativePath);
90
+ // never delete a directory that holds files we did not generate
91
+ if (generatedRelPaths && !(await containsOnlyGenerated(fullPath, generatedRelPaths))) {
92
+ skipped.push(fullPath);
93
+ continue;
94
+ }
95
+ // Remove this directory since it's not in the allowed list
57
96
  await fs.rm(fullPath, { recursive: true, force: true });
58
97
  }
59
98
  }
@@ -1,5 +1,5 @@
1
- import path from 'node:path';
2
1
  import { createRequire } from 'node:module';
2
+ import path from 'node:path';
3
3
  import { getPublicModuleNameFromPath } from './get-public-module-name-from-path.mjs';
4
4
  // Returns the path up to and including the last occurrence of the given module name
5
5
  export function getPathUpToModule(moduleName, fullPath) {
@@ -24,7 +24,6 @@ export function resolveAbsoluteModulePath(modulePath, cwd) {
24
24
  return path.resolve(getPathUpToModule(moduleName, path.dirname(resolved)), restPath);
25
25
  }
26
26
  catch (e) {
27
- // eslint-disable-next-line no-console
28
27
  console.error(`Error resolving module path: ${modulePath}`, e);
29
28
  // If resolution fails, fall back to the original behavior
30
29
  return path.resolve(cwd, './node_modules', modulePath);
@@ -1,4 +1,4 @@
1
- import { Project, QuoteKind, IndentationText, NewLineKind, SyntaxKind, Node, } from 'ts-morph';
1
+ import { IndentationText, NewLineKind, Node, Project, QuoteKind, SyntaxKind, } from 'ts-morph';
2
2
  export function updateConfigProperty(sourceCode, pathToProperty, newValue) {
3
3
  const project = createProject();
4
4
  const sourceFile = project.createSourceFile('config-temp.mts', sourceCode, { overwrite: true });
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-beta.0",
4
4
  "description": "CLI tool for managing Vovk.ts projects",
5
5
  "files": [
6
6
  "dist",
7
7
  "client-templates",
8
- "module-templates"
8
+ "module-templates",
9
+ "CHANGELOG.md"
9
10
  ],
10
11
  "bin": {
11
12
  "vovk": "./dist/index.mjs"
@@ -18,10 +19,10 @@
18
19
  "build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -P tsconfig.build.json",
19
20
  "postbuild": "chmod +x ./dist/index.mjs",
20
21
  "pre-test": "npm run build && npm run clear-test-cache",
21
- "test-only": "npm run pre-test && NODE_ENV=test node --experimental-transform-types --experimental-strip-types --experimental-vm-modules --test --test-only test/spec/**/*.mts",
22
- "test": "npm run pre-test && NODE_ENV=test node --experimental-transform-types --experimental-strip-types --experimental-vm-modules --test --test-concurrency=1 test/spec/**/*.mts",
22
+ "test-only": "npm run pre-test && NODE_ENV=test node --experimental-strip-types --experimental-vm-modules --test --test-only test/spec/**/*.mts",
23
+ "test": "npm run pre-test && NODE_ENV=test node --experimental-strip-types --experimental-vm-modules --test --test-concurrency=1 test/spec/**/*.mts",
23
24
  "tsc": "tsc --noEmit",
24
- "ncu": "npm-check-updates -u -x commander,node-pty",
25
+ "ncu": "npm-check-updates -u -x commander,tsdown",
25
26
  "npm-publish": "npm publish",
26
27
  "clear-test-cache": "rm -rf ./tmp_*"
27
28
  },
@@ -44,36 +45,29 @@
44
45
  "peerDependencies": {
45
46
  "vovk": ">=3.0.0"
46
47
  },
47
- "optionalDependencies": {
48
- "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.13"
49
- },
50
48
  "dependencies": {
51
49
  "@iarna/toml": "^2.2.5",
52
- "@inquirer/prompts": "^8.3.2",
53
- "@npmcli/package-json": "^7.0.5",
50
+ "@inquirer/prompts": "^8.5.2",
51
+ "@npmcli/package-json": "^8.0.0",
54
52
  "@types/json-schema": "^7.0.15",
55
- "@types/lodash": "^4.17.24",
56
- "chalk": "^5.6.2",
53
+ "@types/lodash": "^4.17.25",
54
+ "chalk": "^6.0.0",
57
55
  "chokidar": "^5.0.0",
58
- "clone-deep": "^4.0.1",
59
56
  "commander": "^13.1.0",
60
- "concurrently": "^9.2.1",
61
- "dotenv": "^17.4.1",
62
- "ejs": "^5.0.1",
63
- "get-tsconfig": "^4.13.7",
57
+ "concurrently": "^10.0.4",
58
+ "dotenv": "^17.4.2",
59
+ "ejs": "^6.0.1",
60
+ "get-tsconfig": "^4.14.1",
64
61
  "glob": "^13.0.6",
65
62
  "gray-matter": "^4.0.3",
66
- "inflection": "^3.0.2",
67
63
  "jsonc-parser": "^3.3.1",
68
64
  "lodash": "^4.18.1",
69
65
  "loglevel": "^1.9.2",
70
66
  "pluralize": "^8.0.0",
71
- "prettier": "^3.8.1",
72
- "tar-stream": "^3.1.8",
73
- "ts-morph": "^27.0.2",
74
- "type-fest": "^5.5.0",
75
- "undici": "^8.0.2",
76
- "yaml": "^2.8.3"
67
+ "ts-morph": "^28.0.0",
68
+ "type-fest": "^5.8.0",
69
+ "undici": "^8.9.0",
70
+ "yaml": "^2.9.0"
77
71
  },
78
72
  "devDependencies": {
79
73
  "@types/concat-stream": "^2.0.3",
@@ -81,13 +75,12 @@
81
75
  "@types/js-yaml": "^4.0.9",
82
76
  "@types/npmcli__package-json": "^4.0.4",
83
77
  "@types/pluralize": "^0.0.33",
84
- "@types/tar-stream": "^3.1.4",
85
78
  "concat-stream": "^2.0.0",
86
- "create-next-app": "^16.2.2",
79
+ "create-next-app": "^16.2.12",
87
80
  "http-server": "^14.1.1",
88
- "node-pty": "0.10.1",
89
- "openapi3-ts": "^4.5.0",
90
- "tsdown": "^0.21.7",
91
- "zod": "^4.3.6"
81
+ "node-pty": "1.1.0",
82
+ "openapi3-ts": "^4.6.1",
83
+ "tsdown": "0.22.14",
84
+ "zod": "^4.4.3"
92
85
  }
93
86
  }
@@ -1,21 +0,0 @@
1
- <%- t.getFirstLineBanner() %>
2
- import type { VovkFetcher } from 'vovk/fetcher';
3
- import type { createRPC } from '<%= t.commonImports.createRPC %>';
4
- <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { if(segment.segmentType !== 'mixin') { %>
5
- import type { Controllers as Controllers<%= i %> } from "<%= t.segmentMeta[segment.segmentName].segmentImportPath %>";
6
- <% }}) %>
7
- <% if (t.hasMixins) { %>
8
- import type { Controllers as MixinControllers, Mixins } from "./mixins";
9
- <% } %>
10
- <% Object.entries(t.reExports).forEach(([reExportWhat, reExportFrom]) => { %>
11
- export { <%= reExportWhat %> } from '<%= reExportFrom %>';
12
- <% }) %>
13
- <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { %>
14
- <% Object.keys(segment.controllers).forEach((rpcModuleName) => { %>
15
- export const <%= rpcModuleName %>: ReturnType<typeof createRPC<<%= segment.segmentType === 'mixin' ? `MixinControllers` : `Controllers${i}` %>["<%= rpcModuleName %>"], typeof import('<%- t.segmentImports[segment.segmentName].fetcher %>').fetcher extends VovkFetcher<infer U> ? U : never>>;
16
- <% })
17
- }) %>
18
- export { schema } from './schema.js';
19
- <% if (t.hasMixins) { %>
20
- export { Mixins };
21
- <% } %>
@@ -1,18 +0,0 @@
1
- <%- t.getFirstLineBanner() %>
2
- import { createRPC } from '<%= t.commonImports.createRPC %>';
3
- import { schema } from './schema.js';
4
- <% Object.entries(t.reExports).forEach(([reExportWhat, reExportFrom]) => { %>
5
- export { <%= reExportWhat %> } from '<%= reExportFrom %>';
6
- <% }) %>
7
- <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment, i) => { %>
8
- <% Object.keys(segment.controllers).forEach((rpcModuleName) => {
9
- const apiRoot = t.segmentMeta[segment.segmentName].forceApiRoot ?? t.apiRoot; %>
10
- export const <%= rpcModuleName %> = createRPC(
11
- schema, '<%= segment.segmentName %>', '<%= rpcModuleName %>', import('<%- t.segmentImports[segment.segmentName].fetcher %>'),
12
- { validateOnClient: <%- t.segmentImports[segment.segmentName].validateOnClient ? `import('${t.segmentImports[segment.segmentName].validateOnClient}')` : 'undefined' %>, <%- typeof t.segmentMeta[segment.segmentName].segmentNameOverride === 'string' ? `segmentNameOverride: '${t.segmentMeta[segment.segmentName].segmentNameOverride}', ` : '' %><%- segment.segmentType === 'mixin' ? '' : apiRoot ? `apiRoot: '${apiRoot}'` : '' %> }
13
- );
14
- <%
15
- });
16
- });
17
- %>
18
- export { schema };
@@ -1,4 +0,0 @@
1
- <%- t.getFirstLineBanner() %>
2
- import type { OpenAPIObject } from 'openapi3-ts/oas31';
3
-
4
- export const openapi: OpenAPIObject;
@@ -1,4 +0,0 @@
1
- <%- t.getFirstLineBanner() %>
2
- import openapi from './openapi.json' with { type: 'json' };
3
-
4
- export { openapi };
@@ -1,10 +0,0 @@
1
- <%- t.getFirstLineBanner() %>
2
- import type { VovkMetaSchema, VovkSegmentSchema } from 'vovk';
3
-
4
- export const schema: {
5
- $schema: '<%- t.VovkSchemaIdEnum.SCHEMA %>';
6
- meta: VovkMetaSchema;
7
- segments: {<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema).forEach((segment) => { %>
8
- '<%= segment.segmentName %>': VovkSegmentSchema;<% }) %>
9
- };
10
- };
@@ -1,29 +0,0 @@
1
- <%- t.getFirstLineBanner() %>
2
- <% if(t.isVovkProject) { %>
3
- import meta from './<%= t.schemaOutDir %>/_meta.json' with { type: 'json' };
4
- <% } else { %>
5
- const meta = <%- JSON.stringify(t.schema.meta, null, 2) %>;
6
- <% } %>
7
-
8
- <% if(t.hasMixins) { %>
9
- import mixins from './mixins.json' with { type: 'json' };
10
- <% } %>
11
- <% Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType !== 'mixin').forEach((segment, i) => { %>
12
- import schema<%= i %> from './<%= t.schemaOutDir %>/<%= segment.segmentName || t.ROOT_SEGMENT_FILE_NAME %>.json' with { type: 'json' };
13
- <% }) %>
14
-
15
- const segments = {<% Object.values(t.schema.segments).filter((segment) => segment.emitSchema && segment.segmentType !== 'mixin').forEach((segment, i) => { %>
16
- '<%= segment.segmentName %>': schema<%= i %>,
17
- <% }) %>
18
- <% if(t.hasMixins) { %>
19
- ...mixins,
20
- <% } %>
21
- };
22
-
23
- const schema = {
24
- $schema: '<%- t.VovkSchemaIdEnum.SCHEMA %>',
25
- segments,
26
- meta,
27
- };
28
-
29
- export { schema };