cabloy 5.1.56 → 5.1.58

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 (42) hide show
  1. package/.gitignore +1 -0
  2. package/CHANGELOG.md +18 -0
  3. package/cabloy-docs/.vitepress/config.mjs +1 -0
  4. package/cabloy-docs/backend/dto-guide.md +7 -0
  5. package/cabloy-docs/backend/orm-select-guide.md +2 -0
  6. package/cabloy-docs/backend/validation-guide.md +8 -0
  7. package/cabloy-docs/fullstack/comparison-with-other-frameworks.md +1 -0
  8. package/cabloy-docs/fullstack/framework-performance.md +89 -0
  9. package/cabloy-docs/fullstack/introduction.md +1 -0
  10. package/cabloy-docs/fullstack/quickstart.md +19 -3
  11. package/cabloy-docs/fullstack/vona-zova-integration.md +2 -0
  12. package/cabloy-docs/index.md +8 -0
  13. package/package.json +1 -1
  14. package/scripts/init.ts +46 -0
  15. package/scripts/upgrade.ts +139 -27
  16. package/vona/packages-cli/cli/package.json +1 -1
  17. package/vona/packages-cli/cli-set-api/package.json +1 -1
  18. package/vona/packages-utils/zod-query/package.json +1 -1
  19. package/vona/packages-utils/zod-query/src/index.ts +1 -0
  20. package/vona/packages-utils/zod-query/src/lib/query.ts +31 -12
  21. package/vona/packages-utils/zod-query/src/lib/zod-is-type.ts +48 -6
  22. package/vona/packages-vona/vona/package.json +1 -1
  23. package/vona/packages-vona/vona-core/package.json +1 -1
  24. package/vona/packages-vona/vona-mock/package.json +1 -1
  25. package/vona/pnpm-lock.yaml +482 -477
  26. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  27. package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
  28. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
  29. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/schema/v/system.ts +6 -0
  30. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/schema/v.ts +2 -0
  31. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  32. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/filterTransform.base.ts +6 -0
  33. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/filterTransform.dateRange.ts +1 -0
  34. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +5 -5
  35. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  36. package/zova/packages-utils/zova-jsx/package.json +2 -2
  37. package/zova/packages-zova/zova/package.json +3 -3
  38. package/zova/packages-zova/zova-core/package.json +2 -2
  39. package/zova/packages-zova/zova-core/src/types/interface/component.ts +2 -2
  40. package/zova/src/suite-vendor/a-zova/modules/a-zod/package.json +2 -2
  41. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  42. package/zova/src/suite-vendor/a-zova/package.json +3 -3
package/.gitignore CHANGED
@@ -62,6 +62,7 @@ zova/.quasar
62
62
  **/.vitepress/cache
63
63
  **/.rollup.cache
64
64
  **/.temp-dynamic-*
65
+ /.cabloy-version
65
66
  vona/package.json
66
67
  zova/package.json
67
68
  vona/docker-compose.yml
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.58
4
+
5
+ ### Features
6
+
7
+ - Support nullable query filters.
8
+ - Deliver several feature updates across the library.
9
+
10
+ ### Improvements
11
+
12
+ - Add a Docker Compose quickstart flow to the documentation.
13
+ - Refresh the framework performance documentation.
14
+
15
+ ## 5.1.57
16
+
17
+ ### Features
18
+
19
+ - Track the Cabloy version during initialization and upgrade.
20
+
3
21
  ## 5.1.56
4
22
 
5
23
  ### Features
@@ -53,6 +53,7 @@ const fullstackGroups = [
53
53
  text: 'Comparison with Other Frameworks',
54
54
  link: '/fullstack/comparison-with-other-frameworks',
55
55
  },
56
+ { text: 'Framework Performance', link: '/fullstack/framework-performance' },
56
57
  { text: 'Vona + Zova Integration', link: '/fullstack/vona-zova-integration' },
57
58
  { text: 'Backend OpenAPI to Frontend SDK', link: '/fullstack/openapi-to-sdk' },
