zova-vite 1.1.53 → 1.1.54

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/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './collect-css-ssr.ts';
2
2
  export * from './configUtils.ts';
3
+ export * from './generateEntryFiles.ts';
3
4
  export * from './teleports.ts';
4
5
  export * from './types.ts';
5
6
  export * from './utils.ts';
6
7
  export * from './zova.ts';
8
+ export * from './zovaRuntimeResolver.ts';
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import compileTemplate from 'lodash/template.js';
7
7
  import parseArgs from 'minimist';
8
8
  import { createRequire } from 'node:module';
9
9
  import { pathToFileURL } from 'node:url';
10
+ import { lstatSync } from 'node:fs';
10
11
  import chalk from 'chalk';
11
12
  import babel from '@cabloy/vite-plugin-babel';
12
13
  import devtoolsJson from 'vite-plugin-devtools-json';
@@ -183,6 +184,126 @@ function pathToHref(fileName) {
183
184
  return pathToFileURL(fileName).href;
184
185
  }
185
186
 
187
+ const ZOVA_RUNTIME_DIR_ENV = 'ZOVA_RUNTIME_DIR';
188
+ const ZOVA_ISOLATED_ENV = 'ZOVA_ISOLATED';
189
+ const ZOVA_DEFAULT_RUNTIME_DIR = '.zova';
190
+
191
+ /**
192
+ * Resolve the generated-runtime directory without allowing it to escape the
193
+ * application directory. The returned path always uses `/`, which is also the
194
+ * separator used by Vite module ids.
195
+ */
196
+ function resolveZovaRuntimeDir(runtimeDir) {
197
+ const value = process.env[ZOVA_RUNTIME_DIR_ENV] ?? runtimeDir ?? ZOVA_DEFAULT_RUNTIME_DIR;
198
+ if (process.env[ZOVA_ISOLATED_ENV] === 'true' && !/^\.zova-build-[a-z0-9][\w-]*$/i.test(value)) {
199
+ throw new Error('Isolated Zova builds require an explicit .zova-build-<ID> runtime directory');
200
+ }
201
+ if (!value || value === '.' || value === '..') {
202
+ throw new Error('Zova runtime directory must be a non-empty relative path');
203
+ }
204
+ // path.isAbsolute only recognizes the host platform. Reject foreign Win32
205
+ // absolute/drive-relative forms as well so the same input is safe on either
206
+ // platform.
207
+ if (path.isAbsolute(value) || path.posix.isAbsolute(value) || path.win32.isAbsolute(value) || /^[a-z]:/i.test(value) || value.startsWith('\\\\')) {
208
+ throw new Error('Zova runtime directory must be relative to the application directory');
209
+ }
210
+ const normalized = value.replaceAll('\\', '/');
211
+ const parts = normalized.split('/');
212
+ if (parts.some(part => part === '..')) {
213
+ throw new Error('Zova runtime directory must remain inside the application directory');
214
+ }
215
+ const cleaned = parts.filter(part => part !== '.').join('/');
216
+ // Generated config/app imports use ../src and ../../src. Keep the runtime
217
+ // beside src rather than accepting nested paths whose imports would be wrong.
218
+ if (!/^[\w.-]+$/.test(cleaned) || cleaned === '.' || cleaned === '..') {
219
+ throw new Error('Zova runtime directory must be one local directory name beside src');
220
+ }
221
+ return cleaned;
222
+ }
223
+
224
+ /** Validate a runner-owned sibling directory before creating or removing it. */
225
+ function resolveZovaLocalPath(appDir, dirName, prefix) {
226
+ if (!/^[\w.-]+$/.test(dirName) || dirName === '.' || dirName === '..' || prefix !== undefined && (!dirName.startsWith(prefix) || !/^[a-z0-9][\w-]*$/i.test(dirName.slice(prefix.length)))) {
227
+ throw new Error('Zova local directory must be a validated sibling directory name');
228
+ }
229
+ const localPath = path.resolve(appDir, dirName);
230
+ try {
231
+ const stat = lstatSync(localPath);
232
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
233
+ throw new Error('Zova local directory must not be a symlink or non-directory');
234
+ }
235
+ } catch (error) {
236
+ if (error.code !== 'ENOENT') throw error;
237
+ }
238
+ return localPath;
239
+ }
240
+ function resolveZovaViteContext(options) {
241
+ const appDir = path.resolve(options.appDir);
242
+ const runtimeDir = resolveZovaRuntimeDir(options.runtimeDir);
243
+ const runtimePath = resolveZovaLocalPath(appDir, runtimeDir);
244
+ return {
245
+ appDir,
246
+ runtimeDir,
247
+ runtimePath,
248
+ isolated: process.env[ZOVA_ISOLATED_ENV] === 'true'
249
+ };
250
+ }
251
+ function stripIdQuery(id) {
252
+ return id.split(/[?#]/, 1)[0];
253
+ }
254
+ function slash(value) {
255
+ return value.replaceAll('\\', '/');
256
+ }
257
+ function isWithin(root, target) {
258
+ const relative = path.relative(root, target);
259
+ return relative === '' || !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative);
260
+ }
261
+
262
+ /**
263
+ * Create the resolver used by isolated builds. Existing source imports point at
264
+ * `.zova`; isolated builds generate the same files under ZOVA_RUNTIME_DIR.
265
+ * Delegating the mapped absolute id back to Vite preserves Vite's normal
266
+ * extension handling (including its `.js` to `.ts` fallback).
267
+ */
268
+ function createZovaRuntimeResolverPlugin(configOptions) {
269
+ if (process.env[ZOVA_ISOLATED_ENV] !== 'true') return undefined;
270
+ const context = resolveZovaViteContext(configOptions);
271
+ const legacyRuntimePath = path.join(context.appDir, ZOVA_DEFAULT_RUNTIME_DIR);
272
+ const legacyRuntimePrefix = `${slash(legacyRuntimePath)}/`;
273
+ return {
274
+ name: 'zova-runtime-resolver',
275
+ enforce: 'pre',
276
+ async resolveId(source, importer, resolveOptions) {
277
+ const sourceFile = stripIdQuery(source);
278
+ let sourcePath;
279
+ if (sourceFile.startsWith('app/.zova/')) {
280
+ sourcePath = path.join(context.appDir, sourceFile.slice('app/'.length));
281
+ } else if (path.isAbsolute(sourceFile)) {
282
+ sourcePath = path.resolve(sourceFile);
283
+ } else if (sourceFile.startsWith('.')) {
284
+ if (!importer || importer.startsWith('\0')) return null;
285
+ const importerPath = path.resolve(stripIdQuery(importer));
286
+ if (!isWithin(context.appDir, importerPath)) return null;
287
+ sourcePath = path.resolve(path.dirname(importerPath), sourceFile);
288
+ } else {
289
+ return null;
290
+ }
291
+ const normalizedSourcePath = slash(sourcePath);
292
+ if (!normalizedSourcePath.startsWith(legacyRuntimePrefix)) return null;
293
+ const relative = path.relative(legacyRuntimePath, sourcePath);
294
+ const mapped = slash(path.join(context.runtimePath, relative)) + source.slice(sourceFile.length);
295
+ const resolved = await this.resolve(mapped, importer, {
296
+ ...resolveOptions,
297
+ skipSelf: true
298
+ });
299
+ if (!resolved) {
300
+ this.error(`Unable to resolve isolated Zova runtime import: ${source}`);
301
+ }
302
+ return resolved;
303
+ }
304
+ };
305
+ }
306
+
186
307
  // const __ModuleLibs = [
187
308
  // /src\/module\/([^/]*)\//,
188
309
  // /src\/module-vendor\/([^/]*)\//,
@@ -214,6 +335,9 @@ const __ZovaManualChunkVendorsSys = [{
214
335
  output: 'faker'
215
336
  }];
216
337
  function createConfigUtils(configMeta, configOptions) {
338
+ const {
339
+ runtimeDir
340
+ } = resolveZovaViteContext(configOptions);
217
341
  let __modulesMeta;
218
342
  return {
219
343
  loadEnvs: __loadEnvs,
@@ -356,7 +480,7 @@ function createConfigUtils(configMeta, configOptions) {
356
480
  const groups = [];
357
481
  if (process.env.BUILD_MINIFY === 'false') {
358
482
  groups.push({
359
- test: /\.zova\/config\.ts/,
483
+ test: new RegExp(`(?:^|/)${runtimeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/config\\.ts(?:$|[?#])`),
360
484
  name: '-zova-config'
361
485
  });
362
486
  }
@@ -436,22 +560,10 @@ function createConfigUtils(configMeta, configOptions) {
436
560
  // }
437
561
  }
438
562
 
439
- function collectTeleports(html, teleports) {
440
- return html.replace(/(\n|\r\n)\s*<!--app-teleports-->/, renderTeleports(teleports));
441
- }
442
- function renderTeleports(teleports) {
443
- if (!teleports) return '';
444
- return Object.entries(teleports).reduce((all, [key, value]) => {
445
- // This serializer creates dedicated containers, not arbitrary CSS selector targets.
446
- const target = /^#[a-z_][\w-]*/i.exec(key);
447
- if (!target || target[0] !== key) {
448
- throw new Error(`Unsupported SSR Teleport target ${JSON.stringify(key)}. Only simple #id targets are supported (ASCII letter or underscore first, then letters, digits, underscores, or hyphens). Use a dedicated Teleport container, not body.`);
449
- }
450
- return `${all}<div id="${key.slice(1)}">${value}</div>`;
451
- }, '');
452
- }
453
-
454
563
  async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
564
+ const {
565
+ runtimeDir
566
+ } = resolveZovaViteContext(configOptions);
455
567
  // config
456
568
  await __generateConfig();
457
569
  // modules meta
@@ -493,13 +605,13 @@ async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
493
605
  }
494
606
  const contentDest = `${imports.join('\n')}\nexport default [${constNames.join(', ')}];`;
495
607
  // output
496
- const fileDest = path.join(configOptions.appDir, configOptions.runtimeDir, 'config.ts');
608
+ const fileDest = path.join(configOptions.appDir, runtimeDir, 'config.ts');
497
609
  fse.ensureFileSync(fileDest);
498
610
  fse.writeFileSync(fileDest, contentDest, 'utf-8');
499
611
  }
500
612
  async function __generateAppComponent() {
501
613
  // dest
502
- const pathDest = path.join(configOptions.appDir, configOptions.runtimeDir, 'app');
614
+ const pathDest = path.join(configOptions.appDir, runtimeDir, 'app');
503
615
  fse.ensureDirSync(pathDest);
504
616
  // vars
505
617
  const vars = {
@@ -523,8 +635,8 @@ async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
523
635
  const moduleNames = modulesArray.map(item => item.info.relativeName);
524
636
  // src
525
637
  const fileSrc = resolveTemplatePath('zova-modules-meta.ejs');
526
- const fileDest = path.join(configOptions.appDir, configOptions.runtimeDir, 'modules-meta.ts');
527
- await fse.ensureDir(path.join(configOptions.appDir, configOptions.runtimeDir));
638
+ const fileDest = path.join(configOptions.appDir, runtimeDir, 'modules-meta.ts');
639
+ await fse.ensureDir(path.join(configOptions.appDir, runtimeDir));
528
640
  await copyTemplateFile(fileSrc, fileDest, {
529
641
  modules,
530
642
  moduleNames
@@ -543,7 +655,7 @@ async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
543
655
  }
544
656
  // src
545
657
  const fileSrc = resolveTemplatePath('env.ejs');
546
- const fileDest = path.join(configOptions.appDir, configOptions.runtimeDir, 'env.ts');
658
+ const fileDest = path.join(configOptions.appDir, runtimeDir, 'env.ts');
547
659
  await copyTemplateFile(fileSrc, fileDest, {
548
660
  envNormal: JSON.stringify(envNormal, null, 2),
549
661
  envSecret: JSON.stringify(envSecret, null, 2)
@@ -588,6 +700,21 @@ async function generateEntryFiles(configMeta, configOptions, modulesMeta, env) {
588
700
  // }
589
701
  }
590
702
 
703
+ function collectTeleports(html, teleports) {
704
+ return html.replace(/(\n|\r\n)\s*<!--app-teleports-->/, renderTeleports(teleports));
705
+ }
706
+ function renderTeleports(teleports) {
707
+ if (!teleports) return '';
708
+ return Object.entries(teleports).reduce((all, [key, value]) => {
709
+ // This serializer creates dedicated containers, not arbitrary CSS selector targets.
710
+ const target = /^#[a-z_][\w-]*/i.exec(key);
711
+ if (!target || target[0] !== key) {
712
+ throw new Error(`Unsupported SSR Teleport target ${JSON.stringify(key)}. Only simple #id targets are supported (ASCII letter or underscore first, then letters, digits, underscores, or hyphens). Use a dedicated Teleport container, not body.`);
713
+ }
714
+ return `${all}<div id="${key.slice(1)}">${value}</div>`;
715
+ }, '');
716
+ }
717
+
591
718
  // eslint-disable-next-line
592
719
  const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
593
720
  const isCSSRequest = request => CSS_LANGS_RE.test(request);
@@ -636,6 +763,9 @@ function hmrPlugin(_config) {
636
763
 
637
764
  function generateVitePlugins(configOptions, modulesMeta) {
638
765
  const vitePlugins = [];
766
+ if (process.env.ZOVA_ISOLATED === 'true') {
767
+ vitePlugins.push(['zova-runtime-resolver', createZovaRuntimeResolverPlugin, configOptions, undefined]);
768
+ }
639
769
  vitePlugins.push(__getVitePluginTs());
640
770
  vitePlugins.push(__getVitePluginTsx());
641
771
  if (process.env.MOCK_ENABLED === 'true') {
@@ -662,7 +792,8 @@ function generateVitePlugins(configOptions, modulesMeta) {
662
792
  babelrc: false,
663
793
  configFile: false,
664
794
  plugins: [[babelPluginZovaBeanModule, {
665
- brandName: 'zova'
795
+ brandName: 'zova',
796
+ runtimeDir: resolveZovaRuntimeDir(configOptions.runtimeDir)
666
797
  }], [babelPluginZovaBeanUse], [babelPluginTransformTypescriptMetadata], [babelPluginProposalDecorators, {
667
798
  version: 'legacy'
668
799
  }], [babelPluginTransformClassProperties, {
@@ -683,7 +814,8 @@ function generateVitePlugins(configOptions, modulesMeta) {
683
814
  return ['@vitejs/plugin-vue-jsx', vueJsxPlugin, {
684
815
  include: /\.[jt]sx$/,
685
816
  babelPlugins: [[babelPluginZovaComponent], [babelPluginZovaBehavior], [babelPluginZovaBeanModule, {
686
- brandName: 'zova'
817
+ brandName: 'zova',
818
+ runtimeDir: resolveZovaRuntimeDir(configOptions.runtimeDir)
687
819
  }], [babelPluginZovaBeanUse], [babelPluginTransformTypescriptMetadata], [babelPluginProposalDecorators, {
688
820
  version: 'legacy'
689
821
  }], [babelPluginTransformClassProperties, {
@@ -856,4 +988,4 @@ async function generateZovaViteMeta(configMeta, configOptions) {
856
988
  }
857
989
  }
858
990
 
859
- export { SymbolCssResult, collectCss, collectTeleports, copyTemplateFile, copyTemplateIfNeed, createConfigUtils, generateConfigDefine, generateZovaViteMeta, getAbsolutePathOfModule, getAppMode, getEnvFromCli, getEnvMeta, getFlavor, getOutDir, getOutReleasesDir, loadJSONFile, pathToHref, removeCssHotReloaded, renderTeleports, requireModule, resolveTemplatePath, saveJSONFile };
991
+ export { SymbolCssResult, ZOVA_DEFAULT_RUNTIME_DIR, ZOVA_ISOLATED_ENV, ZOVA_RUNTIME_DIR_ENV, collectCss, collectTeleports, copyTemplateFile, copyTemplateIfNeed, createConfigUtils, createZovaRuntimeResolverPlugin, generateConfigDefine, generateEntryFiles, generateZovaViteMeta, getAbsolutePathOfModule, getAppMode, getEnvFromCli, getEnvMeta, getFlavor, getOutDir, getOutReleasesDir, loadJSONFile, pathToHref, removeCssHotReloaded, renderTeleports, requireModule, resolveTemplatePath, resolveZovaLocalPath, resolveZovaRuntimeDir, resolveZovaViteContext, saveJSONFile };
package/dist/types.d.ts CHANGED
@@ -4,12 +4,19 @@ export interface ZovaViteConfigChunkVendor {
4
4
  }
5
5
  export interface ZovaViteConfigOptions {
6
6
  appDir: string;
7
- runtimeDir: string;
7
+ /** Generated runtime directory, relative to appDir. Defaults to `.zova`. */
8
+ runtimeDir?: string;
8
9
  zovaManualChunk?: {
9
10
  debug: boolean;
10
11
  vendors: ZovaViteConfigChunkVendor[];
11
12
  };
12
13
  }
14
+ export interface ZovaViteContext {
15
+ appDir: string;
16
+ runtimeDir: string;
17
+ runtimePath: string;
18
+ isolated: boolean;
19
+ }
13
20
  export interface ZovaViteConfigResult {
14
21
  env: {
15
22
  [name: string]: string;
@@ -0,0 +1,21 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { ZovaViteConfigOptions, ZovaViteContext } from './types.ts';
3
+ export declare const ZOVA_RUNTIME_DIR_ENV = "ZOVA_RUNTIME_DIR";
4
+ export declare const ZOVA_ISOLATED_ENV = "ZOVA_ISOLATED";
5
+ export declare const ZOVA_DEFAULT_RUNTIME_DIR = ".zova";
6
+ /**
7
+ * Resolve the generated-runtime directory without allowing it to escape the
8
+ * application directory. The returned path always uses `/`, which is also the
9
+ * separator used by Vite module ids.
10
+ */
11
+ export declare function resolveZovaRuntimeDir(runtimeDir?: string): string;
12
+ /** Validate a runner-owned sibling directory before creating or removing it. */
13
+ export declare function resolveZovaLocalPath(appDir: string, dirName: string, prefix?: string): string;
14
+ export declare function resolveZovaViteContext(options: ZovaViteConfigOptions): ZovaViteContext;
15
+ /**
16
+ * Create the resolver used by isolated builds. Existing source imports point at
17
+ * `.zova`; isolated builds generate the same files under ZOVA_RUNTIME_DIR.
18
+ * Delegating the mapped absolute id back to Vite preserves Vite's normal
19
+ * extension handling (including its `.js` to `.ts` fallback).
20
+ */
21
+ export declare function createZovaRuntimeResolverPlugin(configOptions: ZovaViteConfigOptions): Plugin | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zova-vite",
3
- "version": "1.1.53",
4
- "gitHead": "b0a4dbbda3b804afdfacf0792ad37a6e45724349",
3
+ "version": "1.1.54",
4
+ "gitHead": "c94b659a5ab1819ad7714ddacb6875c4f8cbc022",
5
5
  "description": "zova vite",
6
6
  "keywords": [
7
7
  "framework",
@@ -51,7 +51,7 @@
51
51
  "@types/node": "^22.19.17",
52
52
  "@vitejs/plugin-vue-jsx": "^5.1.5",
53
53
  "babel-plugin-transform-typescript-metadata": "^0.4.0",
54
- "babel-plugin-zova-bean-module": "^1.2.15",
54
+ "babel-plugin-zova-bean-module": "^1.2.16",
55
55
  "babel-plugin-zova-bean-use": "^1.1.15",
56
56
  "babel-plugin-zova-behavior": "^1.1.16",
57
57
  "babel-plugin-zova-component": "^1.1.15",