vovk-cli 0.0.1-beta.60 → 0.0.1-beta.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,30 +7,30 @@
7
7
  </picture>
8
8
  </a>
9
9
  <br>
10
- <strong>Back-end for Next.js (beta)</strong>
10
+ <strong>Back-end Framework for Next.js App Router</strong>
11
11
  <br />
12
- <a href="https://vovk.dev/about">About Vovk.ts</a>
13
- &nbsp;
12
+ <a href="https://vovk.dev/">Documentation</a>
13
+ &nbsp;&nbsp;
14
14
  <a href="https://vovk.dev/quick-install">Quick Start</a>
15
- &nbsp;
16
- <a href="https://github.com/finom/vovk">Github Repo</a>
15
+ &nbsp;&nbsp;
16
+ <a href="https://vovk.dev/performance">Performance</a>
17
17
  </p>
18
18
 
19
19
  ---
20
20
 
21
21
  ## vovk-cli [![npm version](https://badge.fury.io/js/vovk-cli.svg)](https://www.npmjs.com/package/vovk-cli)
22
22
 
23
- The Vovk.ts [CLI](https://vovk.dev/cli) that will be used as a devDependency in a Vovk.ts app.
23
+ Vovk.ts CLI that will be used as a devDependency in a Vovk.ts app.
24
24
 
25
25
  ```sh
26
26
  npm install -D vovk-cli
27
27
  ```
28
28
 
29
- - [vovk dev](https://vovk.dev/dev) - starts the development script that watches the changes in [controllers](https://vovk.dev/controller) and regenerates the [schema](https://vovk.dev/schema) and [client](https://vovk.dev/typescript).
30
- - [vovk generate](https://vovk.dev/generate) - generates the client based on the schema.
31
- - [vovk bundle](https://vovk.dev/bundle) - bundles the client with [tsdown](https://tsdown.dev/).
32
- - [vovk init](https://vovk.dev/init) - initializes Vovk.ts project.
33
- - [vovk new](https://vovk.dev/new) - generates a new controller, service or a custom module.
29
+ - [vovk dev](https://vovk.dev/dev) - starts the development script that watches the changes in [controllers](https://vovk.dev/controller) and regenerates the [schema](https://vovk.dev/schema) and [client](https://vovk.dev/typescript)
30
+ - [vovk generate](https://vovk.dev/generate) - generates the client based on the schema
31
+ - [vovk bundle](https://vovk.dev/bundle) - bundles the client with [tsdown](https://tsdown.dev/)
32
+ - [vovk init](https://vovk.dev/init) - initializes a new Vovk.ts project
33
+ - [vovk new](https://vovk.dev/new) - generates a new controller, service or a custom module
34
34
 
35
35
  ```sh
36
36
  npx vovk-cli --help
@@ -38,13 +38,14 @@ const getIncludedSegmentNames = (config, fullSchema, configKey, cliGenerateOptio
38
38
  };
39
39
  function logClientGenerationResults({ results, log, isEnsuringClient = false, forceNothingWrittenLog = false, clientType = 'Composed', startTime, fromTemplates, }) {
40
40
  const writtenResults = results.filter(({ written }) => written);
41
+ const origins = _.uniq(results.map((result) => result.origin).filter((origin) => !!origin));
41
42
  const duration = Date.now() - startTime;
42
43
  const groupedByDir = _.groupBy(writtenResults, ({ outAbsoluteDir }) => outAbsoluteDir);
43
44
  const logOrDebug = forceNothingWrittenLog ? log.info : log.debug;
44
45
  if (writtenResults.length) {
45
46
  for (const [outAbsoluteDir, dirResults] of Object.entries(groupedByDir)) {
46
47
  const templateNames = _.uniq(dirResults.map(({ templateName }) => templateName));
47
- log.info(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is generated to ${chalkHighlightThing(normalizeOutTemplatePath(outAbsoluteDir, dirResults[0].package))} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))} in ${duration}ms`);
48
+ log.info(`${clientType} client${isEnsuringClient ? ' placeholder' : ''} is generated to ${chalkHighlightThing(normalizeOutTemplatePath(outAbsoluteDir, dirResults[0].package))} from template${templateNames.length !== 1 ? 's' : ''} ${chalkHighlightThing(templateNames.map((s) => `"${s}"`).join(', '))}${origins.length && !isEnsuringClient ? ` with origin${origins.length !== 1 ? 's' : ''} ${chalkHighlightThing(origins.join(', '))}` : ''} in ${duration}ms`);
48
49
  }
49
50
  }
50
51
  else if (fromTemplates.length) {
@@ -191,6 +192,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
191
192
  templateName,
192
193
  outAbsoluteDir,
193
194
  package: packageJson,
195
+ origin,
194
196
  };
195
197
  }));
196
198
  if (composedClientTemplateFiles.length) {
@@ -272,6 +274,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
272
274
  written,
273
275
  templateName,
274
276
  package: packageJson,
277
+ origin,
275
278
  };
276
279
  }));
277
280
  const outAbsoluteDir = path.resolve(cwd, outCwdRelativeDir);
@@ -282,6 +285,7 @@ export async function generate({ isEnsuringClient = false, isBundle = false, pro
282
285
  templateName,
283
286
  outAbsoluteDir,
284
287
  package: results[0]?.package || {}, // TODO: Might be wrong in Python segmented client (unknown use case)
288
+ origin: results[0]?.origin || '',
285
289
  };
286
290
  }));