58
59
  {
@@ -73,6 +73,13 @@ class DtoStudentCreate {
73
73
  }
74
74
  ```
75
75
 
76
+ For query-oriented DTOs, another important distinction is optional vs nullable:
77
+
78
+ - `v.optional()` means the field may be omitted
79
+ - `v.nullable()` means the field may carry a real `null`
80
+
81
+ That becomes especially important for DTO query filters. A field declared with `v.optional(), v.nullable()` can preserve real `null` through query parsing so the downstream ORM filter can express SQL `IS NULL` instead of silently treating the value as omitted.
82
+
76
83
  ## DTO options
77
84
 
78
85
  Three especially important DTO option areas are:
@@ -146,6 +146,8 @@ await this.scope.model.post.select({
146
146
 
147
147
  This lets a query builder remove one condition cleanly without rebuilding the whole `where` object by hand, while `null` remains available for SQL `IS NULL` semantics.
148
148
 
149
+ That distinction also matters at the request-contract layer: omitting a nullable filter and passing a real `null` are different operations. When a query DTO needs `IS NULL` behavior from frontend query params, the parsing contract must explicitly preserve `null` instead of collapsing it to omission.
150
+
149
151
  ## Joint operators and subqueries
150
152
 
151
153
  The query language also supports joint operators such as:
@@ -37,12 +37,20 @@ findOne(@Arg.query('id', z.number().min(6)) id: number) {}
37
37
  Inferred schemas can also be extended through helper tools like:
38
38
 
39
39
  - `v.optional`
40
+ - `v.nullable`
40
41
  - `v.default`
41
42
  - `v.array`
42
43
  - `v.lazy`
43
44
 
44
45
  That is important because many real validation cases need augmentation rather than total replacement.
45
46
 
47
+ A useful distinction is:
48
+
49
+ - `v.optional` means the field may be omitted
50
+ - `v.nullable` means the field may carry a real `null`
51
+
52
+ For query parameters, that distinction matters because plain optional values still collapse `null` to omission, while `v.optional(), v.nullable()` allows a real `null` to survive parsing when the contract needs SQL `IS NULL` semantics downstream.
53
+
46
54
  ## `@Arg.filter`
47
55
 
48
56
  The validation layer also supports more advanced query-style inputs through `@Arg.filter`, which ties into DTO/query helper structures.
@@ -11,6 +11,7 @@ That means the comparison is not only about a backend runtime or only about a fr
11
11
  For the broader Cabloy model, start with these pages:
12
12
 
13
13
  - [Fullstack Introduction](/fullstack/introduction)
14
+ - [Framework Performance](/fullstack/framework-performance)
14
15
  - [Vona + Zova Integration](/fullstack/vona-zova-integration)
15
16
  - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
16
17
  - [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend)
@@ -0,0 +1,89 @@
1
+ # Framework Performance
2
+
3
+ Cabloy is designed to stay fast where real systems usually become fragile: under growing business complexity.
4
+
5
+ Its performance story is not only about looking fast in simplified demos. It is about keeping real business systems maintainable, expressive, and performant as they grow in size and complexity.
6
+
7
+ This page explains that philosophy first, then shows a concrete runtime example from an internally generated project.
8
+
9
+ ## Philosophy
10
+
11
+ Many frameworks highlight performance with simplified cases, but real systems face a different challenge: as business complexity grows, performance work can easily turn into a growing pile of special-case optimization code.
12
+
13
+ Cabloy takes a different direction.
14
+
15
+ At the framework level, the goal is not only to make one isolated path faster. The goal is to let teams build large-scale systems in a way that stays elegant, maintainable, and performance-aware over time.
16
+
17
+ That philosophy comes from the earlier Vona design direction:
18
+
19
+ - performance should be considered under real business complexity
20
+ - framework support should reduce the amount of ad hoc optimization code that teams need to write themselves
21
+ - maintainability and performance should improve together instead of fighting each other
22
+
23
+ In other words, Cabloy does not treat performance as a last-minute patch. It treats performance-related capability as part of the framework design.
24
+
25
+ ## What supports this philosophy
26
+
27
+ A practical example is **Vona** on the backend side.
28
+
29
+ Vona moves caching into the framework core instead of treating caching as something every project must rebuild from scratch. That includes framework-managed cache behavior such as:
30
+
31
+ - entity cache
32
+ - query cache
33
+ - broader cache-centered workflow support for complex data access paths
34
+
35
+ This matters because large systems usually do not become slow only because one query is inefficient. They become slow because optimization logic gets scattered across the codebase and becomes difficult to keep correct.
36
+
37
+ By making caching first-class, Cabloy helps teams keep performance work closer to the framework model and farther away from repetitive custom optimization code.
38
+
39
+ For the current public explanation of this backend capability, see [Cache Guide](/backend/cache-guide).
40
+
41
+ ## Runtime example
42
+
43
+ This is the kind of result Cabloy is designed to support: a framework that stays operationally calm even when the system keeps running for long periods.
44
+
45
+ One internally generated project was kept running continuously for **33 hours**. At one representative PM2 snapshot, the process looked like this:
46
+
47
+ ```text
48
+ ┌────┬─────────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
49
+ │ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
50
+ ├────┼─────────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
51
+ │ 0 │ cabloy_store │ default │ N/A │ cluster │ 226947 │ 33h │ 17 │ online │ 0% │ 401.9mb │ ubuntu │ disabled │
52
+ └────┴─────────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
53
+ ```
54
+
55
+ For this 33-hour continuous run, the observed result was **0 memory leak**.
56
+
57
+ The value of this example is not that it is a synthetic micro-benchmark. The value is that it reflects a real long-running project process with a clear, inspectable runtime footprint.
58
+
59
+ ## How to read this example
60
+
61
+ This example is meant to show a real runtime stability observation from an internal project.
62
+
63
+ It should not be read as:
64
+
65
+ - a formal benchmark against other frameworks
66
+ - a universal guarantee for every Cabloy workload
67
+ - proof that every deployment will show the same memory profile
68
+
69
+ Actual memory usage still depends on factors such as:
70
+
71
+ - enabled modules and features
72
+ - request traffic patterns
73
+ - data volume
74
+ - deployment topology
75
+ - operational configuration
76
+
77
+ If you want to check your own SSR workload, you can use the `detect-ssr-leak` Claude skill in the Vona workspace to investigate long-running memory behavior and verify whether the system shows leak signals.
78
+
79
+ This is a diagnostic workflow, not a universal guarantee that every deployment is leak-free.
80
+
81
+ So the point of the example is not that one PM2 snapshot explains everything. The point is that Cabloy’s framework design is intended to support long-running, real business systems without forcing teams into constant performance firefighting.
82
+
83
+ ## Where to go next
84
+
85
+ If you want to continue from the performance philosophy into the concrete framework mechanisms behind it, these pages are the best next stops:
86
+
87
+ - [Cache Guide](/backend/cache-guide) — how Vona makes caching first-class in the backend model
88
+ - [Vona + Zova Integration](/fullstack/vona-zova-integration) — how the backend and frontend stay aligned as one framework system
89
+ - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk) — one example of how Cabloy reduces repetitive cross-stack work at scale
@@ -41,6 +41,7 @@ Start here when you want the shortest route to a working monorepo mental model:
41
41
  Use this path when the task is about how backend and frontend stay aligned inside one framework system:
42
42
 
43
43
  - [Comparison with Other Frameworks](/fullstack/comparison-with-other-frameworks)
44
+ - [Framework Performance](/fullstack/framework-performance)
44
45
  - [Vona + Zova Integration](/fullstack/vona-zova-integration)
45
46
  - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
46
47
  - [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend)
@@ -57,13 +57,29 @@ If you are not sure which edition you are using or which one to choose, read:
57
57
  - [Cabloy Basic](/editions/cabloy-basic)
58
58
  - [Cabloy Start](/editions/cabloy-start)
59
59
 
60
- ## 5. Upgrade an existing project
60
+ ## 5. Run with Docker Compose
61
+
62
+ Both Cabloy Basic and Cabloy Start support the same Docker Compose command flow. Run these commands from the repository for the edition you are using:
63
+
64
+ ```bash
65
+ npm run build:docker
66
+ cd docker-compose
67
+ sudo COMPOSE_BAKE=true docker-compose build
68
+ sudo docker-compose up
69
+ ```
70
+
71
+ - Web: http://localhost/
72
+ - Admin: http://localhost/admin/
73
+
74
+ These commands build the edition-specific frontend flavors from the repository you are using.
75
+
76
+ ## 6. Upgrade an existing project
61
77
 
62
78
  ```bash
63
79
  npm run upgrade
64
80
  ```
65
81
 
66
- ## 6. Next steps for framework-aware development
82
+ ## 7. Next steps for framework-aware development
67
83
 
68
84
  If you are contributing to framework-aware workflows or using Cabloy CLI generation directly, prefer CLI-backed generation over manual scaffolding.
69
85
 
@@ -76,7 +92,7 @@ npm run zova :create
76
92
 
77
93
  Then narrow into the specific command family you need.
78
94
 
79
- ## 7. Shared verification commands for deeper workflow checks
95
+ ## 8. Shared verification commands for deeper workflow checks
80
96
 
81
97
  If you are validating framework-aware changes or a broader workflow, use the shared project scripts before declaring a workflow correct:
82
98
 
@@ -23,6 +23,8 @@ Cabloy uses a frontend-backend separation architecture:
23
23
  - generated or built frontend output is consumed by the backend-side fullstack flow
24
24
  - type sharing is coordinated through OpenAPI, generated SDKs, and frontend-generated route/component typing
25
25
 
26
+ For the framework-level performance philosophy behind this fullstack model, see [Framework Performance](/fullstack/framework-performance).
27
+
26
28
  ## Cabloy Basic
27
29
 
28
30
  In Cabloy Basic, the root repository already exposes these shared entrypoints:
@@ -42,6 +42,7 @@ Start here to learn the shared Cabloy architecture, see how Vona and Zova fit to
42
42
  - **Get started quickly** with the fullstack quickstart and core Cabloy concepts
43
43
  - **Learn the shared fullstack architecture** across Cabloy, Vona, and Zova
44
44
  - **Explore backend and frontend workflows** without losing the cross-stack picture
45
+ - **Understand Cabloy’s performance philosophy and runtime stability story** with [Framework Performance](/fullstack/framework-performance)
45
46
  - **See how Cabloy Basic and Cabloy Start differ by edition** when UI assumptions, flavors, modules, SSR sites, or AI workflow guidance matter
46
47
  - **Follow source-grounded AI vibe coding guidance** for prompting, workflow selection, and verification
47
48
 
@@ -63,6 +64,13 @@ Start here to learn the shared Cabloy architecture, see how Vona and Zova fit to
63
64
  5. [Reference Introduction](/reference/introduction)
64
65
  6. [Editions Overview](/editions/overview)
65
66
 
67
+ ### For performance-oriented reading
68
+
69
+ 1. [Fullstack Introduction](/fullstack/introduction)
70
+ 2. [Framework Performance](/fullstack/framework-performance)
71
+ 3. [Cache Guide](/backend/cache-guide)
72
+ 4. [Vona + Zova Integration](/fullstack/vona-zova-integration)
73
+
66
74
  ## Documentation scope labels
67
75
 
68
76
  Use these labels throughout the site:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.56",
3
+ "version": "5.1.58",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
package/scripts/init.ts CHANGED
@@ -19,6 +19,7 @@ const VONA_DIR = resolve(ROOT_DIR, 'vona');
19
19
  const ZOVA_DIR = resolve(ROOT_DIR, 'zova');
20
20
  const CABLOY_DOCS_DIR = resolve(ROOT_DIR, 'cabloy-docs');
21
21
  const PNPM_VERSION = '11.5.2';
22
+ const VERSION_MARKER_FILE = '.cabloy-version';
22
23
 
23
24
  // --- Helpers ---
24
25
 
@@ -70,6 +71,50 @@ function deleteGitkeepFiles(dir: string): void {
70
71
  }
71
72
  }
72
73
 
74
+ function isValidVersion(version: string): boolean {
75
+ return /^\d+\.\d+\.\d+$/.test(version);
76
+ }
77
+
78
+ function readPackageVersion(): string {
79
+ const filePath = resolve(ROOT_DIR, 'package.json');
80
+ const packageContent = JSON.parse(readFileSync(filePath, 'utf-8')) as { version?: string };
81
+ const version = packageContent.version?.trim();
82
+ if (!version || !isValidVersion(version)) {
83
+ throw new Error(`Invalid Cabloy version in package.json: ${version ?? '<missing>'}`);
84
+ }
85
+ return version;
86
+ }
87
+
88
+ function readVersionMarker(): string | undefined {
89
+ const filePath = resolve(ROOT_DIR, VERSION_MARKER_FILE);
90
+ if (!existsSync(filePath)) return undefined;
91
+ const version = readFileSync(filePath, 'utf-8').trim();
92
+ if (!version) return undefined;
93
+ if (!isValidVersion(version)) {
94
+ throw new Error(`Invalid Cabloy version marker: ${version}`);
95
+ }
96
+ return version;
97
+ }
98
+
99
+ function resolveVersionMarker(): string {
100
+ const versionFromEnv = process.env.CABLOY_VERSION?.trim();
101
+ if (versionFromEnv) {
102
+ if (!isValidVersion(versionFromEnv)) {
103
+ throw new Error(`Invalid CABLOY_VERSION: ${versionFromEnv}`);
104
+ }
105
+ return versionFromEnv;
106
+ }
107
+ return readVersionMarker() ?? readPackageVersion();
108
+ }
109
+
110
+ function writeVersionMarker(): void {
111
+ const version = resolveVersionMarker();
112
+ const filePath = resolve(ROOT_DIR, VERSION_MARKER_FILE);
113
+ writeFileSync(filePath, `${version}\n`);
114
+ // eslint-disable-next-line
115
+ console.log(`[init] Marked Cabloy version: ${version}`);
116
+ }
117
+
73
118
  // --- Step 0: Set APP_NAME in .env files ---
74
119
 
75
120
  function setAppName(): void {
@@ -245,5 +290,6 @@ initVona();
245
290
  initZova();
246
291
  initCabloyDocs();
247
292
  buildSsrCabloyBasicStartBatch();
293
+ writeVersionMarker();
248
294
  // eslint-disable-next-line
249
295
  console.log('[init] Done!');
@@ -1,5 +1,13 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { cpSync, createWriteStream, existsSync, mkdirSync, rmSync, unlinkSync } from 'node:fs';
2
+ import {
3
+ cpSync,
4
+ createWriteStream,
5
+ existsSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ rmSync,
9
+ unlinkSync,
10
+ } from 'node:fs';
3
11
  import { tmpdir } from 'node:os';
4
12
  import { dirname, join, resolve } from 'node:path';
5
13
  import { pipeline } from 'node:stream/promises';
@@ -11,6 +19,7 @@ const ROOT_DIR = resolve(__dirname, '..');
11
19
  const NPM_REGISTRY = 'https://registry.npmjs.org';
12
20
  const PACKAGE_NAME = 'cabloy';
13
21
  const TEMP_DIR = resolve(ROOT_DIR, 'node_modules/.cabloy-upgrade');
22
+ const VERSION_MARKER_FILE = '.cabloy-version';
14
23
 
15
24
  // --- Whitelist ---
16
25
 
@@ -78,13 +87,22 @@ const WHITELIST_FILES: string[] = [
78
87
  'zova/openapi.config.ts',
79
88
  ];
80
89
 
90
+ type LatestPackageInfo = {
91
+ version: string;
92
+ tarballUrl: string;
93
+ };
94
+
81
95
  // --- Helpers ---
82
96
 
83
97
  // oxlint-disable no-console
84
98
  const log = console.log; // eslint-disable-line no-console
85
99
 
86
- function exec(cmd: string): void {
87
- execSync(cmd, { stdio: 'inherit', cwd: ROOT_DIR });
100
+ function exec(cmd: string, env?: NodeJS.ProcessEnv): void {
101
+ execSync(cmd, {
102
+ stdio: 'inherit',
103
+ cwd: ROOT_DIR,
104
+ env: env ? { ...process.env, ...env } : process.env,
105
+ });
88
106
  }
89
107
 
90
108
  function shouldCopyPath(path: string): boolean {
@@ -95,6 +113,54 @@ function copyDirectory(src: string, dest: string): void {
95
113
  cpSync(src, dest, { recursive: true, filter: shouldCopyPath });
96
114
  }
97
115
 
116
+ function isValidVersion(version: string): boolean {
117
+ return /^\d+\.\d+\.\d+$/.test(version);
118
+ }
119
+
120
+ function parseVersion(version: string): [number, number, number] {
121
+ if (!isValidVersion(version)) {
122
+ throw new Error(`Invalid Cabloy version: ${version}`);
123
+ }
124
+ const [major, minor, patch] = version.split('.').map(item => Number.parseInt(item, 10));
125
+ return [major, minor, patch];
126
+ }
127
+
128
+ function compareVersions(left: string, right: string): number {
129
+ const leftParts = parseVersion(left);
130
+ const rightParts = parseVersion(right);
131
+ for (let i = 0; i < leftParts.length; i++) {
132
+ if (leftParts[i] > rightParts[i]) return 1;
133
+ if (leftParts[i] < rightParts[i]) return -1;
134
+ }
135
+ return 0;
136
+ }
137
+
138
+ function readVersionMarker(): string | undefined {
139
+ const filePath = resolve(ROOT_DIR, VERSION_MARKER_FILE);
140
+ if (!existsSync(filePath)) return undefined;
141
+ const version = readFileSync(filePath, 'utf-8').trim();
142
+ if (!version) {
143
+ throw new Error(`Invalid Cabloy version marker in ${VERSION_MARKER_FILE}: <empty>`);
144
+ }
145
+ if (!isValidVersion(version)) {
146
+ throw new Error(`Invalid Cabloy version marker in ${VERSION_MARKER_FILE}: ${version}`);
147
+ }
148
+ return version;
149
+ }
150
+
151
+ function readRequiredVersionMarker(expectedVersion: string): string {
152
+ const version = readVersionMarker();
153
+ if (!version) {
154
+ throw new Error(`Expected ${VERSION_MARKER_FILE} to be created after upgrade`);
155
+ }
156
+ if (version !== expectedVersion) {
157
+ throw new Error(
158
+ `Expected ${VERSION_MARKER_FILE} to be ${expectedVersion} after upgrade, received ${version}`,
159
+ );
160
+ }
161
+ return version;
162
+ }
163
+
98
164
  async function extractTarball(tarballPath: string, targetDir: string): Promise<void> {
99
165
  rmSync(targetDir, { recursive: true, force: true });
100
166
  mkdirSync(targetDir, { recursive: true });
@@ -120,14 +186,22 @@ function preflight(): void {
120
186
 
121
187
  // --- Step 2: Download & extract ---
122
188
 
123
- async function fetchLatestTarballUrl(): Promise<string> {
189
+ async function fetchLatestPackageInfo(): Promise<LatestPackageInfo> {
124
190
  const url = `${NPM_REGISTRY}/${PACKAGE_NAME}/latest`;
125
191
  const res = await fetch(url);
126
192
  if (!res.ok) {
127
193
  throw new Error(`Failed to fetch package info: ${res.status} ${res.statusText}`);
128
194
  }
129
- const data = (await res.json()) as { dist: { tarball: string } };
130
- return data.dist.tarball;
195
+ const data = (await res.json()) as { version?: string; dist?: { tarball?: string } };
196
+ const version = data.version?.trim();
197
+ const tarballUrl = data.dist?.tarball?.trim();
198
+ if (!version || !isValidVersion(version)) {
199
+ throw new Error(`Invalid latest Cabloy version from npm: ${version ?? '<missing>'}`);
200
+ }
201
+ if (!tarballUrl) {
202
+ throw new Error('Missing tarball URL in npm latest metadata');
203
+ }
204
+ return { version, tarballUrl };
131
205
  }
132
206
 
133
207
  async function downloadTarball(tarballUrl: string): Promise<string> {
@@ -145,8 +219,7 @@ async function downloadTarball(tarballUrl: string): Promise<string> {
145
219
  return tmpFile;
146
220
  }
147
221
 
148
- async function downloadAndExtract(): Promise<void> {
149
- const tarballUrl = await fetchLatestTarballUrl();
222
+ async function downloadAndExtract(tarballUrl: string): Promise<void> {
150
223
  const tarballPath = await downloadTarball(tarballUrl);
151
224
  try {
152
225
  await extractTarball(tarballPath, TEMP_DIR);
@@ -190,9 +263,12 @@ function selectiveOverwrite(dryRun?: boolean): void {
190
263
  // Delete directories
191
264
  for (const dir of BLACKLIST_DIRS) {
192
265
  const dest = resolve(ROOT_DIR, dir);
193
- if (existsSync(dest)) {
194
- rmSync(dest, { recursive: true, force: true });
266
+ if (!existsSync(dest)) continue;
267
+ if (dryRun) {
268
+ log(` [dry-run] Delete directory: ${dir}`);
269
+ continue;
195
270
  }
271
+ rmSync(dest, { recursive: true, force: true });
196
272
  }
197
273
 
198
274
  // Overwrite files
@@ -210,12 +286,12 @@ function selectiveOverwrite(dryRun?: boolean): void {
210
286
 
211
287
  // --- Step 4: Run init ---
212
288
 
213
- function runInit(dryRun?: boolean): void {
289
+ function runInit(dryRun: boolean, version: string): void {
214
290
  if (dryRun) {
215
291
  log(' [dry-run] Run: npm run init');
216
292
  return;
217
293
  }
218
- exec('npm run init');
294
+ exec('npm run init', { CABLOY_VERSION: version } as any);
219
295
  }
220
296
 
221
297
  // --- Step 5: Cleanup ---
@@ -240,25 +316,61 @@ async function main(): Promise<void> {
240
316
  // 1. Pre-flight
241
317
  preflight();
242
318
 
243
- // 2. Download & extract
244
- log('Downloading latest cabloy from npm registry...');
245
- await downloadAndExtract();
246
- log('Downloaded and extracted successfully!\n');
319
+ const latestPackageInfo = await fetchLatestPackageInfo();
320
+ const currentVersion = readVersionMarker();
321
+
322
+ if (currentVersion) {
323
+ log(`Current project Cabloy version: ${currentVersion}`);
324
+ } else {
325
+ log(
326
+ `Warning: Missing ${VERSION_MARKER_FILE}, continuing with upgrade for backward compatibility.`,
327
+ );
328
+ }
329
+ log(`Latest Cabloy version: ${latestPackageInfo.version}`);
330
+
331
+ if (currentVersion) {
332
+ const comparison = compareVersions(currentVersion, latestPackageInfo.version);
333
+ if (comparison === 0) {
334
+ log(`Cabloy is already up to date (current: ${currentVersion}). Skipping upgrade.`);
335
+ return;
336
+ }
337
+ if (comparison > 0) {
338
+ throw new Error(
339
+ `Project Cabloy version ${currentVersion} is newer than npm latest ${latestPackageInfo.version}. Aborting upgrade.`,
340
+ );
341
+ }
342
+ log(`Upgrading Cabloy from ${currentVersion} to ${latestPackageInfo.version}...\n`);
343
+ } else {
344
+ log(`Upgrading Cabloy to ${latestPackageInfo.version}...\n`);
345
+ }
247
346
 
248
- // 3. Selective overwrite
249
- log('Overwriting framework-owned files...');
250
- selectiveOverwrite(dryRun);
251
- log('');
347
+ try {
348
+ // 2. Download & extract
349
+ log('Downloading latest cabloy from npm registry...');
350
+ await downloadAndExtract(latestPackageInfo.tarballUrl);
351
+ log('Downloaded and extracted successfully!\n');
252
352
 
253
- // 4. Run init
254
- log('Running npm run init...');
255
- runInit(dryRun);
256
- log('');
353
+ // 3. Selective overwrite
354
+ log('Overwriting framework-owned files...');
355
+ selectiveOverwrite(dryRun);
356
+ log('');
257
357
 
258
- // 5. Cleanup
259
- cleanup(dryRun);
358
+ // 4. Run init
359
+ log('Running npm run init...');
360
+ runInit(dryRun, latestPackageInfo.version);
361
+ log('');
260
362
 
261
- log('Upgrade complete!');
363
+ if (dryRun) {
364
+ log(`[dry-run] Current Cabloy version would become: ${latestPackageInfo.version}`);
365
+ } else {
366
+ log(`Current Cabloy version: ${readRequiredVersionMarker(latestPackageInfo.version)}`);
367
+ }
368
+
369
+ log('Upgrade complete!');
370
+ } finally {
371
+ // 5. Cleanup
372
+ cleanup(dryRun);
373
+ }
262
374
  }
263
375
 
264
376
  main().catch(err => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.108",
3
+ "version": "1.1.109",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.106",
3
+ "version": "1.1.107",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabloy/zod-query",
3
- "version": "2.1.8",
3
+ "version": "2.1.9",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "zod-query",
6
6
  "keywords": [
@@ -1 +1,2 @@
1
1
  export * from './lib/query.ts';
2
+ export * from './lib/zod-is-type.ts';