openxiangda 1.0.231 → 1.0.232
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/README.md +27 -2
- package/lib/cli.js +219 -29
- package/lib/delivery-v2-cli.js +216 -0
- package/lib/delivery-v2-executor.js +955 -0
- package/lib/delivery-v2-package.js +681 -0
- package/lib/policy.js +4 -0
- package/lib/workspace-bootstrap.js +1 -0
- package/lib/workspace-init.js +4 -20
- package/openxiangda-skills/SKILL.md +21 -1
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +17 -0
- package/package.json +4 -2
- package/packages/sdk/dist/build/index.cjs.map +1 -1
- package/packages/sdk/dist/build/index.d.mts +1 -0
- package/packages/sdk/dist/build/index.d.ts +1 -0
- package/packages/sdk/dist/build/index.mjs.map +1 -1
- package/policy/openxiangda-policy.json +8 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +6 -5
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +6 -6
- package/templates/openxiangda-react-spa/AGENTS.md +5 -1
- package/templates/openxiangda-react-spa/DELIVERY.md +33 -0
- package/templates/openxiangda-react-spa/app-workspace.config.ts +3 -3
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +4 -1
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +4 -1
- package/templates/sy-lowcode-app-workspace/AGENTS.md +7 -4
- package/templates/sy-lowcode-app-workspace/DELIVERY.md +33 -0
- package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +3 -3
- package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +1 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- OpenXiangda-Policy-Version:
|
|
1
|
+
<!-- OpenXiangda-Policy-Version: 5 -->
|
|
2
2
|
# OpenXiangda
|
|
3
3
|
|
|
4
4
|
OpenXiangda is a lightweight CLI and skill package for private low-code platforms.
|
|
@@ -7,6 +7,30 @@ Normal OpenXiangda app development uses platform-user login tokens through `/ope
|
|
|
7
7
|
|
|
8
8
|
Private platform routing is fixed: backend APIs are under `/service`, platform management is under `/platform`, and app runtime access is under `/view`. Passing a root domain such as `https://yida.wisejob.cn/` to the CLI is supported; OpenXiangda stores the API base as `https://yida.wisejob.cn/service`.
|
|
9
9
|
|
|
10
|
+
## Delivery V2
|
|
11
|
+
|
|
12
|
+
New workspaces declare `deliveryVersion: 2`. The release flow is intentionally
|
|
13
|
+
small and deterministic:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
openxiangda check --environment preproduction
|
|
17
|
+
openxiangda deploy preproduction --json
|
|
18
|
+
openxiangda deploy production --package <packageDigest> --json
|
|
19
|
+
openxiangda status <runId> --json
|
|
20
|
+
openxiangda retry <runId> --json
|
|
21
|
+
openxiangda rollback production --to <appReleaseId> --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The preproduction deploy compiles one content-addressed App Package containing
|
|
25
|
+
source, form, configuration, backend, workflow, and Runtime layers. Production
|
|
26
|
+
downloads that exact package and does not rebuild it. ReleaseRun state and
|
|
27
|
+
checkpoints live on the platform, so a CLI/AI interruption does not discard
|
|
28
|
+
completed work. V2 does not require SDD, a clean/pushed Git mainline,
|
|
29
|
+
`--change`, `--only`, or candidate/ship orchestration.
|
|
30
|
+
|
|
31
|
+
The lower-level commands later in this README remain available for V1
|
|
32
|
+
compatibility and diagnostics; do not use them to assemble a normal V2 release.
|
|
33
|
+
|
|
10
34
|
```bash
|
|
11
35
|
npm install -g openxiangda@latest --registry=https://registry.npmjs.org
|
|
12
36
|
openxiangda update check
|
|
@@ -22,7 +46,8 @@ openxiangda env
|
|
|
22
46
|
openxiangda workspace init ./my-app-workspace --profile dev --app-name "示例应用"
|
|
23
47
|
cd ./my-app-workspace
|
|
24
48
|
pnpm install
|
|
25
|
-
openxiangda
|
|
49
|
+
openxiangda check --environment preproduction
|
|
50
|
+
openxiangda deploy preproduction --json
|
|
26
51
|
openxiangda menu list --profile dev
|
|
27
52
|
openxiangda workflow list --profile dev
|
|
28
53
|
openxiangda automation list --profile dev
|
package/lib/cli.js
CHANGED
|
@@ -166,6 +166,7 @@ const {
|
|
|
166
166
|
withReleaseClientSessionArgs,
|
|
167
167
|
} = require('./application-environments');
|
|
168
168
|
const { startDeveloperCenter } = require('./developer-center');
|
|
169
|
+
const { createDeliveryV2Cli } = require('./delivery-v2-cli');
|
|
169
170
|
const {
|
|
170
171
|
describeOpenApiOperation,
|
|
171
172
|
listOpenApiOperations,
|
|
@@ -219,6 +220,8 @@ let readOnlyHttpGuardLabel = null;
|
|
|
219
220
|
let activePublishRequestContext = null;
|
|
220
221
|
let activePublishLeaseHeartbeat = null;
|
|
221
222
|
let activeCommandScopedPublishLease = null;
|
|
223
|
+
let activeDeliveryV2Context = null;
|
|
224
|
+
let deliveryV2Cli = null;
|
|
222
225
|
const genericGitBasePlanCache = new Map();
|
|
223
226
|
|
|
224
227
|
async function main(argv) {
|
|
@@ -270,6 +273,9 @@ async function mainImpl(argv) {
|
|
|
270
273
|
if (command === 'design') return design(rest);
|
|
271
274
|
if (command === 'sdd') return sdd(rest);
|
|
272
275
|
if (command === 'environment') return environment(rest);
|
|
276
|
+
if (['check', 'deploy', 'status', 'retry', 'rollback'].includes(command)) {
|
|
277
|
+
return getDeliveryV2Cli().run(command, rest);
|
|
278
|
+
}
|
|
273
279
|
if (command === 'release') return release(rest);
|
|
274
280
|
if (command === 'studio') return studio(rest);
|
|
275
281
|
if (command === 'task') return task(rest);
|
|
@@ -321,6 +327,11 @@ Usage:
|
|
|
321
327
|
openxiangda design gates|template|review [--topic code] [--json]
|
|
322
328
|
openxiangda sdd init|migrate|propose|quick|bundle|approve|status|context|verify|sync|archive [change] [--change id] [--json]
|
|
323
329
|
openxiangda environment init|attach|bind|swap|policy update|list|status|diff|use [preproduction|production] [--logical-app code] [--environment target] [--json]
|
|
330
|
+
openxiangda check [--environment preproduction|production] [--json]
|
|
331
|
+
openxiangda deploy <preproduction|production> [--package <sha256>] [--profile name] [--json]
|
|
332
|
+
openxiangda status <runId> [--environment preproduction|production] [--json]
|
|
333
|
+
openxiangda retry <runId> [--environment preproduction|production] [--json]
|
|
334
|
+
openxiangda rollback <preproduction|production> --to <appReleaseId> [--json]
|
|
324
335
|
openxiangda release publish|begin|status|integration-status|renew|end [--change id] [--profile name] [--watch] [--json]
|
|
325
336
|
openxiangda release ship|candidate|deploy|test|promote|rollback [--candidate id] [--environment target] [--replace-manifest --reason text] [--adopt-online-baseline --adoption-reason text] [--json]
|
|
326
337
|
openxiangda task status --change <id> [--profile name] [--watch] [--json]
|
|
@@ -1451,6 +1462,120 @@ async function runOpenXiangdaInProcess(args, options = {}) {
|
|
|
1451
1462
|
}
|
|
1452
1463
|
}
|
|
1453
1464
|
|
|
1465
|
+
function getDeliveryV2Cli() {
|
|
1466
|
+
if (deliveryV2Cli) return deliveryV2Cli;
|
|
1467
|
+
deliveryV2Cli = createDeliveryV2Cli({
|
|
1468
|
+
clearDeliveryContext: clearDeliveryV2Context,
|
|
1469
|
+
downloadWithAuth,
|
|
1470
|
+
getWorkspaceTarget,
|
|
1471
|
+
installDeliveryContext: installDeliveryV2Context,
|
|
1472
|
+
loadConfig,
|
|
1473
|
+
requestFormWithAuth,
|
|
1474
|
+
requestWithAuth,
|
|
1475
|
+
runOpenXiangdaInProcess,
|
|
1476
|
+
});
|
|
1477
|
+
return deliveryV2Cli;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function deliveryV2SourceRevision(packageDigest) {
|
|
1481
|
+
return {
|
|
1482
|
+
repo: `sha256:${packageDigest}`,
|
|
1483
|
+
repositoryId: `sha256:${packageDigest}`,
|
|
1484
|
+
repoAliases: [`sha256:${packageDigest}`],
|
|
1485
|
+
baseCommit: packageDigest,
|
|
1486
|
+
treeHash: packageDigest,
|
|
1487
|
+
branch: 'delivery-v2',
|
|
1488
|
+
mainBranch: null,
|
|
1489
|
+
remoteName: null,
|
|
1490
|
+
remoteUrlHash: null,
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function installDeliveryV2Context(target, run, targets = {}) {
|
|
1495
|
+
const control = run?.control || {};
|
|
1496
|
+
const publishLeaseId = String(control.publishLeaseId || '').trim();
|
|
1497
|
+
const baselineId = String(control.baselineId || '').trim();
|
|
1498
|
+
const changeId = String(control.changeId || '').trim();
|
|
1499
|
+
const clientSessionId = String(control.clientSessionId || '').trim();
|
|
1500
|
+
const packageDigest = String(run?.packageDigest || '').trim();
|
|
1501
|
+
if (
|
|
1502
|
+
!publishLeaseId ||
|
|
1503
|
+
!baselineId ||
|
|
1504
|
+
!changeId ||
|
|
1505
|
+
!clientSessionId ||
|
|
1506
|
+
!/^[a-f0-9]{64}$/.test(packageDigest)
|
|
1507
|
+
) {
|
|
1508
|
+
fail(
|
|
1509
|
+
'DELIVERY_CONTROL_INVALID: ReleaseRun 缺少 publishLease/baseline/change/session/package 控制信息'
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const sourceRevision = deliveryV2SourceRevision(packageDigest);
|
|
1513
|
+
saveChangeBaseline(target, {
|
|
1514
|
+
baselineId,
|
|
1515
|
+
appType: target.appType,
|
|
1516
|
+
profile: target.profileName,
|
|
1517
|
+
changeId,
|
|
1518
|
+
clientSessionId,
|
|
1519
|
+
sourceBase: {
|
|
1520
|
+
repo: sourceRevision.repo,
|
|
1521
|
+
baseCommit: packageDigest,
|
|
1522
|
+
treeHash: packageDigest,
|
|
1523
|
+
},
|
|
1524
|
+
releaseSourceRevision: sourceRevision,
|
|
1525
|
+
resourceHeads: {},
|
|
1526
|
+
lifecycleState: 'active',
|
|
1527
|
+
});
|
|
1528
|
+
savePublishLease(target, {
|
|
1529
|
+
leaseId: publishLeaseId,
|
|
1530
|
+
expiresAt:
|
|
1531
|
+
control.leaseExpiresAt ||
|
|
1532
|
+
new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
1533
|
+
appType: target.appType,
|
|
1534
|
+
profile: target.profileName,
|
|
1535
|
+
changeId,
|
|
1536
|
+
clientSessionId,
|
|
1537
|
+
baseRevision: packageDigest,
|
|
1538
|
+
holder: 'self',
|
|
1539
|
+
workspace: {
|
|
1540
|
+
branch: 'delivery-v2',
|
|
1541
|
+
commit: packageDigest,
|
|
1542
|
+
treeHash: packageDigest,
|
|
1543
|
+
},
|
|
1544
|
+
});
|
|
1545
|
+
activeDeliveryV2Context = {
|
|
1546
|
+
runId: run.id,
|
|
1547
|
+
appType: target.appType,
|
|
1548
|
+
profileName: target.profileName,
|
|
1549
|
+
packageDigest,
|
|
1550
|
+
publishLeaseId,
|
|
1551
|
+
baselineId,
|
|
1552
|
+
changeId,
|
|
1553
|
+
clientSessionId,
|
|
1554
|
+
leaseExpiresAt: control.leaseExpiresAt,
|
|
1555
|
+
sourceRevision,
|
|
1556
|
+
targets,
|
|
1557
|
+
};
|
|
1558
|
+
activePublishRequestContext = normalizePublishContext({
|
|
1559
|
+
appType: target.appType,
|
|
1560
|
+
profileName: target.profileName,
|
|
1561
|
+
publishLeaseId,
|
|
1562
|
+
changeBaselineId: baselineId,
|
|
1563
|
+
changeId,
|
|
1564
|
+
clientSessionId,
|
|
1565
|
+
});
|
|
1566
|
+
return activeDeliveryV2Context;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function clearDeliveryV2Context(target, run) {
|
|
1570
|
+
const context = activeDeliveryV2Context;
|
|
1571
|
+
if (!context) return;
|
|
1572
|
+
if (run?.id && context.runId !== run.id) return;
|
|
1573
|
+
clearPublishLease(target, context.publishLeaseId);
|
|
1574
|
+
clearChangeBaseline(target, context.baselineId);
|
|
1575
|
+
activePublishRequestContext = null;
|
|
1576
|
+
activeDeliveryV2Context = null;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1454
1579
|
function normalizeReleaseWaitSeconds(flags) {
|
|
1455
1580
|
const raw =
|
|
1456
1581
|
readStringFlag(flags, 'wait-seconds') ||
|
|
@@ -6478,22 +6603,35 @@ function assertStagedAppReleaseScope(target, flags, resources) {
|
|
|
6478
6603
|
}
|
|
6479
6604
|
const stagedContext = assertStagedResourcesContext(target, changeId);
|
|
6480
6605
|
let scope;
|
|
6481
|
-
|
|
6482
|
-
scope =
|
|
6483
|
-
cwd: process.cwd(),
|
|
6484
|
-
configText: readWorkspaceConfigText(),
|
|
6606
|
+
if (activeDeliveryV2Context?.changeId === changeId) {
|
|
6607
|
+
scope = {
|
|
6485
6608
|
changeId,
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6609
|
+
targets: activeDeliveryV2Context.targets || {},
|
|
6610
|
+
changedFiles: [],
|
|
6611
|
+
deliveryRunId: activeDeliveryV2Context.runId,
|
|
6612
|
+
};
|
|
6613
|
+
} else {
|
|
6614
|
+
try {
|
|
6615
|
+
scope = getSddChangeScope({
|
|
6616
|
+
cwd: process.cwd(),
|
|
6617
|
+
configText: readWorkspaceConfigText(),
|
|
6490
6618
|
changeId,
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6619
|
+
});
|
|
6620
|
+
} catch (error) {
|
|
6621
|
+
if (
|
|
6622
|
+
/SDD 未初始化|change 不存在|ENOENT/i.test(
|
|
6623
|
+
String(error?.message || '')
|
|
6624
|
+
)
|
|
6625
|
+
) {
|
|
6626
|
+
return {
|
|
6627
|
+
changeId,
|
|
6628
|
+
scope: null,
|
|
6629
|
+
resources: normalizeStagedAppReleaseResources(resources),
|
|
6630
|
+
compatibility: 'unmanaged_sdd_scope',
|
|
6631
|
+
};
|
|
6632
|
+
}
|
|
6633
|
+
throw error;
|
|
6495
6634
|
}
|
|
6496
|
-
throw error;
|
|
6497
6635
|
}
|
|
6498
6636
|
const runtimeMode = getWorkspaceRuntimeModeFromConfig(
|
|
6499
6637
|
readWorkspaceConfigText()
|
|
@@ -6799,6 +6937,7 @@ function appReleaseEnvironmentControl(flags = {}) {
|
|
|
6799
6937
|
environmentId: readStringFlag(flags, 'environment-id') || undefined,
|
|
6800
6938
|
candidateId: readStringFlag(flags, 'candidate-id') || undefined,
|
|
6801
6939
|
deploymentId: readStringFlag(flags, 'deployment-id') || undefined,
|
|
6940
|
+
deliveryRunId: readStringFlag(flags, 'delivery-run-id') || undefined,
|
|
6802
6941
|
});
|
|
6803
6942
|
}
|
|
6804
6943
|
|
|
@@ -7528,6 +7667,30 @@ async function renewPublishLease(config, target, lease, flags = {}) {
|
|
|
7528
7667
|
}
|
|
7529
7668
|
|
|
7530
7669
|
async function resolvePublishLeaseForWrite(config, target, flags = {}) {
|
|
7670
|
+
if (
|
|
7671
|
+
activeDeliveryV2Context?.appType === target.appType &&
|
|
7672
|
+
activeDeliveryV2Context?.profileName === target.profileName
|
|
7673
|
+
) {
|
|
7674
|
+
activePublishRequestContext = normalizePublishContext({
|
|
7675
|
+
appType: target.appType,
|
|
7676
|
+
profileName: target.profileName,
|
|
7677
|
+
publishLeaseId: activeDeliveryV2Context.publishLeaseId,
|
|
7678
|
+
changeBaselineId: activeDeliveryV2Context.baselineId,
|
|
7679
|
+
changeId: activeDeliveryV2Context.changeId,
|
|
7680
|
+
clientSessionId: activeDeliveryV2Context.clientSessionId,
|
|
7681
|
+
});
|
|
7682
|
+
return {
|
|
7683
|
+
leaseId: activeDeliveryV2Context.publishLeaseId,
|
|
7684
|
+
expiresAt: activeDeliveryV2Context.leaseExpiresAt,
|
|
7685
|
+
appType: target.appType,
|
|
7686
|
+
profile: target.profileName,
|
|
7687
|
+
changeId: activeDeliveryV2Context.changeId,
|
|
7688
|
+
clientSessionId: activeDeliveryV2Context.clientSessionId,
|
|
7689
|
+
baseRevision: activeDeliveryV2Context.packageDigest,
|
|
7690
|
+
holder: 'self',
|
|
7691
|
+
explicit: true,
|
|
7692
|
+
};
|
|
7693
|
+
}
|
|
7531
7694
|
const explicitLeaseId = readStringFlag(flags, 'publish-lease-id');
|
|
7532
7695
|
const changeId = readStringFlag(flags, 'change');
|
|
7533
7696
|
const storedLease = getStoredPublishLease(target);
|
|
@@ -14807,7 +14970,9 @@ async function runtime(args) {
|
|
|
14807
14970
|
// Preserve Runtime's dedicated dirty-source contract before the generic
|
|
14808
14971
|
// frozen-release revision check runs inside lease resolution. The release
|
|
14809
14972
|
// freeze and clean-HEAD gates still run immediately afterwards.
|
|
14810
|
-
|
|
14973
|
+
if (!activeDeliveryV2Context) {
|
|
14974
|
+
assertRuntimeSourceClean(process.cwd());
|
|
14975
|
+
}
|
|
14811
14976
|
// Keep build after lease acquisition. In direct-storage modes the build
|
|
14812
14977
|
// embeds the provider-specific assetBaseUrl returned by the protected
|
|
14813
14978
|
// upload plan; substituting the gateway URL would change chunk/publicPath
|
|
@@ -14819,13 +14984,26 @@ async function runtime(args) {
|
|
|
14819
14984
|
const runtimeHead = await telemetry.runPhase('head', async () =>
|
|
14820
14985
|
loadRuntimeHeadApp(config, target)
|
|
14821
14986
|
);
|
|
14822
|
-
const runtimeLineage =
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14987
|
+
const runtimeLineage = activeDeliveryV2Context
|
|
14988
|
+
? {
|
|
14989
|
+
sourceRevision: activeDeliveryV2Context.sourceRevision,
|
|
14990
|
+
parentReleaseId: runtimeHead?.activeRuntimeReleaseId || null,
|
|
14991
|
+
parentSourceRevision:
|
|
14992
|
+
runtimeHead?.activeRuntimeSourceRevision || null,
|
|
14993
|
+
lineageBypassReason: null,
|
|
14994
|
+
lineageStatus: 'delivery-v2-package',
|
|
14995
|
+
legacyBootstrap: Boolean(
|
|
14996
|
+
runtimeHead?.activeRuntimeReleaseId &&
|
|
14997
|
+
!runtimeHead?.activeRuntimeSourceRevision
|
|
14998
|
+
),
|
|
14999
|
+
}
|
|
15000
|
+
: prepareRuntimeDeployLineage({
|
|
15001
|
+
cwd: process.cwd(),
|
|
15002
|
+
app: runtimeHead,
|
|
15003
|
+
activate: !flags['no-activate'],
|
|
15004
|
+
allowRollback: flags['allow-runtime-rollback'],
|
|
15005
|
+
reason: readStringFlag(flags, 'reason'),
|
|
15006
|
+
});
|
|
14829
15007
|
if (runtimeLineage.lineageStatus === 'rollback-bypass') {
|
|
14830
15008
|
warn(
|
|
14831
15009
|
`[RUNTIME_LINEAGE_BYPASS] 已允许审计式 Runtime 源码回退: reason=${runtimeLineage.lineageBypassReason}`
|
|
@@ -14904,13 +15082,15 @@ async function runtime(args) {
|
|
|
14904
15082
|
jsonOutput: Boolean(flags.json),
|
|
14905
15083
|
})
|
|
14906
15084
|
);
|
|
14907
|
-
const postBuildLineage =
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
15085
|
+
const postBuildLineage = activeDeliveryV2Context
|
|
15086
|
+
? runtimeLineage
|
|
15087
|
+
: prepareRuntimeDeployLineage({
|
|
15088
|
+
cwd: process.cwd(),
|
|
15089
|
+
app: runtimeHead,
|
|
15090
|
+
activate: !flags['no-activate'],
|
|
15091
|
+
allowRollback: flags['allow-runtime-rollback'],
|
|
15092
|
+
reason: readStringFlag(flags, 'reason'),
|
|
15093
|
+
});
|
|
14914
15094
|
assertRuntimeSourceRevisionStable(
|
|
14915
15095
|
runtimeLineage.sourceRevision,
|
|
14916
15096
|
postBuildLineage.sourceRevision
|
|
@@ -21427,6 +21607,14 @@ async function runResourceChangeBaselinePreflight(
|
|
|
21427
21607
|
flags = {},
|
|
21428
21608
|
publishLease = null
|
|
21429
21609
|
) {
|
|
21610
|
+
if (activeDeliveryV2Context) {
|
|
21611
|
+
return {
|
|
21612
|
+
ok: true,
|
|
21613
|
+
baselineId: activeDeliveryV2Context.baselineId,
|
|
21614
|
+
checkedTargets: [],
|
|
21615
|
+
skipped: 'trusted-delivery-v2-package',
|
|
21616
|
+
};
|
|
21617
|
+
}
|
|
21430
21618
|
const onlineBaselineAdoption = resolveOnlineBaselineAdoption(
|
|
21431
21619
|
flags,
|
|
21432
21620
|
planActions,
|
|
@@ -28690,6 +28878,7 @@ function buildExplicitSddReleasePlan(targets, options = {}) {
|
|
|
28690
28878
|
}
|
|
28691
28879
|
|
|
28692
28880
|
function assertSddReleaseGate(plan, flags, command) {
|
|
28881
|
+
if (activeDeliveryV2Context) return;
|
|
28693
28882
|
if (!plan?.sdd || plan.sdd.errors.length === 0) return;
|
|
28694
28883
|
const nonBypassable = new Set([
|
|
28695
28884
|
'sdd-release-direct-form-schema-forbidden',
|
|
@@ -28980,7 +29169,8 @@ function isPublishMutationRequest(apiPath, options = {}) {
|
|
|
28980
29169
|
if (
|
|
28981
29170
|
normalizedPath.includes('/publish-lease/') ||
|
|
28982
29171
|
normalizedPath.endsWith('/publish-lease/acquire') ||
|
|
28983
|
-
normalizedPath.includes('/change-baselines')
|
|
29172
|
+
normalizedPath.includes('/change-baselines') ||
|
|
29173
|
+
normalizedPath.includes('/delivery/')
|
|
28984
29174
|
) {
|
|
28985
29175
|
return false;
|
|
28986
29176
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const { createDeliveryV2Executor } = require('./delivery-v2-executor');
|
|
3
|
+
const { parseArgs, print, warn, writeJson } = require('./utils');
|
|
4
|
+
|
|
5
|
+
function createDeliveryV2Cli(dependencies) {
|
|
6
|
+
const deps = assertDependencies(dependencies);
|
|
7
|
+
const executor = createDeliveryV2Executor(deps);
|
|
8
|
+
|
|
9
|
+
async function run(command, argv = []) {
|
|
10
|
+
const { flags, positional } = parseArgs(argv);
|
|
11
|
+
if (flags.help || flags.h) {
|
|
12
|
+
printHelp(command);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const config = deps.loadConfig();
|
|
16
|
+
if (command === 'check') {
|
|
17
|
+
const target = resolveTarget(config, flags, positional, false);
|
|
18
|
+
const inspected = await executor.inspect({
|
|
19
|
+
config,
|
|
20
|
+
target,
|
|
21
|
+
workspaceRoot: process.cwd(),
|
|
22
|
+
skipBuild: flags.build !== true,
|
|
23
|
+
buildCommand: stringFlag(flags, 'build-command'),
|
|
24
|
+
distDir: stringFlag(flags, 'dist'),
|
|
25
|
+
onBuildOutput: buildOutput(flags),
|
|
26
|
+
});
|
|
27
|
+
return output(
|
|
28
|
+
flags,
|
|
29
|
+
{
|
|
30
|
+
ok: true,
|
|
31
|
+
appType: target.appType,
|
|
32
|
+
environment: target.targetName || target.environmentKind || null,
|
|
33
|
+
packageDigest: inspected.compiled.packageDigest,
|
|
34
|
+
summary: inspected.compiled.summary,
|
|
35
|
+
targets: inspected.targets,
|
|
36
|
+
previousPackageDigest: inspected.previous?.packageDigest || null,
|
|
37
|
+
},
|
|
38
|
+
`Delivery V2 检查通过: ${inspected.compiled.packageDigest}`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (command === 'deploy') {
|
|
42
|
+
const target = resolveTarget(config, flags, positional, true);
|
|
43
|
+
const packageDigest = stringFlag(flags, 'package');
|
|
44
|
+
const result = await executor.deploy({
|
|
45
|
+
config,
|
|
46
|
+
target,
|
|
47
|
+
workspaceRoot: process.cwd(),
|
|
48
|
+
packageDigest,
|
|
49
|
+
buildCommand: stringFlag(flags, 'build-command'),
|
|
50
|
+
distDir: stringFlag(flags, 'dist'),
|
|
51
|
+
clientSessionId: createClientSessionId(),
|
|
52
|
+
onBuildOutput: buildOutput(flags),
|
|
53
|
+
});
|
|
54
|
+
return output(
|
|
55
|
+
flags,
|
|
56
|
+
result,
|
|
57
|
+
`Delivery V2 发布成功: run=${result.run.id} package=${result.packageDigest}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (command === 'status') {
|
|
61
|
+
const runId = positional[0] || stringFlag(flags, 'run');
|
|
62
|
+
if (!runId) {
|
|
63
|
+
throw usageError('用法: openxiangda status <runId> [--json]');
|
|
64
|
+
}
|
|
65
|
+
const target = resolveTarget(config, flags, [], false);
|
|
66
|
+
const result = await executor.status({ config, target, runId });
|
|
67
|
+
return output(
|
|
68
|
+
flags,
|
|
69
|
+
result,
|
|
70
|
+
`Delivery V2: run=${result.id} status=${result.status} stage=${result.stage}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (command === 'retry') {
|
|
74
|
+
const runId = positional[0] || stringFlag(flags, 'run');
|
|
75
|
+
if (!runId) {
|
|
76
|
+
throw usageError('用法: openxiangda retry <runId> [--json]');
|
|
77
|
+
}
|
|
78
|
+
const target = resolveTarget(config, flags, [], false);
|
|
79
|
+
const result = await executor.retry({
|
|
80
|
+
config,
|
|
81
|
+
target,
|
|
82
|
+
runId,
|
|
83
|
+
workspaceRoot: process.cwd(),
|
|
84
|
+
});
|
|
85
|
+
return output(
|
|
86
|
+
flags,
|
|
87
|
+
result,
|
|
88
|
+
`Delivery V2 重试成功: run=${result.run.id} status=${result.run.status}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (command === 'rollback') {
|
|
92
|
+
const target = resolveTarget(config, flags, positional, true);
|
|
93
|
+
const releaseId = stringFlag(flags, 'to');
|
|
94
|
+
if (!releaseId) {
|
|
95
|
+
throw usageError(
|
|
96
|
+
'用法: openxiangda rollback <environment> --to <appReleaseId>'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
const result = await executor.rollback({
|
|
100
|
+
config,
|
|
101
|
+
target,
|
|
102
|
+
releaseId,
|
|
103
|
+
workspaceRoot: process.cwd(),
|
|
104
|
+
clientSessionId: createClientSessionId(),
|
|
105
|
+
});
|
|
106
|
+
return output(
|
|
107
|
+
flags,
|
|
108
|
+
result,
|
|
109
|
+
`Delivery V2 回滚成功: run=${result.run.id} package=${result.packageDigest}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
throw usageError(`未知 Delivery V2 命令: ${command}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveTarget(config, flags, positional, environmentRequired) {
|
|
116
|
+
const environment =
|
|
117
|
+
positional[0] ||
|
|
118
|
+
stringFlag(flags, 'environment') ||
|
|
119
|
+
stringFlag(flags, 'target');
|
|
120
|
+
if (environmentRequired && !environment) {
|
|
121
|
+
throw usageError(
|
|
122
|
+
'必须指定环境,例如 openxiangda deploy preproduction'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return deps.getWorkspaceTarget(
|
|
126
|
+
config,
|
|
127
|
+
stringFlag(flags, 'profile') || config.currentProfile,
|
|
128
|
+
{
|
|
129
|
+
...flags,
|
|
130
|
+
...(environment ? { environment } : {}),
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { run };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function output(flags, value, message) {
|
|
139
|
+
if (flags.json) return writeJson(value);
|
|
140
|
+
print(message);
|
|
141
|
+
if (value?.run?.failure?.code) {
|
|
142
|
+
warn(
|
|
143
|
+
`${value.run.failure.code}: ${value.run.failure.message || '发布失败'}`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildOutput(flags) {
|
|
150
|
+
if (flags.json || flags.quiet) return undefined;
|
|
151
|
+
return (stream, text) => {
|
|
152
|
+
if (stream === 'stderr') warn(text.trimEnd());
|
|
153
|
+
else print(text.trimEnd());
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function stringFlag(flags, name) {
|
|
158
|
+
const value = flags?.[name];
|
|
159
|
+
if (value === undefined || value === null || value === false) return '';
|
|
160
|
+
return String(value).trim();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createClientSessionId() {
|
|
164
|
+
return `delivery-v2-${process.pid}-${crypto
|
|
165
|
+
.randomBytes(10)
|
|
166
|
+
.toString('hex')}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function usageError(message) {
|
|
170
|
+
const error = new Error(message);
|
|
171
|
+
error.code = 'DELIVERY_USAGE_ERROR';
|
|
172
|
+
error.retryable = false;
|
|
173
|
+
return error;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function printHelp(command) {
|
|
177
|
+
const lines = {
|
|
178
|
+
check: [
|
|
179
|
+
'用法: openxiangda check [--environment <name>] [--build] [--json]',
|
|
180
|
+
'编译并校验 App Package V2;默认复用现有 dist,不执行 Runtime 构建。',
|
|
181
|
+
],
|
|
182
|
+
deploy: [
|
|
183
|
+
'用法: openxiangda deploy <environment> [--package <sha256>] [--json]',
|
|
184
|
+
'不带 --package 时从当前工作区构建;带 --package 时直接部署已封存制品。',
|
|
185
|
+
],
|
|
186
|
+
status: ['用法: openxiangda status <runId> [--json]'],
|
|
187
|
+
retry: ['用法: openxiangda retry <runId> [--json]'],
|
|
188
|
+
rollback: [
|
|
189
|
+
'用法: openxiangda rollback <environment> --to <appReleaseId> [--json]',
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
print((lines[command] || ['Delivery V2']).join('\n'));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function assertDependencies(value) {
|
|
196
|
+
const required = [
|
|
197
|
+
'clearDeliveryContext',
|
|
198
|
+
'downloadWithAuth',
|
|
199
|
+
'getWorkspaceTarget',
|
|
200
|
+
'installDeliveryContext',
|
|
201
|
+
'loadConfig',
|
|
202
|
+
'requestFormWithAuth',
|
|
203
|
+
'requestWithAuth',
|
|
204
|
+
'runOpenXiangdaInProcess',
|
|
205
|
+
];
|
|
206
|
+
for (const key of required) {
|
|
207
|
+
if (typeof value?.[key] !== 'function') {
|
|
208
|
+
throw new Error(`Delivery V2 CLI dependency 缺失: ${key}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
createDeliveryV2Cli,
|
|
216
|
+
};
|