287
291
  if (segmentedClientTemplateFiles.length) {
@@ -1,9 +1,10 @@
1
+ import { pathToFileURL } from 'node:url';
1
2
  import { readFile } from 'node:fs/promises';
2
3
  import { createRequire } from 'node:module';
3
- import { dirname } from 'node:path';
4
- import { runInNewContext } from 'node:vm';
5
- import { pathToFileURL } from 'node:url';
4
+ import { dirname, extname } from 'node:path';
5
+ import vm from 'node:vm';
6
6
  import { getConfigAbsolutePaths } from './getConfigAbsolutePaths.mjs';
7
+ const isVMModulesEnabled = typeof vm.SourceTextModule !== 'undefined';
7
8
  export async function getUserConfig({ configPath: givenConfigPath, cwd, }) {
8
9
  const configAbsolutePaths = await getConfigAbsolutePaths({ configPath: givenConfigPath, cwd });
9
10
  if (!configAbsolutePaths.length) {
@@ -12,10 +13,7 @@ export async function getUserConfig({ configPath: givenConfigPath, cwd, }) {
12
13
  const configPath = configAbsolutePaths[0];
13
14
  let userConfig;
14
15
  let lastError;
15
- const loaders = [
16
- () => loadConfigWithVm(configPath),
17
- () => importWithCacheBuster(configPath),
18
- ];
16
+ const loaders = await getLoadersForExtension(configPath);
19
17
  for (const loader of loaders) {
20
18
  try {
21
19
  userConfig = await loader();
@@ -27,36 +25,119 @@ export async function getUserConfig({ configPath: givenConfigPath, cwd, }) {
27
25
  }
28
26
  return { userConfig: null, configAbsolutePaths, error: lastError };
29
27
  }
30
- async function importWithCacheBuster(configPath) {
31
- const cacheBuster = Date.now();
32
- const configPathUrl = pathToFileURL(configPath).href;
33
- const { default: userConfig } = (await import(`${configPathUrl}?cache=${cacheBuster}`));
34
- return userConfig;
28
+ async function getLoadersForExtension(configPath) {
29
+ const ext = extname(configPath).toLowerCase();
30
+ const code = await readFile(configPath, 'utf-8');
31
+ const hasDynamicImport = /\bimport\s*\(/.test(code);
32
+ // If config has dynamic imports and flag is not enabled, skip VM loaders entirely
33
+ const canUseVM = isVMModulesEnabled || !hasDynamicImport;
34
+ switch (ext) {
35
+ case '.mjs':
36
+ return canUseVM
37
+ ? [() => importWithVMModule(configPath, code), () => importWithCacheBuster(configPath)]
38
+ : [() => importWithCacheBuster(configPath)];
39
+ case '.cjs':
40
+ return canUseVM
41
+ ? [() => importWithVMCommonJS(configPath, code), () => importWithCacheBuster(configPath)]
42
+ : [() => importWithCacheBuster(configPath)];
43
+ case '.js':
44
+ default:
45
+ return canUseVM
46
+ ? [
47
+ () => importWithVMCommonJS(configPath, code),
48
+ () => importWithVMModule(configPath, code),
49
+ () => importWithCacheBuster(configPath),
50
+ ]
51
+ : [() => importWithCacheBuster(configPath)];
52
+ }
35
53
  }
36
- async function loadConfigWithVm(configPath) {
37
- const source = await readFile(configPath, 'utf8');
38
- const moduleUrl = pathToFileURL(configPath).href;
39
- const cjsSource = transformDefaultExportToCjs(source);
54
+ function createDynamicImportHandler(context) {
55
+ return async (specifier) => {
56
+ const imported = await import(specifier);
57
+ const exportNames = Object.keys(imported);
58
+ const syntheticModule = new vm.SyntheticModule(exportNames, function () {
59
+ for (const name of exportNames) {
60
+ this.setExport(name, imported[name]);
61
+ }
62
+ }, { context, identifier: specifier });
63
+ await syntheticModule.link(() => {
64
+ throw new Error('Nested linking not supported');
65
+ });
66
+ await syntheticModule.evaluate();
67
+ return syntheticModule;
68
+ };
69
+ }
70
+ async function importWithVMCommonJS(configPath, code) {
71
+ const require = createRequire(configPath);
40
72
  const moduleObj = { exports: {} };
41
- const context = {
73
+ const contextObject = {
42
74
  module: moduleObj,
43
75
  exports: moduleObj.exports,
44
- require: createRequire(moduleUrl),
45
- __dirname: dirname(configPath),
76
+ require,
46
77
  __filename: configPath,
78
+ __dirname: dirname(configPath),
47
79
  console,
48
80
  process,
49
81
  Buffer,
82
+ URL,
83
+ URLSearchParams,
50
84
  setTimeout,
51
- clearTimeout,
52
85
  setInterval,
86
+ setImmediate,
87
+ clearTimeout,
53
88
  clearInterval,
54
- URL,
89
+ clearImmediate,
55
90
  };
56
- runInNewContext(cjsSource, context, { filename: moduleUrl, displayErrors: true });
57
- const result = moduleObj.exports;
58
- return result?.default ?? result;
91
+ const context = vm.createContext(contextObject);
92
+ const script = new vm.Script(code, {
93
+ filename: configPath,
94
+ importModuleDynamically: createDynamicImportHandler(context),
95
+ });
96
+ script.runInContext(context);
97
+ return moduleObj.exports;
59
98
  }
60
- function transformDefaultExportToCjs(source) {
61
- return source.replace(/^\s*export\s+default\s+/m, 'module.exports = ');
99
+ async function importWithVMModule(configPath, code) {
100
+ if (!isVMModulesEnabled) {
101
+ throw new Error('vm.SourceTextModule not available');
102
+ }
103
+ const configUrl = pathToFileURL(configPath).href;
104
+ const context = vm.createContext({
105
+ console,
106
+ process,
107
+ Buffer,
108
+ URL,
109
+ URLSearchParams,
110
+ setTimeout,
111
+ setInterval,
112
+ setImmediate,
113
+ clearTimeout,
114
+ clearInterval,
115
+ clearImmediate,
116
+ });
117
+ const module = new vm.SourceTextModule(code, {
118
+ context,
119
+ identifier: configUrl,
120
+ initializeImportMeta(meta) {
121
+ meta.url = configUrl;
122
+ },
123
+ importModuleDynamically: createDynamicImportHandler(context),
124
+ });
125
+ await module.link(async (specifier) => {
126
+ const imported = await import(specifier);
127
+ const exportNames = Object.keys(imported);
128
+ const syntheticModule = new vm.SyntheticModule(exportNames, function () {
129
+ for (const name of exportNames) {
130
+ this.setExport(name, imported[name]);
131
+ }
132
+ }, { context, identifier: specifier });
133
+ return syntheticModule;
134
+ });
135
+ await module.evaluate();
136
+ return module.namespace.default;
137
+ }
138
+ async function importWithCacheBuster(configPath) {
139
+ const cacheBuster = Date.now();
140
+ const configPathUrl = pathToFileURL(configPath).href;
141
+ const { default: userConfig } = (await import(`${configPathUrl}?cache=${cacheBuster}`));
142
+ return userConfig;
62
143
  }
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S node --experimental-vm-modules --disable-warning=ExperimentalWarning
2
2
  import 'dotenv/config';
3
3
  import type { VovkEnv } from './types.mjs';
4
4
  export type { VovkEnv };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S node --experimental-vm-modules --disable-warning=ExperimentalWarning
2
2
  import path from 'node:path';
3
3
  import { readFileSync } from 'node:fs';
4
4
  import 'dotenv/config';
@@ -223,7 +223,7 @@ export class Init {
223
223
  }
224
224
  }
225
225
  bundle ??= await confirm({
226
- message: 'Do you want to set up "tsdown" to bundle TypeScript client?',
226
+ message: 'Do you want to set up "tsdown" and tsconfig.bundle.json to bundle TypeScript client?',
227
227
  default: true,
228
228
  });
229
229
  updateScripts ??= !pkgJson
@@ -46,7 +46,7 @@ export async function render(codeTemplate, { cwd, config, withService, segmentNa
46
46
  };
47
47
  const parsed = matter((await ejs.render(codeTemplate, { t }, { async: true, filename: templateFileName })).trim());
48
48
  const { outDir, fileName, sourceName, compiledName } = parsed.data;
49
- const code = empty ? (sourceName ? `export class ${sourceName} {}` : '') : parsed.content;
49
+ const code = empty ? (sourceName ? `export default class ${sourceName} {}` : '') : parsed.content;
50
50
  return {
51
51
  outDir,
52
52
  fileName,
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "vovk-cli",
3
- "version": "0.0.1-beta.60",
3
+ "version": "0.0.1-beta.62",
4
+ "description": "CLI tool for managing Vovk.ts projects",
4
5
  "bin": {
5
6
  "vovk": "./dist/index.mjs"
6
7
  },
@@ -12,11 +13,12 @@
12
13
  "build": "rm -rf dist tsconfig.build.tsbuildinfo && tsc -P tsconfig.build.json",
13
14
  "postbuild": "chmod +x ./dist/index.mjs",
14
15
  "pre-test": "npm run build",
15
- "test-only": "npm run pre-test && node --experimental-transform-types --experimental-strip-types --test --test-only test/spec/**/*.mts",
16
- "test": "npm run pre-test && node --experimental-transform-types --experimental-strip-types --test --test-concurrency=1 test/spec/**/*.mts",
16
+ "test-only": "npm run pre-test && node --experimental-transform-types --experimental-strip-types --experimental-vm-modules --test --test-only test/spec/**/*.mts",
17
+ "test": "npm run pre-test && node --experimental-transform-types --experimental-strip-types --experimental-vm-modules --test --test-concurrency=1 test/spec/**/*.mts",
17
18
  "tsc": "tsc --noEmit",
18
19
  "ncu": "npm-check-updates -u -x commander,node-pty",
19
- "npm-publish": "npm publish"
20
+ "npm-publish": "npm publish",
21
+ "clear-test-cache": "rm -rf ./tmp_*"
20
22
  },
21
23
  "repository": {
22
24
  "type": "git",
@@ -35,7 +37,7 @@
35
37
  },
36
38
  "homepage": "https://vovk.dev",
37
39
  "peerDependencies": {
38
- "vovk": "^3.0.0-beta.89",
40
+ "vovk": "^3.0.0-beta.98",
39
41
  "vovk-ajv": "^0.0.0-beta.4",
40
42
  "vovk-client": "^0.0.4-beta.4",
41
43
  "vovk-python": "^0.0.1-beta.4",