@zenithbuild/cli 0.6.5 → 0.6.7
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/build.d.ts +32 -0
- package/dist/build.js +193 -548
- package/dist/compiler-bridge-runner.d.ts +5 -0
- package/dist/compiler-bridge-runner.js +70 -0
- package/dist/component-instance-ir.d.ts +6 -0
- package/dist/component-instance-ir.js +0 -20
- package/dist/component-occurrences.d.ts +6 -0
- package/dist/component-occurrences.js +6 -28
- package/dist/dev-server.d.ts +18 -0
- package/dist/dev-server.js +76 -116
- package/dist/dev-watch.d.ts +1 -0
- package/dist/dev-watch.js +19 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -28
- package/dist/manifest.d.ts +23 -0
- package/dist/manifest.js +22 -48
- package/dist/preview.d.ts +100 -0
- package/dist/preview.js +418 -488
- package/dist/resolve-components.d.ts +39 -0
- package/dist/resolve-components.js +30 -104
- package/dist/server/resolve-request-route.d.ts +39 -0
- package/dist/server/resolve-request-route.js +104 -113
- package/dist/server-contract.d.ts +39 -0
- package/dist/server-contract.js +15 -67
- package/dist/toolchain-paths.d.ts +23 -0
- package/dist/toolchain-paths.js +111 -39
- package/dist/toolchain-runner.d.ts +33 -0
- package/dist/toolchain-runner.js +170 -0
- package/dist/types/generate-env-dts.d.ts +5 -0
- package/dist/types/generate-env-dts.js +4 -2
- package/dist/types/generate-routes-dts.d.ts +8 -0
- package/dist/types/generate-routes-dts.js +7 -5
- package/dist/types/index.d.ts +14 -0
- package/dist/types/index.js +16 -7
- package/dist/ui/env.d.ts +18 -0
- package/dist/ui/env.js +0 -12
- package/dist/ui/format.d.ts +33 -0
- package/dist/ui/format.js +7 -45
- package/dist/ui/logger.d.ts +59 -0
- package/dist/ui/logger.js +3 -32
- package/dist/version-check.d.ts +54 -0
- package/dist/version-check.js +41 -98
- package/package.json +17 -5
|
@@ -8,100 +8,89 @@
|
|
|
8
8
|
* @returns {number}
|
|
9
9
|
*/
|
|
10
10
|
export function compareRouteSpecificity(a, b) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const max = Math.min(aSegs.length, bSegs.length);
|
|
23
|
-
for (let i = 0; i < max; i++) {
|
|
24
|
-
const aWeight = segmentWeight(aSegs[i]);
|
|
25
|
-
const bWeight = segmentWeight(bSegs[i]);
|
|
26
|
-
if (aWeight !== bWeight) {
|
|
27
|
-
return bWeight - aWeight;
|
|
11
|
+
if (a === '/' && b !== '/')
|
|
12
|
+
return -1;
|
|
13
|
+
if (b === '/' && a !== '/')
|
|
14
|
+
return 1;
|
|
15
|
+
const aSegs = splitPath(a);
|
|
16
|
+
const bSegs = splitPath(b);
|
|
17
|
+
const aClass = routeClass(aSegs);
|
|
18
|
+
const bClass = routeClass(bSegs);
|
|
19
|
+
if (aClass !== bClass) {
|
|
20
|
+
return bClass - aClass;
|
|
28
21
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
const max = Math.min(aSegs.length, bSegs.length);
|
|
23
|
+
for (let i = 0; i < max; i++) {
|
|
24
|
+
const aWeight = segmentWeight(aSegs[i]);
|
|
25
|
+
const bWeight = segmentWeight(bSegs[i]);
|
|
26
|
+
if (aWeight !== bWeight) {
|
|
27
|
+
return bWeight - aWeight;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (aSegs.length !== bSegs.length) {
|
|
31
|
+
return bSegs.length - aSegs.length;
|
|
32
|
+
}
|
|
33
|
+
return a.localeCompare(b);
|
|
36
34
|
}
|
|
37
|
-
|
|
38
35
|
/**
|
|
39
36
|
* @param {string} pathname
|
|
40
37
|
* @param {Array<{ path: string }>} routes
|
|
41
38
|
* @returns {{ entry: { path: string }, params: Record<string, string> } | null}
|
|
42
39
|
*/
|
|
43
40
|
export function matchRoute(pathname, routes) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
41
|
+
const target = splitPath(pathname);
|
|
42
|
+
const ordered = [...routes].sort((a, b) => compareRouteSpecificity(a.path, b.path));
|
|
43
|
+
for (const entry of ordered) {
|
|
44
|
+
const pattern = splitPath(entry.path);
|
|
45
|
+
const params = Object.create(null);
|
|
46
|
+
let patternIndex = 0;
|
|
47
|
+
let valueIndex = 0;
|
|
48
|
+
let matched = true;
|
|
49
|
+
while (patternIndex < pattern.length) {
|
|
50
|
+
const segment = pattern[patternIndex];
|
|
51
|
+
if (segment.startsWith('*')) {
|
|
52
|
+
const optionalCatchAll = segment.endsWith('?');
|
|
53
|
+
const key = optionalCatchAll ? segment.slice(1, -1) : segment.slice(1);
|
|
54
|
+
if (patternIndex !== pattern.length - 1) {
|
|
55
|
+
matched = false;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const rest = target.slice(valueIndex);
|
|
59
|
+
const rootRequiredCatchAll = !optionalCatchAll && pattern.length === 1;
|
|
60
|
+
if (rest.length === 0 && !optionalCatchAll && !rootRequiredCatchAll) {
|
|
61
|
+
matched = false;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
params[key] = normalizeCatchAll(rest);
|
|
65
|
+
valueIndex = target.length;
|
|
66
|
+
patternIndex = pattern.length;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
if (valueIndex >= target.length) {
|
|
70
|
+
matched = false;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
const value = target[valueIndex];
|
|
74
|
+
if (segment.startsWith(':')) {
|
|
75
|
+
params[segment.slice(1)] = value;
|
|
76
|
+
}
|
|
77
|
+
else if (segment !== value) {
|
|
78
|
+
matched = false;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
patternIndex += 1;
|
|
82
|
+
valueIndex += 1;
|
|
61
83
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (rest.length === 0 && !optionalCatchAll && !rootRequiredCatchAll) {
|
|
65
|
-
matched = false;
|
|
66
|
-
break;
|
|
84
|
+
if (!matched) {
|
|
85
|
+
continue;
|
|
67
86
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (valueIndex >= target.length) {
|
|
75
|
-
matched = false;
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const value = target[valueIndex];
|
|
80
|
-
if (segment.startsWith(':')) {
|
|
81
|
-
params[segment.slice(1)] = value;
|
|
82
|
-
} else if (segment !== value) {
|
|
83
|
-
matched = false;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
patternIndex += 1;
|
|
88
|
-
valueIndex += 1;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!matched) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (valueIndex !== target.length || patternIndex !== pattern.length) {
|
|
96
|
-
continue;
|
|
87
|
+
if (valueIndex !== target.length || patternIndex !== pattern.length) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
return { entry, params: { ...params } };
|
|
97
91
|
}
|
|
98
|
-
|
|
99
|
-
return { entry, params: { ...params } };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return null;
|
|
92
|
+
return null;
|
|
103
93
|
}
|
|
104
|
-
|
|
105
94
|
/**
|
|
106
95
|
* Resolve an incoming request URL against a manifest route list.
|
|
107
96
|
*
|
|
@@ -110,60 +99,62 @@ export function matchRoute(pathname, routes) {
|
|
|
110
99
|
* @returns {{ matched: boolean, route: { path: string } | null, params: Record<string, string> }}
|
|
111
100
|
*/
|
|
112
101
|
export function resolveRequestRoute(reqUrl, manifest) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
102
|
+
const url = reqUrl instanceof URL ? reqUrl : new URL(String(reqUrl), 'http://localhost');
|
|
103
|
+
const matched = matchRoute(url.pathname, manifest);
|
|
104
|
+
if (!matched) {
|
|
105
|
+
return { matched: false, route: null, params: {} };
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
matched: true,
|
|
109
|
+
route: matched.entry,
|
|
110
|
+
params: matched.params
|
|
111
|
+
};
|
|
123
112
|
}
|
|
124
|
-
|
|
125
113
|
/**
|
|
126
114
|
* @param {string[]} segments
|
|
127
115
|
* @returns {number}
|
|
128
116
|
*/
|
|
129
117
|
function routeClass(segments) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
118
|
+
let hasParam = false;
|
|
119
|
+
let hasCatchAll = false;
|
|
120
|
+
for (const segment of segments) {
|
|
121
|
+
if (segment.startsWith('*')) {
|
|
122
|
+
hasCatchAll = true;
|
|
123
|
+
}
|
|
124
|
+
else if (segment.startsWith(':')) {
|
|
125
|
+
hasParam = true;
|
|
126
|
+
}
|
|
137
127
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
128
|
+
if (!hasParam && !hasCatchAll)
|
|
129
|
+
return 3;
|
|
130
|
+
if (hasCatchAll)
|
|
131
|
+
return 1;
|
|
132
|
+
return 2;
|
|
142
133
|
}
|
|
143
|
-
|
|
144
134
|
/**
|
|
145
135
|
* @param {string | undefined} segment
|
|
146
136
|
* @returns {number}
|
|
147
137
|
*/
|
|
148
138
|
function segmentWeight(segment) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
139
|
+
if (!segment)
|
|
140
|
+
return 0;
|
|
141
|
+
if (segment.startsWith('*'))
|
|
142
|
+
return 1;
|
|
143
|
+
if (segment.startsWith(':'))
|
|
144
|
+
return 2;
|
|
145
|
+
return 3;
|
|
153
146
|
}
|
|
154
|
-
|
|
155
147
|
/**
|
|
156
148
|
* @param {string} pathname
|
|
157
149
|
* @returns {string[]}
|
|
158
150
|
*/
|
|
159
151
|
function splitPath(pathname) {
|
|
160
|
-
|
|
152
|
+
return pathname.split('/').filter(Boolean);
|
|
161
153
|
}
|
|
162
|
-
|
|
163
154
|
/**
|
|
164
155
|
* @param {string[]} segments
|
|
165
156
|
* @returns {string}
|
|
166
157
|
*/
|
|
167
158
|
function normalizeCatchAll(segments) {
|
|
168
|
-
|
|
159
|
+
return segments.filter(Boolean).join('/');
|
|
169
160
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function allow(): {
|
|
2
|
+
kind: string;
|
|
3
|
+
};
|
|
4
|
+
export function redirect(location: any, status?: number): {
|
|
5
|
+
kind: string;
|
|
6
|
+
location: string;
|
|
7
|
+
status: number;
|
|
8
|
+
};
|
|
9
|
+
export function deny(status?: number, message?: undefined): {
|
|
10
|
+
kind: string;
|
|
11
|
+
status: number;
|
|
12
|
+
message: undefined;
|
|
13
|
+
};
|
|
14
|
+
export function data(payload: any): {
|
|
15
|
+
kind: string;
|
|
16
|
+
data: any;
|
|
17
|
+
};
|
|
18
|
+
export function validateServerExports({ exports, filePath }: {
|
|
19
|
+
exports: any;
|
|
20
|
+
filePath: any;
|
|
21
|
+
}): void;
|
|
22
|
+
export function assertJsonSerializable(value: any, where?: string): void;
|
|
23
|
+
export function resolveRouteResult({ exports, ctx, filePath, guardOnly }: {
|
|
24
|
+
exports: any;
|
|
25
|
+
ctx: any;
|
|
26
|
+
filePath: any;
|
|
27
|
+
guardOnly?: boolean | undefined;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
result: any;
|
|
30
|
+
trace: {
|
|
31
|
+
guard: string;
|
|
32
|
+
load: string;
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
export function resolveServerPayload({ exports, ctx, filePath }: {
|
|
36
|
+
exports: any;
|
|
37
|
+
ctx: any;
|
|
38
|
+
filePath: any;
|
|
39
|
+
}): Promise<any>;
|
package/dist/server-contract.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
// server-contract.js — Zenith CLI V0
|
|
2
2
|
// ---------------------------------------------------------------------------
|
|
3
3
|
// Shared validation and payload resolution logic for <script server> blocks.
|
|
4
|
-
|
|
5
4
|
const NEW_KEYS = new Set(['data', 'load', 'guard', 'prerender']);
|
|
6
5
|
const LEGACY_KEYS = new Set(['ssr_data', 'props', 'ssr', 'prerender']);
|
|
7
6
|
const ALLOWED_KEYS = new Set(['data', 'load', 'guard', 'prerender', 'ssr_data', 'props', 'ssr']);
|
|
8
|
-
|
|
9
7
|
const ROUTE_RESULT_KINDS = new Set(['allow', 'redirect', 'deny', 'data']);
|
|
10
|
-
|
|
11
8
|
export function allow() {
|
|
12
9
|
return { kind: 'allow' };
|
|
13
10
|
}
|
|
14
|
-
|
|
15
11
|
export function redirect(location, status = 302) {
|
|
16
12
|
return {
|
|
17
13
|
kind: 'redirect',
|
|
@@ -19,7 +15,6 @@ export function redirect(location, status = 302) {
|
|
|
19
15
|
status: Number.isInteger(status) ? status : 302
|
|
20
16
|
};
|
|
21
17
|
}
|
|
22
|
-
|
|
23
18
|
export function deny(status = 403, message = undefined) {
|
|
24
19
|
return {
|
|
25
20
|
kind: 'deny',
|
|
@@ -27,11 +22,9 @@ export function deny(status = 403, message = undefined) {
|
|
|
27
22
|
message: typeof message === 'string' ? message : undefined
|
|
28
23
|
};
|
|
29
24
|
}
|
|
30
|
-
|
|
31
25
|
export function data(payload) {
|
|
32
26
|
return { kind: 'data', data: payload };
|
|
33
27
|
}
|
|
34
|
-
|
|
35
28
|
function isRouteResultLike(value) {
|
|
36
29
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
37
30
|
return false;
|
|
@@ -39,18 +32,14 @@ function isRouteResultLike(value) {
|
|
|
39
32
|
const kind = value.kind;
|
|
40
33
|
return typeof kind === 'string' && ROUTE_RESULT_KINDS.has(kind);
|
|
41
34
|
}
|
|
42
|
-
|
|
43
35
|
function assertValidRouteResultShape(value, where, allowedKinds) {
|
|
44
36
|
if (!isRouteResultLike(value)) {
|
|
45
37
|
throw new Error(`[Zenith] ${where}: invalid route result. Expected object with kind.`);
|
|
46
38
|
}
|
|
47
39
|
const kind = value.kind;
|
|
48
40
|
if (!allowedKinds.has(kind)) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
`[Zenith] ${where}: kind "${kind}" is not allowed here (allowed: ${Array.from(allowedKinds).join(', ')}).`
|
|
51
|
-
);
|
|
41
|
+
throw new Error(`[Zenith] ${where}: kind "${kind}" is not allowed here (allowed: ${Array.from(allowedKinds).join(', ')}).`);
|
|
52
42
|
}
|
|
53
|
-
|
|
54
43
|
if (kind === 'redirect') {
|
|
55
44
|
if (typeof value.location !== 'string' || value.location.length === 0) {
|
|
56
45
|
throw new Error(`[Zenith] ${where}: redirect requires non-empty string location.`);
|
|
@@ -59,7 +48,6 @@ function assertValidRouteResultShape(value, where, allowedKinds) {
|
|
|
59
48
|
throw new Error(`[Zenith] ${where}: redirect status must be an integer 3xx.`);
|
|
60
49
|
}
|
|
61
50
|
}
|
|
62
|
-
|
|
63
51
|
if (kind === 'deny') {
|
|
64
52
|
if (!Number.isInteger(value.status) || (value.status !== 401 && value.status !== 403)) {
|
|
65
53
|
throw new Error(`[Zenith] ${where}: deny status must be 401 or 403.`);
|
|
@@ -69,36 +57,26 @@ function assertValidRouteResultShape(value, where, allowedKinds) {
|
|
|
69
57
|
}
|
|
70
58
|
}
|
|
71
59
|
}
|
|
72
|
-
|
|
73
60
|
export function validateServerExports({ exports, filePath }) {
|
|
74
61
|
const exportKeys = Object.keys(exports);
|
|
75
62
|
const illegalKeys = exportKeys.filter(k => !ALLOWED_KEYS.has(k));
|
|
76
|
-
|
|
77
63
|
if (illegalKeys.length > 0) {
|
|
78
64
|
throw new Error(`[Zenith] ${filePath}: illegal export(s): ${illegalKeys.join(', ')}`);
|
|
79
65
|
}
|
|
80
|
-
|
|
81
66
|
const hasData = 'data' in exports;
|
|
82
67
|
const hasLoad = 'load' in exports;
|
|
83
68
|
const hasGuard = 'guard' in exports;
|
|
84
|
-
|
|
85
69
|
const hasNew = hasData || hasLoad;
|
|
86
70
|
const hasLegacy = ('ssr_data' in exports) || ('props' in exports) || ('ssr' in exports);
|
|
87
|
-
|
|
88
71
|
if (hasData && hasLoad) {
|
|
89
72
|
throw new Error(`[Zenith] ${filePath}: cannot export both "data" and "load". Choose one.`);
|
|
90
73
|
}
|
|
91
|
-
|
|
92
74
|
if (hasNew && hasLegacy) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`[Zenith] ${filePath}: cannot mix new ("data"/"load") with legacy ("ssr_data"/"props"/"ssr") exports.`
|
|
95
|
-
);
|
|
75
|
+
throw new Error(`[Zenith] ${filePath}: cannot mix new ("data"/"load") with legacy ("ssr_data"/"props"/"ssr") exports.`);
|
|
96
76
|
}
|
|
97
|
-
|
|
98
77
|
if ('prerender' in exports && typeof exports.prerender !== 'boolean') {
|
|
99
78
|
throw new Error(`[Zenith] ${filePath}: "prerender" must be a boolean.`);
|
|
100
79
|
}
|
|
101
|
-
|
|
102
80
|
if (hasLoad && typeof exports.load !== 'function') {
|
|
103
81
|
throw new Error(`[Zenith] ${filePath}: "load" must be a function.`);
|
|
104
82
|
}
|
|
@@ -112,7 +90,6 @@ export function validateServerExports({ exports, filePath }) {
|
|
|
112
90
|
throw new Error(`[Zenith] ${filePath}: "load(ctx)" must not contain rest parameters.`);
|
|
113
91
|
}
|
|
114
92
|
}
|
|
115
|
-
|
|
116
93
|
if (hasGuard && typeof exports.guard !== 'function') {
|
|
117
94
|
throw new Error(`[Zenith] ${filePath}: "guard" must be a function.`);
|
|
118
95
|
}
|
|
@@ -127,53 +104,45 @@ export function validateServerExports({ exports, filePath }) {
|
|
|
127
104
|
}
|
|
128
105
|
}
|
|
129
106
|
}
|
|
130
|
-
|
|
131
107
|
export function assertJsonSerializable(value, where = 'payload') {
|
|
132
108
|
const seen = new Set();
|
|
133
|
-
|
|
134
109
|
function walk(v, path) {
|
|
135
110
|
const t = typeof v;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (t === 'string' || t === 'number' || t === 'boolean')
|
|
139
|
-
|
|
111
|
+
if (v === null)
|
|
112
|
+
return;
|
|
113
|
+
if (t === 'string' || t === 'number' || t === 'boolean')
|
|
114
|
+
return;
|
|
140
115
|
if (t === 'bigint' || t === 'function' || t === 'symbol') {
|
|
141
116
|
throw new Error(`[Zenith] ${where}: non-serializable ${t} at ${path}`);
|
|
142
117
|
}
|
|
143
|
-
|
|
144
118
|
if (t === 'undefined') {
|
|
145
119
|
throw new Error(`[Zenith] ${where}: undefined is not allowed at ${path}`);
|
|
146
120
|
}
|
|
147
|
-
|
|
148
121
|
if (v instanceof Date) {
|
|
149
122
|
throw new Error(`[Zenith] ${where}: Date is not allowed at ${path} (convert to ISO string)`);
|
|
150
123
|
}
|
|
151
|
-
|
|
152
124
|
if (v instanceof Map || v instanceof Set) {
|
|
153
125
|
throw new Error(`[Zenith] ${where}: Map/Set not allowed at ${path}`);
|
|
154
126
|
}
|
|
155
|
-
|
|
156
127
|
if (t === 'object') {
|
|
157
|
-
if (seen.has(v))
|
|
128
|
+
if (seen.has(v))
|
|
129
|
+
throw new Error(`[Zenith] ${where}: circular reference at ${path}`);
|
|
158
130
|
seen.add(v);
|
|
159
|
-
|
|
160
131
|
if (Array.isArray(v)) {
|
|
161
132
|
if (path === '$') {
|
|
162
133
|
throw new Error(`[Zenith] ${where}: top-level payload must be a plain object, not an array at ${path}`);
|
|
163
134
|
}
|
|
164
|
-
for (let i = 0; i < v.length; i++)
|
|
135
|
+
for (let i = 0; i < v.length; i++)
|
|
136
|
+
walk(v[i], `${path}[${i}]`);
|
|
165
137
|
return;
|
|
166
138
|
}
|
|
167
|
-
|
|
168
139
|
const proto = Object.getPrototypeOf(v);
|
|
169
140
|
const isPlainObject = proto === null ||
|
|
170
141
|
proto === Object.prototype ||
|
|
171
142
|
(proto && proto.constructor && proto.constructor.name === 'Object');
|
|
172
|
-
|
|
173
143
|
if (!isPlainObject) {
|
|
174
144
|
throw new Error(`[Zenith] ${where}: non-plain object at ${path}`);
|
|
175
145
|
}
|
|
176
|
-
|
|
177
146
|
for (const k of Object.keys(v)) {
|
|
178
147
|
if (k === '__proto__' || k === 'constructor' || k === 'prototype') {
|
|
179
148
|
throw new Error(`[Zenith] ${where}: forbidden prototype pollution key "${k}" at ${path}.${k}`);
|
|
@@ -182,54 +151,40 @@ export function assertJsonSerializable(value, where = 'payload') {
|
|
|
182
151
|
}
|
|
183
152
|
return;
|
|
184
153
|
}
|
|
185
|
-
|
|
186
154
|
throw new Error(`[Zenith] ${where}: unsupported type at ${path}`);
|
|
187
155
|
}
|
|
188
|
-
|
|
189
156
|
walk(value, '$');
|
|
190
157
|
}
|
|
191
|
-
|
|
192
158
|
export async function resolveRouteResult({ exports, ctx, filePath, guardOnly = false }) {
|
|
193
159
|
validateServerExports({ exports, filePath });
|
|
194
|
-
|
|
195
160
|
const trace = {
|
|
196
161
|
guard: 'none',
|
|
197
162
|
load: 'none'
|
|
198
163
|
};
|
|
199
|
-
|
|
200
164
|
if ('guard' in exports) {
|
|
201
165
|
const guardRaw = await exports.guard(ctx);
|
|
202
166
|
const guardResult = guardRaw == null ? allow() : guardRaw;
|
|
203
167
|
if (guardResult.kind === 'data') {
|
|
204
168
|
throw new Error(`[Zenith] ${filePath}: guard(ctx) returned data(payload) which is a critical invariant violation. guard() can only return allow(), redirect(), or deny(). Use load(ctx) for data injection.`);
|
|
205
169
|
}
|
|
206
|
-
assertValidRouteResultShape(
|
|
207
|
-
guardResult,
|
|
208
|
-
`${filePath}: guard(ctx) return`,
|
|
209
|
-
new Set(['allow', 'redirect', 'deny'])
|
|
210
|
-
);
|
|
170
|
+
assertValidRouteResultShape(guardResult, `${filePath}: guard(ctx) return`, new Set(['allow', 'redirect', 'deny']));
|
|
211
171
|
trace.guard = guardResult.kind;
|
|
212
172
|
if (guardResult.kind === 'redirect' || guardResult.kind === 'deny') {
|
|
213
173
|
return { result: guardResult, trace };
|
|
214
174
|
}
|
|
215
175
|
}
|
|
216
|
-
|
|
217
176
|
if (guardOnly) {
|
|
218
177
|
return { result: allow(), trace };
|
|
219
178
|
}
|
|
220
|
-
|
|
221
179
|
let payload;
|
|
222
180
|
if ('load' in exports) {
|
|
223
181
|
const loadRaw = await exports.load(ctx);
|
|
224
182
|
let loadResult = null;
|
|
225
183
|
if (isRouteResultLike(loadRaw)) {
|
|
226
184
|
loadResult = loadRaw;
|
|
227
|
-
assertValidRouteResultShape(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
new Set(['data', 'redirect', 'deny'])
|
|
231
|
-
);
|
|
232
|
-
} else {
|
|
185
|
+
assertValidRouteResultShape(loadResult, `${filePath}: load(ctx) return`, new Set(['data', 'redirect', 'deny']));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
233
188
|
assertJsonSerializable(loadRaw, `${filePath}: load(ctx) return`);
|
|
234
189
|
loadResult = data(loadRaw);
|
|
235
190
|
}
|
|
@@ -242,7 +197,6 @@ export async function resolveRouteResult({ exports, ctx, filePath, guardOnly = f
|
|
|
242
197
|
trace.load = 'data';
|
|
243
198
|
return { result: data(payload), trace };
|
|
244
199
|
}
|
|
245
|
-
|
|
246
200
|
// legacy fallback
|
|
247
201
|
if ('ssr_data' in exports) {
|
|
248
202
|
payload = exports.ssr_data;
|
|
@@ -262,24 +216,18 @@ export async function resolveRouteResult({ exports, ctx, filePath, guardOnly = f
|
|
|
262
216
|
trace.load = 'data';
|
|
263
217
|
return { result: data(payload), trace };
|
|
264
218
|
}
|
|
265
|
-
|
|
266
219
|
return { result: data({}), trace };
|
|
267
220
|
}
|
|
268
|
-
|
|
269
221
|
export async function resolveServerPayload({ exports, ctx, filePath }) {
|
|
270
222
|
const resolved = await resolveRouteResult({ exports, ctx, filePath });
|
|
271
223
|
if (!resolved || !resolved.result || typeof resolved.result !== 'object') {
|
|
272
224
|
return {};
|
|
273
225
|
}
|
|
274
|
-
|
|
275
226
|
if (resolved.result.kind === 'data') {
|
|
276
227
|
return resolved.result.data;
|
|
277
228
|
}
|
|
278
229
|
if (resolved.result.kind === 'allow') {
|
|
279
230
|
return {};
|
|
280
231
|
}
|
|
281
|
-
|
|
282
|
-
throw new Error(
|
|
283
|
-
`[Zenith] ${filePath}: resolveServerPayload() expected data but received ${resolved.result.kind}. Use resolveRouteResult() for guard/load flows.`
|
|
284
|
-
);
|
|
232
|
+
throw new Error(`[Zenith] ${filePath}: resolveServerPayload() expected data but received ${resolved.result.kind}. Use resolveRouteResult() for guard/load flows.`);
|
|
285
233
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ToolchainTool = 'compiler' | 'bundler';
|
|
2
|
+
export type ToolchainMode = 'binary' | 'node-bridge';
|
|
3
|
+
export interface ToolchainCandidate {
|
|
4
|
+
tool: ToolchainTool;
|
|
5
|
+
mode: ToolchainMode;
|
|
6
|
+
source: string;
|
|
7
|
+
sourceKey: string;
|
|
8
|
+
label: string;
|
|
9
|
+
path: string;
|
|
10
|
+
command: string;
|
|
11
|
+
argsPrefix: string[];
|
|
12
|
+
explicit?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveBinary(candidates: Array<string | ToolchainCandidate>): string;
|
|
15
|
+
export declare function resolvePackageRoot(packageName: string, projectRoot?: string | null): string | null;
|
|
16
|
+
export declare function readInstalledPackageVersion(packageName: string, projectRoot?: string | null): string | null;
|
|
17
|
+
export declare function readCliPackageVersion(): string;
|
|
18
|
+
export declare function compilerCommandCandidates(projectRoot?: string | null, env?: NodeJS.ProcessEnv): ToolchainCandidate[];
|
|
19
|
+
export declare function compilerBinCandidates(projectRoot?: string | null, env?: NodeJS.ProcessEnv): string[];
|
|
20
|
+
export declare function resolveCompilerBin(projectRoot?: string | null, env?: NodeJS.ProcessEnv): string;
|
|
21
|
+
export declare function bundlerCommandCandidates(projectRoot?: string | null, env?: NodeJS.ProcessEnv): ToolchainCandidate[];
|
|
22
|
+
export declare function resolveBundlerBin(projectRoot?: string | null, env?: NodeJS.ProcessEnv): string;
|
|
23
|
+
export declare function bundlerBinCandidates(projectRoot?: string | null, env?: NodeJS.ProcessEnv): string[];
|