openxiangda-devkit-core 2.0.0-alpha.98 → 2.0.0
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/LICENSE +21 -0
- package/README.md +3 -9
- package/dist/app-spec.d.ts +30 -12
- package/dist/app-spec.d.ts.map +1 -1
- package/dist/app-spec.js +238 -57
- package/dist/app-spec.js.map +1 -1
- package/dist/application-services.d.ts +625 -42
- package/dist/application-services.d.ts.map +1 -1
- package/dist/application-services.js +321 -85
- package/dist/application-services.js.map +1 -1
- package/dist/application-ui-contract.d.ts +5 -0
- package/dist/application-ui-contract.d.ts.map +1 -0
- package/dist/application-ui-contract.js +149 -0
- package/dist/application-ui-contract.js.map +1 -0
- package/dist/backend-image-build.js +6 -11
- package/dist/backend-image-build.js.map +1 -1
- package/dist/command-process.d.ts +14 -0
- package/dist/command-process.d.ts.map +1 -0
- package/dist/command-process.js +51 -0
- package/dist/command-process.js.map +1 -0
- package/dist/command-registry.d.ts +44 -4
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +10 -6
- package/dist/command-registry.js.map +1 -1
- package/dist/compiler/ai-catalog.d.ts.map +1 -1
- package/dist/compiler/ai-catalog.js +2 -1
- package/dist/compiler/ai-catalog.js.map +1 -1
- package/dist/compiler/application-model.d.ts +60 -0
- package/dist/compiler/application-model.d.ts.map +1 -0
- package/dist/compiler/application-model.js +141 -0
- package/dist/compiler/application-model.js.map +1 -0
- package/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +196 -98
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +36 -10
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +536 -171
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/field-surface.js +1 -1
- package/dist/compiler/field-surface.js.map +1 -1
- package/dist/compiler/package-compiler.d.ts.map +1 -1
- package/dist/compiler/package-compiler.js +12 -0
- package/dist/compiler/package-compiler.js.map +1 -1
- package/dist/compiler/permission-review.d.ts +139 -0
- package/dist/compiler/permission-review.d.ts.map +1 -0
- package/dist/compiler/permission-review.js +131 -0
- package/dist/compiler/permission-review.js.map +1 -0
- package/dist/configuration-preflight.d.ts +7 -0
- package/dist/configuration-preflight.d.ts.map +1 -0
- package/dist/configuration-preflight.js +13 -0
- package/dist/configuration-preflight.js.map +1 -0
- package/dist/control-plane-client.d.ts +56 -61
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/control-plane-client.js +89 -8
- package/dist/control-plane-client.js.map +1 -1
- package/dist/delivery-cache.d.ts +34 -0
- package/dist/delivery-cache.d.ts.map +1 -0
- package/dist/delivery-cache.js +186 -0
- package/dist/delivery-cache.js.map +1 -0
- package/dist/delivery-source.d.ts +17 -0
- package/dist/delivery-source.d.ts.map +1 -0
- package/dist/delivery-source.js +85 -0
- package/dist/delivery-source.js.map +1 -0
- package/dist/deployment-observer.d.ts +14 -0
- package/dist/deployment-observer.d.ts.map +1 -0
- package/dist/deployment-observer.js +68 -0
- package/dist/deployment-observer.js.map +1 -0
- package/dist/deployment.d.ts +17 -1
- package/dist/deployment.d.ts.map +1 -1
- package/dist/deployment.js +55 -15
- package/dist/deployment.js.map +1 -1
- package/dist/design-readiness.d.ts +24 -0
- package/dist/design-readiness.d.ts.map +1 -0
- package/dist/design-readiness.js +102 -0
- package/dist/design-readiness.js.map +1 -0
- package/dist/developer-errors.d.ts +11 -0
- package/dist/developer-errors.d.ts.map +1 -0
- package/dist/developer-errors.js +16 -0
- package/dist/developer-errors.js.map +1 -0
- package/dist/developer-operations.d.ts +190 -0
- package/dist/developer-operations.d.ts.map +1 -0
- package/dist/developer-operations.js +65 -0
- package/dist/developer-operations.js.map +1 -0
- package/dist/development-lifecycle.d.ts +75 -0
- package/dist/development-lifecycle.d.ts.map +1 -0
- package/dist/development-lifecycle.js +189 -0
- package/dist/development-lifecycle.js.map +1 -0
- package/dist/documentation.d.ts +111 -0
- package/dist/documentation.d.ts.map +1 -0
- package/dist/documentation.js +112 -0
- package/dist/documentation.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/operation-progress.d.ts +26 -0
- package/dist/operation-progress.d.ts.map +1 -0
- package/dist/operation-progress.js +92 -0
- package/dist/operation-progress.js.map +1 -0
- package/dist/optional-backend.d.ts +14 -0
- package/dist/optional-backend.d.ts.map +1 -0
- package/dist/optional-backend.js +145 -0
- package/dist/optional-backend.js.map +1 -0
- package/dist/session.d.ts +22 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +36 -0
- package/dist/session.js.map +1 -1
- package/dist/workspace-loader.d.ts.map +1 -1
- package/dist/workspace-loader.js +17 -2
- package/dist/workspace-loader.js.map +1 -1
- package/dist/workspace-operation.d.ts +3 -0
- package/dist/workspace-operation.d.ts.map +1 -0
- package/dist/workspace-operation.js +45 -0
- package/dist/workspace-operation.js.map +1 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +1 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +18 -4
- package/templates/backend/Dockerfile +20 -0
- package/templates/backend/package.json +27 -0
- package/templates/backend/src/app.module.ts +23 -0
- package/templates/backend/src/main.ts +5 -0
- package/templates/backend/tsconfig.json +9 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { canonicalJson, sha256Digest } from 'openxiangda-contracts';
|
|
5
|
+
import { verifySealedAppPackage } from './deployment.js';
|
|
6
|
+
const CACHE_SCHEMA = 'openxiangda.local-delivery-evidence/v1';
|
|
7
|
+
const MAX_FILES = 20_000;
|
|
8
|
+
const MAX_BYTES = 256 * 1024 * 1024;
|
|
9
|
+
const MAX_TIME_MS = 10_000;
|
|
10
|
+
const EXCLUDED = new Set(['.git', '.codegraph', '.openxiangda', 'node_modules', '.turbo', 'coverage', 'playwright-report', 'test-results']);
|
|
11
|
+
/** 有界读取且不跟随符号链接。摘要只用于复核本地证据,不代表平台授权。 */
|
|
12
|
+
function snapshot(root, excluded = () => false) {
|
|
13
|
+
const files = [];
|
|
14
|
+
const started = Date.now();
|
|
15
|
+
let bytes = 0;
|
|
16
|
+
let entries = 0;
|
|
17
|
+
function visit(directory) {
|
|
18
|
+
if (Date.now() - started > MAX_TIME_MS)
|
|
19
|
+
throw new Error('CACHE_SCAN_TIME_LIMIT');
|
|
20
|
+
for (const name of readdirSync(directory).sort()) {
|
|
21
|
+
if (++entries > MAX_FILES)
|
|
22
|
+
throw new Error('CACHE_SCAN_FILE_LIMIT');
|
|
23
|
+
const path = join(directory, name);
|
|
24
|
+
const local = relative(root, path).replaceAll('\\', '/');
|
|
25
|
+
if (excluded(local, name))
|
|
26
|
+
continue;
|
|
27
|
+
const stat = lstatSync(path);
|
|
28
|
+
if (stat.isSymbolicLink())
|
|
29
|
+
throw new Error('CACHE_SYMLINK_UNSUPPORTED');
|
|
30
|
+
if (stat.isDirectory())
|
|
31
|
+
visit(path);
|
|
32
|
+
else if (stat.isFile()) {
|
|
33
|
+
bytes += stat.size;
|
|
34
|
+
if (bytes > MAX_BYTES || Date.now() - started > MAX_TIME_MS)
|
|
35
|
+
throw new Error('CACHE_SCAN_LIMIT');
|
|
36
|
+
const content = readFileSync(path);
|
|
37
|
+
if (content.length !== stat.size)
|
|
38
|
+
throw new Error('CACHE_INPUT_CHANGED');
|
|
39
|
+
files.push({ path: local, digest: createHash('sha256').update(content).digest('hex'), bytes: content.length });
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
throw new Error('CACHE_SPECIAL_FILE_UNSUPPORTED');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
visit(root);
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
function outputRoots(context) {
|
|
49
|
+
return [...new Set([context.frontendRoot, ...(context.backendRoot ? [context.backendRoot] : [])])].map(root => {
|
|
50
|
+
const path = relative(context.root, resolve(context.root, root, 'dist')).replaceAll('\\', '/');
|
|
51
|
+
if (!path || path.startsWith('../'))
|
|
52
|
+
throw new Error('CACHE_OUTPUT_OUTSIDE_WORKSPACE');
|
|
53
|
+
return path;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** 锁文件、安装状态、源码(包含 .env)、工具链及进程环境共同决定输入;不落盘明文环境变量。 */
|
|
57
|
+
export function deliveryInputDigest(context) {
|
|
58
|
+
try {
|
|
59
|
+
const outputs = outputRoots(context);
|
|
60
|
+
const files = snapshot(context.root, (path, name) => EXCLUDED.has(name) || outputs.some(root => path === root || path.startsWith(`${root}/`)));
|
|
61
|
+
const locks = files.filter(file => /(^|\/)(pnpm-lock.yaml|package-lock.json|yarn.lock)$/.test(file.path));
|
|
62
|
+
if (!locks.length)
|
|
63
|
+
return null;
|
|
64
|
+
// 未由当前工作区覆盖的本地依赖没有可靠输入闭包,直接执行正式检查。
|
|
65
|
+
for (const lock of locks) {
|
|
66
|
+
if (/(?:file|link):/.test(readFileSync(join(context.root, lock.path), 'utf8')))
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const installation = ['node_modules/.modules.yaml', 'node_modules/.pnpm/lock.yaml', 'node_modules/.package-lock.json'].map(path => {
|
|
70
|
+
const file = join(context.root, path);
|
|
71
|
+
if (!existsSync(file))
|
|
72
|
+
return { path, digest: null };
|
|
73
|
+
const stat = lstatSync(file);
|
|
74
|
+
if (!stat.isFile() || stat.size > 16 * 1024 * 1024)
|
|
75
|
+
throw new Error('CACHE_DEPENDENCY_STATE_INVALID');
|
|
76
|
+
return { path, digest: createHash('sha256').update(readFileSync(file)).digest('hex') };
|
|
77
|
+
});
|
|
78
|
+
return sha256Digest({ files, installation, toolchain: context.toolchain, node: process.version,
|
|
79
|
+
platform: process.platform, arch: process.arch, environment: { ...(context.environment || process.env) } });
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function deliveryOutputDigest(context) {
|
|
86
|
+
try {
|
|
87
|
+
return sha256Digest(outputRoots(context).map(path => ({ path, files: snapshot(join(context.root, path)) })));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function cachePath(root, name) { return join(root, '.openxiangda', 'build', name); }
|
|
94
|
+
function readJson(path, maxBytes = 1024 * 1024) {
|
|
95
|
+
const stat = lstatSync(path);
|
|
96
|
+
if (!stat.isFile() || stat.size > maxBytes)
|
|
97
|
+
throw new Error('CACHE_RECEIPT_INVALID');
|
|
98
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
99
|
+
}
|
|
100
|
+
function writeJson(path, value) {
|
|
101
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
102
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(temporary, `${canonicalJson(value)}\n`, { mode: 0o600 });
|
|
105
|
+
renameSync(temporary, path);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
rmSync(temporary, { force: true });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function reusableValidation(context, inputDigest = deliveryInputDigest(context)) {
|
|
112
|
+
try {
|
|
113
|
+
if (!inputDigest)
|
|
114
|
+
return null;
|
|
115
|
+
const evidence = readJson(cachePath(context.root, 'validation-evidence.json'));
|
|
116
|
+
if (evidence.schemaVersion !== CACHE_SCHEMA || evidence.inputDigest !== inputDigest || evidence.outputDigest !== deliveryOutputDigest(context))
|
|
117
|
+
return null;
|
|
118
|
+
return { schemaVersion: CACHE_SCHEMA, inputDigest, outputDigest: evidence.outputDigest };
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export function recordValidation(context, before) {
|
|
125
|
+
const after = deliveryInputDigest(context);
|
|
126
|
+
if (before && after !== before) {
|
|
127
|
+
throw Object.assign(new Error('检查期间源码、依赖或构建环境发生变化,请在修改结束后重新检查'), { code: 'OPENXIANGDA_VALIDATION_INPUT_CHANGED' });
|
|
128
|
+
}
|
|
129
|
+
const outputDigest = deliveryOutputDigest(context);
|
|
130
|
+
if (!before || !outputDigest)
|
|
131
|
+
return null;
|
|
132
|
+
const evidence = { schemaVersion: CACHE_SCHEMA, inputDigest: before, outputDigest };
|
|
133
|
+
try {
|
|
134
|
+
writeJson(cachePath(context.root, 'validation-evidence.json'), evidence);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return evidence;
|
|
140
|
+
}
|
|
141
|
+
export function recordSealedCandidate(context, compiled) {
|
|
142
|
+
const evidence = reusableValidation(context);
|
|
143
|
+
if (evidence) {
|
|
144
|
+
try {
|
|
145
|
+
writeJson(cachePath(context.root, 'candidate-evidence.json'), { ...evidence, packageDigest: compiled.digest });
|
|
146
|
+
}
|
|
147
|
+
catch { /* 缓存不可写不改变正式部署结果。 */ }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function reusableSealedCandidate(context, source, backendImage) {
|
|
151
|
+
try {
|
|
152
|
+
const evidence = reusableValidation(context);
|
|
153
|
+
if (!evidence)
|
|
154
|
+
return null;
|
|
155
|
+
const candidate = readJson(cachePath(context.root, 'candidate-evidence.json'));
|
|
156
|
+
if (candidate.schemaVersion !== CACHE_SCHEMA || candidate.inputDigest !== evidence.inputDigest || candidate.outputDigest !== evidence.outputDigest)
|
|
157
|
+
return null;
|
|
158
|
+
const manifest = readJson(cachePath(context.root, 'app-package.json'), 5 * 1024 * 1024);
|
|
159
|
+
if (manifest.source?.repository !== source.repository || manifest.source?.commit !== source.commit || manifest.source?.dirty !== false || !Array.isArray(manifest.artifacts) || manifest.artifacts.length > 8)
|
|
160
|
+
return null;
|
|
161
|
+
const compiled = { manifest, digest: candidate.packageDigest };
|
|
162
|
+
if (sha256Digest(manifest) !== compiled.digest)
|
|
163
|
+
return null;
|
|
164
|
+
const backend = manifest.artifacts.find((artifact) => artifact.kind === 'backend');
|
|
165
|
+
if (backendImage && backend?.metadata?.imageDigest !== backendImage)
|
|
166
|
+
return null;
|
|
167
|
+
const artifactContent = {};
|
|
168
|
+
let bytes = 0;
|
|
169
|
+
for (const artifact of manifest.artifacts) {
|
|
170
|
+
if (!/^[a-f0-9]{64}$/.test(artifact.digest))
|
|
171
|
+
return null;
|
|
172
|
+
const path = cachePath(context.root, `artifacts/${artifact.digest}`);
|
|
173
|
+
const stat = lstatSync(path);
|
|
174
|
+
bytes += stat.size;
|
|
175
|
+
if (!stat.isFile() || bytes > MAX_BYTES)
|
|
176
|
+
return null;
|
|
177
|
+
artifactContent[artifact.digest] = readFileSync(path, 'utf8');
|
|
178
|
+
}
|
|
179
|
+
await verifySealedAppPackage(compiled, artifactContent);
|
|
180
|
+
return { package: compiled, artifactContent };
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=delivery-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-cache.js","sourceRoot":"","sources":["../src/delivery-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PublishedDeliverySource {
|
|
2
|
+
repository: string;
|
|
3
|
+
branch: string;
|
|
4
|
+
commit: string;
|
|
5
|
+
mainlineCommit: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class DeliverySourceError extends Error {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly retryable = false;
|
|
10
|
+
readonly data: Record<string, unknown>;
|
|
11
|
+
constructor(code: string, message: string, remediation: string, details?: Record<string, unknown>);
|
|
12
|
+
}
|
|
13
|
+
/** 权威主线来自 origin 的远端 HEAD,绝不使用任务分支的 upstream。 */
|
|
14
|
+
export declare function publishedDeliverySource(root: string, sourceCommit?: string): Promise<PublishedDeliverySource>;
|
|
15
|
+
/** 构建步骤不能改变被验证源码。平台切换并发仍由 DeploymentRun 管理。 */
|
|
16
|
+
export declare function assertDeliverySourceUnchanged(root: string, source: PublishedDeliverySource): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=delivery-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-source.d.ts","sourceRoot":"","sources":["../src/delivery-source.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAGhC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAFjC,QAAQ,CAAC,SAAS,SAAS;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAClB,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;CAI/G;AAsBD,iDAAiD;AACjD,wBAAsB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAiCnH;AAED,+CAA+C;AAC/C,wBAAsB,6BAA6B,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,uBAAuB,iBAMhG"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
const execute = promisify(execFile);
|
|
4
|
+
const SHA = /^[a-f0-9]{40,64}$/;
|
|
5
|
+
export class DeliverySourceError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
retryable = false;
|
|
8
|
+
data;
|
|
9
|
+
constructor(code, message, remediation, details = {}) {
|
|
10
|
+
super(`${code}: ${message}`);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.data = { pointer: 'git', remediation, ...details };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function git(root, args) {
|
|
16
|
+
return (await execute('git', ['-C', root, ...args], {
|
|
17
|
+
encoding: 'utf8', timeout: 30_000, maxBuffer: 1024 * 1024,
|
|
18
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
|
|
19
|
+
})).stdout.trim();
|
|
20
|
+
}
|
|
21
|
+
async function cleanCommit(root) {
|
|
22
|
+
let commit;
|
|
23
|
+
try {
|
|
24
|
+
commit = await git(root, ['rev-parse', '--verify', 'HEAD']);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new DeliverySourceError('DELIVERY_GIT_COMMIT_REQUIRED', '发布需要已提交的 Git 源码', '先建立应用仓库,提交并推送主分支;日常开发仍可使用 dev/check');
|
|
28
|
+
}
|
|
29
|
+
if (!SHA.test(commit))
|
|
30
|
+
throw new DeliverySourceError('DELIVERY_GIT_COMMIT_REQUIRED', '无法识别源码提交', '检查应用 Git 仓库');
|
|
31
|
+
if (await git(root, ['status', '--porcelain', '--untracked-files=normal'])) {
|
|
32
|
+
throw new DeliverySourceError('DELIVERY_SOURCE_DIRTY', '工作区有未提交内容,尚不能冻结发布候选', '保留其他任务的改动,完成本轮整合并提交、推送主分支后发布');
|
|
33
|
+
}
|
|
34
|
+
return commit;
|
|
35
|
+
}
|
|
36
|
+
/** 权威主线来自 origin 的远端 HEAD,绝不使用任务分支的 upstream。 */
|
|
37
|
+
export async function publishedDeliverySource(root, sourceCommit) {
|
|
38
|
+
const currentCommit = await cleanCommit(root);
|
|
39
|
+
let repository;
|
|
40
|
+
let remoteHead;
|
|
41
|
+
try {
|
|
42
|
+
repository = await git(root, ['remote', 'get-url', 'origin']);
|
|
43
|
+
remoteHead = await git(root, ['ls-remote', '--symref', 'origin', 'HEAD']);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
throw new DeliverySourceError('DELIVERY_MAINLINE_UNAVAILABLE', '无法读取绑定远端 origin 的主分支', '检查 origin 与远端访问;恢复后重试,不使用过期的本地远端引用');
|
|
47
|
+
}
|
|
48
|
+
const branch = remoteHead.match(/^ref: refs\/heads\/(.+)\s+HEAD$/m)?.[1];
|
|
49
|
+
if (!branch)
|
|
50
|
+
throw new DeliverySourceError('DELIVERY_MAINLINE_UNKNOWN', '远端尚未指定可用的默认分支', '在远端仓库设置并推送默认主分支后重试');
|
|
51
|
+
try {
|
|
52
|
+
await git(root, ['check-ref-format', `refs/heads/${branch}`]);
|
|
53
|
+
await git(root, ['fetch', '--quiet', '--no-tags', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new DeliverySourceError('DELIVERY_MAINLINE_UNAVAILABLE', '无法刷新远端主分支', '恢复 origin 访问后重试', { branch });
|
|
57
|
+
}
|
|
58
|
+
const mainlineCommit = await git(root, ['rev-parse', `refs/remotes/origin/${branch}`]);
|
|
59
|
+
const currentBranch = await git(root, ['branch', '--show-current']);
|
|
60
|
+
if (currentBranch !== branch) {
|
|
61
|
+
throw new DeliverySourceError('DELIVERY_MAINLINE_CHECKOUT_REQUIRED', `请从远端默认主分支 ${branch} 发布`, '将本轮任务分支合入并推送主分支,然后在干净的主分支工作区发布;不要切换其他任务正在编辑的目录', { branch, currentBranch });
|
|
62
|
+
}
|
|
63
|
+
const commit = sourceCommit || currentCommit;
|
|
64
|
+
if (!SHA.test(commit))
|
|
65
|
+
throw new DeliverySourceError('DELIVERY_SOURCE_COMMIT_INVALID', '发布来源不是合法的 Git 提交', '重新读取原测试部署引用的版本');
|
|
66
|
+
try {
|
|
67
|
+
await git(root, ['merge-base', '--is-ancestor', commit, `refs/remotes/origin/${branch}`]);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
throw new DeliverySourceError('DELIVERY_SOURCE_NOT_IN_MAINLINE', '发布来源尚未进入远端主分支', '先将源码合入并推送远端主分支;若已 squash/rebase,使用最终主线提交重新验证和冻结', { commit, branch });
|
|
71
|
+
}
|
|
72
|
+
if (!sourceCommit && currentCommit !== mainlineCommit) {
|
|
73
|
+
throw new DeliverySourceError('DELIVERY_MAINLINE_BEHIND', '本地源码尚未包含远端主分支的最新改动', '在保留现有工作前提下同步主分支,并验证最终候选', { commit, mainlineCommit, branch });
|
|
74
|
+
}
|
|
75
|
+
return { repository, branch, commit, mainlineCommit };
|
|
76
|
+
}
|
|
77
|
+
/** 构建步骤不能改变被验证源码。平台切换并发仍由 DeploymentRun 管理。 */
|
|
78
|
+
export async function assertDeliverySourceUnchanged(root, source) {
|
|
79
|
+
const commit = await cleanCommit(root);
|
|
80
|
+
const branch = await git(root, ['branch', '--show-current']);
|
|
81
|
+
if (commit !== source.commit || branch !== source.branch) {
|
|
82
|
+
throw new DeliverySourceError('DELIVERY_SOURCE_CHANGED', '准备发布期间源码或分支发生变化', '保留已有制品与日志,重新核对本轮候选;不要把旧验证用于新源码', { expectedCommit: source.commit, commit });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=delivery-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery-source.js","sourceRoot":"","sources":["../src/delivery-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type DevkitResult } from 'openxiangda-contracts';
|
|
2
|
+
import type { OpenXiangdaApplicationServices } from './application-services.js';
|
|
3
|
+
import { type OperationProgressListener } from './operation-progress.js';
|
|
4
|
+
export interface DeploymentObservationOptions {
|
|
5
|
+
root?: string;
|
|
6
|
+
deploymentId?: string;
|
|
7
|
+
onProgress?: OperationProgressListener;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
pollIntervalMs?: number;
|
|
10
|
+
}
|
|
11
|
+
/** 只观察原运行;观察失败绝不重新提交、取消或修改平台状态。 */
|
|
12
|
+
export declare function observeDeployment(services: Pick<OpenXiangdaApplicationServices, 'deploymentStatus'>, initial: DevkitResult<unknown>, options?: DeploymentObservationOptions): Promise<DevkitResult<unknown>>;
|
|
13
|
+
export declare function watchDeployment(services: Pick<OpenXiangdaApplicationServices, 'deploymentStatus'>, options?: DeploymentObservationOptions): Promise<DevkitResult<unknown>>;
|
|
14
|
+
//# sourceMappingURL=deployment-observer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-observer.d.ts","sourceRoot":"","sources":["../src/deployment-observer.ts"],"names":[],"mappings":"AACA,OAAO,EAAuC,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAA+D,KAAK,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AAEtI,MAAM,WAAW,4BAA4B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAOD,mCAAmC;AACnC,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,IAAI,CAAC,8BAA8B,EAAE,kBAAkB,CAAC,EAClE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,EAC9B,OAAO,GAAE,4BAAiC,GACzC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAiDhC;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,8BAA8B,EAAE,kBAAkB,CAAC,EAAE,OAAO,GAAE,4BAAiC,kCAI7I"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { SCHEMA_VERSIONS } from 'openxiangda-contracts';
|
|
3
|
+
import { operationStage, updateOperationStage, withOperationProgress } from './operation-progress.js';
|
|
4
|
+
const labels = {
|
|
5
|
+
queued: '平台部署排队', preparing: '平台准备配置与运行环境', deploying: '平台部署应用',
|
|
6
|
+
activating: '平台切换应用版本', verifying: '平台检查运行健康',
|
|
7
|
+
succeeded: '平台部署完成', failed: '平台部署失败', cancelled: '平台部署已取消',
|
|
8
|
+
};
|
|
9
|
+
/** 只观察原运行;观察失败绝不重新提交、取消或修改平台状态。 */
|
|
10
|
+
export async function observeDeployment(services, initial, options = {}) {
|
|
11
|
+
if (!initial.ok || !isRun(initial.data))
|
|
12
|
+
return initial;
|
|
13
|
+
let latest = initial;
|
|
14
|
+
const started = Date.now();
|
|
15
|
+
return operationStage('platform', '跟踪平台部署运行', async () => {
|
|
16
|
+
let previous = '';
|
|
17
|
+
for (;;) {
|
|
18
|
+
const run = latest.data;
|
|
19
|
+
const fingerprint = `${run.status}:${run.stage}:${run.attempt}`;
|
|
20
|
+
if (fingerprint !== previous) {
|
|
21
|
+
updateOperationStage('platform', labels[run.status] || '跟踪平台部署运行', {
|
|
22
|
+
deploymentId: run.id, status: run.status, serverStage: run.stage, attempt: run.attempt,
|
|
23
|
+
});
|
|
24
|
+
previous = fingerprint;
|
|
25
|
+
}
|
|
26
|
+
if (run.status === 'succeeded')
|
|
27
|
+
return { ...latest, operation: initial.operation };
|
|
28
|
+
if (run.status === 'failed' || run.status === 'cancelled') {
|
|
29
|
+
const failure = run.latestFailure || run.failure || run.rootFailure;
|
|
30
|
+
return incomplete(failure?.code || 'DEPLOYMENT_CANCELLED', failure?.message || '部署运行已取消', failure?.retryable || false);
|
|
31
|
+
}
|
|
32
|
+
if (Date.now() - started >= (options.timeoutMs ?? 15 * 60_000)) {
|
|
33
|
+
return incomplete('DEPLOYMENT_OBSERVATION_TIMEOUT', '观察达到时限;平台运行仍保留,请继续查询原部署 ID', true);
|
|
34
|
+
}
|
|
35
|
+
await delay(Math.min(options.pollIntervalMs ?? 3_000, Math.max(0, (options.timeoutMs ?? 15 * 60_000) - (Date.now() - started))));
|
|
36
|
+
try {
|
|
37
|
+
const result = await services.deploymentStatus(options.root, run.id);
|
|
38
|
+
if (!result.ok || !isRun(result.data) || result.data.id !== run.id) {
|
|
39
|
+
return incomplete('DEPLOYMENT_OBSERVATION_UNAVAILABLE', '本次未能读取原部署状态;请继续查询原部署 ID', true);
|
|
40
|
+
}
|
|
41
|
+
latest = result;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return incomplete('DEPLOYMENT_OBSERVATION_UNAVAILABLE', '部署状态连接中断;平台运行可能仍在继续,请查询原部署 ID', true);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
function incomplete(code, message, retryable) {
|
|
49
|
+
const command = `openxiangda status ${latest.data.id} --watch`;
|
|
50
|
+
const terminal = ['failed', 'cancelled'].includes(latest.data.status);
|
|
51
|
+
return {
|
|
52
|
+
...latest, ok: false, operation: initial.operation,
|
|
53
|
+
diagnostics: [...(latest.diagnostics || []), {
|
|
54
|
+
schemaVersion: SCHEMA_VERSIONS.diagnostic, code, severity: 'error', message, retryable,
|
|
55
|
+
remediation: terminal ? '查看本次运行的日志与平台恢复建议后处理' : `运行 ${command},不需要重新构建或提交`,
|
|
56
|
+
details: { deploymentId: latest.data.id, status: latest.data.status, recovery: latest.data.recovery },
|
|
57
|
+
}],
|
|
58
|
+
nextActions: terminal ? latest.nextActions : [{ code: 'status', label: '继续跟踪原部署运行', command }],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function watchDeployment(services, options = {}) {
|
|
63
|
+
return withOperationProgress('status', options.onProgress, async () => observeDeployment(services, await services.deploymentStatus(options.root, options.deploymentId), options));
|
|
64
|
+
}
|
|
65
|
+
function isRun(value) {
|
|
66
|
+
return Boolean(value && typeof value === 'object' && 'id' in value && typeof value.id === 'string' && 'status' in value && typeof value.status === 'string');
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=deployment-observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-observer.js","sourceRoot":"","sources":["../src/deployment-observer.ts"],"names":[],"mappings":"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"}
|
package/dist/deployment.d.ts
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { DeploymentStrategy } from 'openxiangda-contracts';
|
|
2
|
+
import { CONFIGURATION_COMPATIBILITY_CAPABILITY, type ApplicationContractCompatibility, type ConfigurationValidationResult, type DeploymentEnvironment, type DeploymentRun, type PlatformCapabilities, type RequiredPlatformCapabilityContract, type RuntimeCapacityPreflight } from 'openxiangda-contracts';
|
|
2
3
|
import { type CreateDeploymentInput, type UploadArtifactInput } from './control-plane-client.js';
|
|
3
4
|
import { type CompiledAppPackage } from './package-compiler.js';
|
|
5
|
+
import { type ProductionPromotionPreflight } from 'openxiangda-contracts';
|
|
4
6
|
export interface DeploymentControlPlane {
|
|
5
7
|
capabilities(): Promise<PlatformCapabilities>;
|
|
8
|
+
artifactStatus?(appCode: string, digest: string): Promise<{
|
|
9
|
+
digest: string;
|
|
10
|
+
kind: string;
|
|
11
|
+
contentType: string;
|
|
12
|
+
sizeBytes: number;
|
|
13
|
+
} | null>;
|
|
6
14
|
uploadArtifact(input: UploadArtifactInput): Promise<Record<string, unknown>>;
|
|
7
15
|
createDeployment(input: CreateDeploymentInput): Promise<DeploymentRun>;
|
|
8
16
|
}
|
|
9
17
|
export interface SubmitAppPackageInput {
|
|
18
|
+
deploymentStrategy?: DeploymentStrategy;
|
|
10
19
|
client: DeploymentControlPlane;
|
|
11
20
|
capabilities?: PlatformCapabilities;
|
|
12
21
|
compiledPackage: CompiledAppPackage;
|
|
@@ -17,6 +26,13 @@ export interface SubmitAppPackageInput {
|
|
|
17
26
|
requestId?: string;
|
|
18
27
|
}
|
|
19
28
|
export declare function submitAppPackage(input: SubmitAppPackageInput): Promise<DeploymentRun>;
|
|
29
|
+
export declare function assertRuntimeCapacityPreflight(value: RuntimeCapacityPreflight, environmentId?: string, strategy?: DeploymentStrategy): void;
|
|
30
|
+
export declare function assertProductionPromotionPreflight(value: ProductionPromotionPreflight, expected: {
|
|
31
|
+
appCode: string;
|
|
32
|
+
sourceDeploymentId: string;
|
|
33
|
+
appVersionId: string;
|
|
34
|
+
packageDigest: string;
|
|
35
|
+
}): void;
|
|
20
36
|
export declare function assertApplicationContractCompatible(capabilities: PlatformCapabilities, required: ApplicationContractCompatibility): void;
|
|
21
37
|
export declare function assertConfigurationValidationResult(capabilities: PlatformCapabilities, required: ApplicationContractCompatibility, result: ConfigurationValidationResult, source: {
|
|
22
38
|
configurationDigest: string;
|
package/dist/deployment.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../src/deployment.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,sCAAsC,EAItC,KAAK,gCAAgC,EACrC,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,kCAAkC,
|
|
1
|
+
{"version":3,"file":"deployment.d.ts","sourceRoot":"","sources":["../src/deployment.ts"],"names":[],"mappings":"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"}
|
package/dist/deployment.js
CHANGED
|
@@ -2,10 +2,22 @@ import { canonicalJson, CONFIGURATION_COMPATIBILITY_CAPABILITY, contractSchemas,
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { Ajv2020 } from 'ajv/dist/2020.js';
|
|
4
4
|
import { ControlPlaneError, } from './control-plane-client.js';
|
|
5
|
+
import { operationStage, updateOperationStage } from './operation-progress.js';
|
|
6
|
+
import { NATIVE_CONFIGURATION_VALIDATOR_DIGEST } from 'openxiangda-contracts/native-compiler';
|
|
7
|
+
async function uploadOrReuse(client, input, stage, label) {
|
|
8
|
+
return operationStage(stage, label, async () => {
|
|
9
|
+
const stored = await client.artifactStatus?.(input.appCode, input.digest);
|
|
10
|
+
if (stored?.digest === input.digest && stored.kind === input.kind && stored.contentType === input.contentType && stored.sizeBytes === new Blob([input.content]).size) {
|
|
11
|
+
updateOperationStage(stage, `${label}(复用已有内容)`, { reused: true, digest: input.digest, bytes: stored.sizeBytes });
|
|
12
|
+
return stored;
|
|
13
|
+
}
|
|
14
|
+
return client.uploadArtifact(input);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
5
17
|
export async function submitAppPackage(input) {
|
|
6
18
|
const capabilities = input.capabilities || (await input.client.capabilities());
|
|
7
19
|
assertApplicationContractCompatible(capabilities, input.compiledPackage.manifest.compatibility.applicationContract);
|
|
8
|
-
await verifySealedAppPackage(input.compiledPackage, input.artifactContent || {});
|
|
20
|
+
await operationStage('artifacts-verify', '上传前校验制品完整性', () => verifySealedAppPackage(input.compiledPackage, input.artifactContent || {}));
|
|
9
21
|
const required = input.compiledPackage.manifest.compatibility
|
|
10
22
|
.requiredPlatformCapabilities;
|
|
11
23
|
assertRequiredCapabilitiesAvailable(capabilities, required);
|
|
@@ -15,7 +27,7 @@ export async function submitAppPackage(input) {
|
|
|
15
27
|
if (content === undefined) {
|
|
16
28
|
throw new ControlPlaneError(400, 'OPENXIANGDA_ARTIFACT_CONTENT_MISSING', `缺少制品内容: ${artifact.digest}`, { digest: artifact.digest, kind: artifact.kind });
|
|
17
29
|
}
|
|
18
|
-
await input.client
|
|
30
|
+
await uploadOrReuse(input.client, {
|
|
19
31
|
appCode: input.compiledPackage.manifest.appCode,
|
|
20
32
|
digest: artifact.digest,
|
|
21
33
|
kind: artifact.kind,
|
|
@@ -25,9 +37,9 @@ export async function submitAppPackage(input) {
|
|
|
25
37
|
appVersion: input.compiledPackage.manifest.version,
|
|
26
38
|
sourceCommit: input.compiledPackage.manifest.source.commit,
|
|
27
39
|
},
|
|
28
|
-
});
|
|
40
|
+
}, `upload-${artifact.kind}`, `上传${{ frontend: '前端', backend: '后端', config: '配置', contracts: '数据契约' }[artifact.kind] || artifact.kind}制品`);
|
|
29
41
|
}
|
|
30
|
-
await input.client
|
|
42
|
+
await uploadOrReuse(input.client, {
|
|
31
43
|
appCode: input.compiledPackage.manifest.appCode,
|
|
32
44
|
digest: input.compiledPackage.digest,
|
|
33
45
|
kind: 'manifest',
|
|
@@ -37,24 +49,46 @@ export async function submitAppPackage(input) {
|
|
|
37
49
|
appVersion: input.compiledPackage.manifest.version,
|
|
38
50
|
sourceCommit: input.compiledPackage.manifest.source.commit,
|
|
39
51
|
},
|
|
40
|
-
});
|
|
41
|
-
return await input.client.createDeployment({
|
|
52
|
+
}, 'upload-manifest', '上传版本清单');
|
|
53
|
+
return await operationStage('submit', '提交平台部署运行', () => input.client.createDeployment({
|
|
42
54
|
appCode: input.compiledPackage.manifest.appCode,
|
|
43
55
|
...(input.environmentId ? { environmentId: input.environmentId } : {}),
|
|
44
56
|
...(input.environmentKind
|
|
45
57
|
? { environmentKind: input.environmentKind }
|
|
46
58
|
: {}),
|
|
59
|
+
...(input.deploymentStrategy ? { deploymentStrategy: input.deploymentStrategy } : {}),
|
|
47
60
|
packageDigest: input.compiledPackage.digest,
|
|
48
61
|
package: input.compiledPackage.manifest,
|
|
49
62
|
idempotencyKey: input.idempotencyKey,
|
|
50
63
|
...(input.requestId ? { requestId: input.requestId } : {}),
|
|
51
|
-
});
|
|
64
|
+
}));
|
|
52
65
|
}
|
|
53
66
|
const validatePlatformCapabilities = new Ajv2020({
|
|
54
67
|
allErrors: true,
|
|
55
68
|
strict: false,
|
|
56
69
|
validateFormats: false,
|
|
57
70
|
}).compile(contractSchemas.platformCapabilities);
|
|
71
|
+
const validatePromotionPreflight = new Ajv2020({ allErrors: true, strict: false }).compile(contractSchemas.promotionPreflight);
|
|
72
|
+
const validateRuntimeCapacityPreflight = new Ajv2020({ allErrors: true, strict: false }).compile(contractSchemas.runtimeCapacityPreflight);
|
|
73
|
+
export function assertRuntimeCapacityPreflight(value, environmentId, strategy = 'rolling') {
|
|
74
|
+
if ((value.deploymentStrategy || 'rolling') !== strategy ||
|
|
75
|
+
(strategy === 'maintenance-replace' && value.basis !== 'existing-run' && (!value.maintenance || value.basis !== 'new-candidate')) ||
|
|
76
|
+
(strategy === 'rolling' && value.maintenance != null) || !validateRuntimeCapacityPreflight(value) || (environmentId && value.environmentId !== environmentId) ||
|
|
77
|
+
!Number.isFinite(Date.parse(value.observedAt)) ||
|
|
78
|
+
(value.capacity && (value.capacity.checked ? typeof value.sufficient !== 'boolean' : value.sufficient !== null)) ||
|
|
79
|
+
(value.sufficient === false && !value.capacity?.shortages.length) ||
|
|
80
|
+
(value.sufficient === true && value.capacity?.shortages.length)) {
|
|
81
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_RUNTIME_CAPACITY_PREFLIGHT_RESULT_INVALID', '平台运行配额预检结果无效或不属于指定环境', { pointer: '/runtime-capacity-preflight' });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function assertProductionPromotionPreflight(value, expected) {
|
|
85
|
+
if (!validatePromotionPreflight(value) || value.validatorDigest !== NATIVE_CONFIGURATION_VALIDATOR_DIGEST ||
|
|
86
|
+
Object.entries(expected).some(([key, expectedValue]) => value[key] !== expectedValue)) {
|
|
87
|
+
throw new ControlPlaneError(409, 'PRODUCTION_PREFLIGHT_RESULT_INVALID', '生产预检结果无法绑定指定测试版本或当前共享校验规则', {
|
|
88
|
+
pointer: '/promotion-preflight', expected, actual: value,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
58
92
|
const validateConfigurationValidationResult = new Ajv2020({
|
|
59
93
|
allErrors: true,
|
|
60
94
|
strict: false,
|
|
@@ -68,20 +102,26 @@ export function assertApplicationContractCompatible(capabilities, required) {
|
|
|
68
102
|
.filter((path, index, values) => values.indexOf(path) === index);
|
|
69
103
|
throw new ControlPlaneError(409, 'OPENXIANGDA_PLATFORM_CAPABILITIES_INVALID', '平台 capabilities 未提供有效的应用契约兼容信息', compatibilityDetails(capabilities, required, paths[0] || '/', {
|
|
70
104
|
paths,
|
|
71
|
-
remediation: '
|
|
72
|
-
}), { remediation: '
|
|
105
|
+
remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda check',
|
|
106
|
+
}), { remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda deploy' });
|
|
73
107
|
}
|
|
74
108
|
const descriptor = capabilities.configurationCompatibility;
|
|
109
|
+
if (descriptor.validatorDigest !== NATIVE_CONFIGURATION_VALIDATOR_DIGEST) {
|
|
110
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_CONFIGURATION_VALIDATOR_MISMATCH', '本地与目标平台的完整校验规则版本不一致,尚未开始构建或上传', compatibilityDetails(capabilities, required, '/configurationCompatibility/validatorDigest', {
|
|
111
|
+
expectedValidatorDigest: NATIVE_CONFIGURATION_VALIDATOR_DIGEST,
|
|
112
|
+
actualValidatorDigest: descriptor.validatorDigest,
|
|
113
|
+
}), { remediation: '升级工具链与目标平台到配套发布版本后重试,保留当前源码与候选' });
|
|
114
|
+
}
|
|
75
115
|
if (descriptor.capability.code !== CONFIGURATION_COMPATIBILITY_CAPABILITY ||
|
|
76
116
|
descriptor.capability.status !== 'available') {
|
|
77
|
-
throw new ControlPlaneError(409, 'OPENXIANGDA_CONFIGURATION_COMPATIBILITY_UNAVAILABLE', '目标平台未提供可用的配置兼容性预检能力', compatibilityDetails(capabilities, required, '/configurationCompatibility/capability'), { remediation: '
|
|
117
|
+
throw new ControlPlaneError(409, 'OPENXIANGDA_CONFIGURATION_COMPATIBILITY_UNAVAILABLE', '目标平台未提供可用的配置兼容性预检能力', compatibilityDetails(capabilities, required, '/configurationCompatibility/capability'), { remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda check' });
|
|
78
118
|
}
|
|
79
119
|
const accepted = descriptor.supportedApplicationContracts;
|
|
80
120
|
const matched = accepted.some(candidate => applicationContractsEqual(candidate, required));
|
|
81
121
|
if (!matched) {
|
|
82
122
|
throw new ControlPlaneError(409, 'OPENXIANGDA_APPLICATION_CONTRACT_UNSUPPORTED', '平台不接受当前工具链生成的应用契约', compatibilityDetails(capabilities, required, '/', {
|
|
83
|
-
remediation: '
|
|
84
|
-
}), { remediation: '
|
|
123
|
+
remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda deploy',
|
|
124
|
+
}), { remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda deploy' });
|
|
85
125
|
}
|
|
86
126
|
}
|
|
87
127
|
export function assertConfigurationValidationResult(capabilities, required, result, source, requiredPlatformCapabilities) {
|
|
@@ -111,7 +151,7 @@ export function assertConfigurationValidationResult(capabilities, required, resu
|
|
|
111
151
|
actualSource: result?.source,
|
|
112
152
|
expectedRequiredPlatformCapabilities: requiredPlatformCapabilities,
|
|
113
153
|
actualRequiredPlatformCapabilities: result?.requiredPlatformCapabilities,
|
|
114
|
-
}), { remediation: '
|
|
154
|
+
}), { remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda check' });
|
|
115
155
|
}
|
|
116
156
|
}
|
|
117
157
|
export function compatibilityDetails(capabilities, required, pointer = '/', extra = {}) {
|
|
@@ -168,8 +208,8 @@ export function assertRequiredCapabilitiesAvailable(capabilities, required) {
|
|
|
168
208
|
.map(item => item.code)
|
|
169
209
|
.join(',')}`, {
|
|
170
210
|
unavailable,
|
|
171
|
-
remediation: '
|
|
172
|
-
}, { remediation: '
|
|
211
|
+
remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda deploy',
|
|
212
|
+
}, { remediation: '核对项目工具与目标平台的配套发布版本和能力启用状态,升级不匹配的一端后重试 openxiangda deploy' });
|
|
173
213
|
}
|
|
174
214
|
}
|
|
175
215
|
/**
|