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.
- package/.gitignore +1 -0
- package/CHANGELOG.md +18 -0
- package/cabloy-docs/.vitepress/config.mjs +1 -0
- package/cabloy-docs/backend/dto-guide.md +7 -0
- package/cabloy-docs/backend/orm-select-guide.md +2 -0
- package/cabloy-docs/backend/validation-guide.md +8 -0
- package/cabloy-docs/fullstack/comparison-with-other-frameworks.md +1 -0
- package/cabloy-docs/fullstack/framework-performance.md +89 -0
- package/cabloy-docs/fullstack/introduction.md +1 -0
- package/cabloy-docs/fullstack/quickstart.md +19 -3
- package/cabloy-docs/fullstack/vona-zova-integration.md +2 -0
- package/cabloy-docs/index.md +8 -0
- package/package.json +1 -1
- package/scripts/init.ts +46 -0
- package/scripts/upgrade.ts +139 -27
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-utils/zod-query/package.json +1 -1
- package/vona/packages-utils/zod-query/src/index.ts +1 -0
- package/vona/packages-utils/zod-query/src/lib/query.ts +31 -12
- package/vona/packages-utils/zod-query/src/lib/zod-is-type.ts +48 -6
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +482 -477
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/schema/v/system.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/schema/v.ts +2 -0
- package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/filterTransform.base.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/filterTransform.dateRange.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +5 -5
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +2 -2
- package/zova/packages-zova/zova-core/src/types/interface/component.ts +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-zod/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
package/.gitignore
CHANGED
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.
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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:
|
package/cabloy-docs/index.md
CHANGED
|
@@ -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
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!');
|
package/scripts/upgrade.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
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, {
|
|
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
|
|
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
|
|
130
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
353
|
+
// 3. Selective overwrite
|
|
354
|
+
log('Overwriting framework-owned files...');
|
|
355
|
+
selectiveOverwrite(dryRun);
|
|
356
|
+
log('');
|
|
257
357
|
|
|
258
|
-
|
|
259
|
-
|
|
358
|
+
// 4. Run init
|
|
359
|
+
log('Running npm run init...');
|
|
360
|
+
runInit(dryRun, latestPackageInfo.version);
|
|
361
|
+
log('');
|
|
260
362
|
|
|
261
|
-
|
|
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 => {
|