openxiangda 1.0.165 → 1.0.166
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 +7 -7
- package/lib/change-baseline.js +74 -6
- package/lib/cli.js +913 -113
- package/lib/sdd.js +320 -6
- package/openxiangda-skills/SKILL.md +10 -5
- package/openxiangda-skills/references/pages/publish-flow.md +15 -16
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +11 -7
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +10 -9
- package/package.json +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +10 -10
- package/templates/openxiangda-react-spa/scripts/build-js-code.mjs +20 -2
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +2 -2
- package/templates/sy-lowcode-app-workspace/AGENTS.md +3 -3
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +20 -2
package/lib/cli.js
CHANGED
|
@@ -990,6 +990,7 @@ async function release(args) {
|
|
|
990
990
|
);
|
|
991
991
|
}
|
|
992
992
|
const pendingBaseline = getStoredChangeBaseline(target);
|
|
993
|
+
const newReleaseLifecycle = !stored && !pendingBaseline;
|
|
993
994
|
const clientSessionId =
|
|
994
995
|
existing?.clientSessionId ||
|
|
995
996
|
(pendingBaseline?.changeId === changeId &&
|
|
@@ -1018,12 +1019,16 @@ async function release(args) {
|
|
|
1018
1019
|
throw error;
|
|
1019
1020
|
}
|
|
1020
1021
|
const finalizedBaseline = finalizeReleaseBaseline(target, baseline);
|
|
1022
|
+
const stagedResourcesFile = newReleaseLifecycle
|
|
1023
|
+
? initializeStagedAppReleaseResources(target, changeId)
|
|
1024
|
+
: null;
|
|
1021
1025
|
const result = {
|
|
1022
1026
|
...lease,
|
|
1023
1027
|
baselineId: finalizedBaseline?.baselineId,
|
|
1024
1028
|
baselineHeadDigest: finalizedBaseline?.headDigest,
|
|
1025
1029
|
sourceBase: finalizedBaseline?.sourceBase,
|
|
1026
1030
|
releaseSourceRevision: finalizedBaseline?.releaseSourceRevision,
|
|
1031
|
+
...(stagedResourcesFile ? { stagedResourcesFile } : {}),
|
|
1027
1032
|
};
|
|
1028
1033
|
if (flags.json) return writeJson(result);
|
|
1029
1034
|
print(
|
|
@@ -1562,6 +1567,270 @@ function readStagedAppReleaseResources(flags = {}) {
|
|
|
1562
1567
|
);
|
|
1563
1568
|
}
|
|
1564
1569
|
|
|
1570
|
+
function stagedResourcesFileForChange(changeId) {
|
|
1571
|
+
const normalized = String(changeId || '').trim();
|
|
1572
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalized)) {
|
|
1573
|
+
fail('APP_RELEASE_STAGED_RESOURCES_CHANGE_INVALID: 自动聚合 staged resources 需要有效 --change');
|
|
1574
|
+
}
|
|
1575
|
+
return path.join(
|
|
1576
|
+
process.cwd(),
|
|
1577
|
+
'.openxiangda',
|
|
1578
|
+
'releases',
|
|
1579
|
+
normalized,
|
|
1580
|
+
'staged-resources.json'
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function stagedResourcesContextFileForChange(changeId) {
|
|
1585
|
+
return stagedResourcesFileForChange(changeId).replace(
|
|
1586
|
+
/staged-resources\.json$/,
|
|
1587
|
+
'staged-resources.context.json'
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
function currentStagedResourcesContext(target, changeId) {
|
|
1592
|
+
const baseline = getStoredChangeBaseline(target);
|
|
1593
|
+
if (!baseline?.baselineId || baseline.changeId !== changeId) {
|
|
1594
|
+
fail(
|
|
1595
|
+
`APP_RELEASE_STAGED_CONTEXT_REQUIRED: change ${changeId} 缺少当前发布基线`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
return {
|
|
1599
|
+
contractVersion: 'staged_resources_context_v1',
|
|
1600
|
+
appType: target.appType,
|
|
1601
|
+
profile: target.profileName,
|
|
1602
|
+
changeId,
|
|
1603
|
+
baselineId: baseline.baselineId,
|
|
1604
|
+
clientSessionId: baseline.clientSessionId || null,
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function writeStagedResourcesFiles(changeId, resources, context) {
|
|
1609
|
+
const file = stagedResourcesFileForChange(changeId);
|
|
1610
|
+
const contextFile = stagedResourcesContextFileForChange(changeId);
|
|
1611
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1612
|
+
for (const [targetFile, value] of [
|
|
1613
|
+
[file, resources],
|
|
1614
|
+
[contextFile, context],
|
|
1615
|
+
]) {
|
|
1616
|
+
const temporary = `${targetFile}.${process.pid}.tmp`;
|
|
1617
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, {
|
|
1618
|
+
mode: 0o600,
|
|
1619
|
+
});
|
|
1620
|
+
fs.renameSync(temporary, targetFile);
|
|
1621
|
+
}
|
|
1622
|
+
return path.relative(process.cwd(), file).replace(/\\/g, '/');
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function initializeStagedAppReleaseResources(target, changeId) {
|
|
1626
|
+
const context = currentStagedResourcesContext(target, changeId);
|
|
1627
|
+
return writeStagedResourcesFiles(changeId, [], {
|
|
1628
|
+
...context,
|
|
1629
|
+
initializedAt: new Date().toISOString(),
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
function assertStagedResourcesContext(target, changeId) {
|
|
1634
|
+
const expected = currentStagedResourcesContext(target, changeId);
|
|
1635
|
+
const contextFile = stagedResourcesContextFileForChange(changeId);
|
|
1636
|
+
if (!fs.existsSync(contextFile)) {
|
|
1637
|
+
if (!fs.existsSync(stagedResourcesFileForChange(changeId))) {
|
|
1638
|
+
initializeStagedAppReleaseResources(target, changeId);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
if (!fs.existsSync(contextFile)) {
|
|
1642
|
+
fail(
|
|
1643
|
+
'APP_RELEASE_STAGED_CONTEXT_REQUIRED: staged resources 缺少当前 release begin 生成的 context;请重新暂存本次 change 的 child releases'
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
const actual = JSON.parse(fs.readFileSync(contextFile, 'utf8'));
|
|
1647
|
+
for (const field of [
|
|
1648
|
+
'contractVersion',
|
|
1649
|
+
'appType',
|
|
1650
|
+
'profile',
|
|
1651
|
+
'changeId',
|
|
1652
|
+
'baselineId',
|
|
1653
|
+
'clientSessionId',
|
|
1654
|
+
]) {
|
|
1655
|
+
if ((actual?.[field] ?? null) !== (expected[field] ?? null)) {
|
|
1656
|
+
fail(
|
|
1657
|
+
`APP_RELEASE_STAGED_CONTEXT_MISMATCH: ${field} 不属于当前 change/baseline/session,请重新暂存 child releases`
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return actual;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function recordStagedAppReleaseResources(
|
|
1665
|
+
changeId,
|
|
1666
|
+
resources,
|
|
1667
|
+
target
|
|
1668
|
+
) {
|
|
1669
|
+
if (!changeId || !Array.isArray(resources) || resources.length === 0) {
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
const file = stagedResourcesFileForChange(changeId);
|
|
1673
|
+
const context = assertStagedResourcesContext(target, changeId);
|
|
1674
|
+
let existing = [];
|
|
1675
|
+
if (fs.existsSync(file)) {
|
|
1676
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
1677
|
+
existing = Array.isArray(parsed) && parsed.length === 0
|
|
1678
|
+
? []
|
|
1679
|
+
: normalizeStagedAppReleaseResources(parsed);
|
|
1680
|
+
}
|
|
1681
|
+
const incoming = normalizeStagedAppReleaseResources(resources);
|
|
1682
|
+
const merged = new Map();
|
|
1683
|
+
for (const resource of [...existing, ...incoming]) {
|
|
1684
|
+
const described = describeAppReleaseResource(resource, 0, 'stagedResources');
|
|
1685
|
+
merged.set(described.stableKey, resource);
|
|
1686
|
+
}
|
|
1687
|
+
const output = Array.from(merged.values()).sort((left, right) => {
|
|
1688
|
+
const leftKey = describeAppReleaseResource(left, 0, 'stagedResources').stableKey;
|
|
1689
|
+
const rightKey = describeAppReleaseResource(right, 0, 'stagedResources').stableKey;
|
|
1690
|
+
return leftKey.localeCompare(rightKey);
|
|
1691
|
+
});
|
|
1692
|
+
return writeStagedResourcesFiles(changeId, output, context);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function exactStringSet(left, right) {
|
|
1696
|
+
const normalize = values =>
|
|
1697
|
+
Array.from(
|
|
1698
|
+
new Set((values || []).map(value => String(value || '').trim()).filter(Boolean))
|
|
1699
|
+
).sort();
|
|
1700
|
+
const a = normalize(left);
|
|
1701
|
+
const b = normalize(right);
|
|
1702
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
function stagedFormCode(target, resource) {
|
|
1706
|
+
const explicit = String(resource?.metadata?.formCode || '').trim();
|
|
1707
|
+
if (explicit) return explicit;
|
|
1708
|
+
const formUuid = String(resource?.identity?.formUuid || '').trim();
|
|
1709
|
+
for (const bucketName of ['formSettings', 'forms']) {
|
|
1710
|
+
for (const [code, entry] of Object.entries(
|
|
1711
|
+
target.bound.resources?.[bucketName] || {}
|
|
1712
|
+
)) {
|
|
1713
|
+
if (String(entry?.formUuid || '').trim() === formUuid) return code;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
return formUuid;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function assertStagedAppReleaseScope(target, flags, resources) {
|
|
1720
|
+
const changeId = readStringFlag(flags, 'change');
|
|
1721
|
+
if (!changeId) {
|
|
1722
|
+
fail(
|
|
1723
|
+
'APP_RELEASE_STAGED_SCOPE_REQUIRED: staged child activation 必须显式提供 --change'
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
assertStagedResourcesContext(target, changeId);
|
|
1727
|
+
let scope;
|
|
1728
|
+
try {
|
|
1729
|
+
scope = getSddChangeScope({
|
|
1730
|
+
cwd: process.cwd(),
|
|
1731
|
+
configText: readWorkspaceConfigText(),
|
|
1732
|
+
changeId,
|
|
1733
|
+
});
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
if (/SDD 未初始化|change 不存在|ENOENT/i.test(String(error?.message || ''))) {
|
|
1736
|
+
return {
|
|
1737
|
+
changeId,
|
|
1738
|
+
scope: null,
|
|
1739
|
+
resources: normalizeStagedAppReleaseResources(resources),
|
|
1740
|
+
compatibility: 'unmanaged_sdd_scope',
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
throw error;
|
|
1744
|
+
}
|
|
1745
|
+
const targets = scope.targets || {};
|
|
1746
|
+
const normalized = normalizeStagedAppReleaseResources(resources);
|
|
1747
|
+
const byKind = kind => normalized.filter(resource => resource.kind === kind);
|
|
1748
|
+
const expectedBackendSelectors = [
|
|
1749
|
+
...(targets.functions || []).map(code => `function:${code}`),
|
|
1750
|
+
...(targets.automations || []).map(code => `automation:${code}`),
|
|
1751
|
+
].sort();
|
|
1752
|
+
const backend = byKind('BackendRelease');
|
|
1753
|
+
if (expectedBackendSelectors.length > 0) {
|
|
1754
|
+
if (
|
|
1755
|
+
backend.length !== 1 ||
|
|
1756
|
+
!exactStringSet(
|
|
1757
|
+
backend[0]?.metadata?.handledSelectors,
|
|
1758
|
+
expectedBackendSelectors
|
|
1759
|
+
)
|
|
1760
|
+
) {
|
|
1761
|
+
fail(
|
|
1762
|
+
`APP_RELEASE_STAGED_SCOPE_MISMATCH: Backend handledSelectors 必须精确为 ${expectedBackendSelectors.join(',')}`
|
|
1763
|
+
);
|
|
1764
|
+
}
|
|
1765
|
+
} else if (backend.length > 0) {
|
|
1766
|
+
fail('APP_RELEASE_STAGED_SCOPE_MISMATCH: SDD 未授权 BackendRelease child');
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
const expectedForms = (targets.forms || []).map(String).sort();
|
|
1770
|
+
const actualForms = byKind('FormRelease')
|
|
1771
|
+
.map(resource => stagedFormCode(target, resource))
|
|
1772
|
+
.sort();
|
|
1773
|
+
if (!exactStringSet(actualForms, expectedForms)) {
|
|
1774
|
+
fail(
|
|
1775
|
+
`APP_RELEASE_STAGED_SCOPE_MISMATCH: FormRelease 必须精确覆盖 ${expectedForms.join(',') || '(none)'}`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
const runtimeCount = byKind('RuntimeRelease').length;
|
|
1780
|
+
if (runtimeCount !== (targets.runtime ? 1 : 0)) {
|
|
1781
|
+
fail(
|
|
1782
|
+
`APP_RELEASE_STAGED_SCOPE_MISMATCH: RuntimeRelease 期望 ${targets.runtime ? 1 : 0} 个,实际 ${runtimeCount} 个`
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
const pageCount = byKind('PageRelease').length;
|
|
1786
|
+
const expectsPageRelease = (targets.pages || []).length > 0;
|
|
1787
|
+
if (pageCount !== (expectsPageRelease ? 1 : 0)) {
|
|
1788
|
+
fail(
|
|
1789
|
+
`APP_RELEASE_STAGED_SCOPE_MISMATCH: PageRelease 期望 ${expectsPageRelease ? 1 : 0} 个,实际 ${pageCount} 个`
|
|
1790
|
+
);
|
|
1791
|
+
}
|
|
1792
|
+
return { changeId, scope, resources: normalized };
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
function changedAppReleaseResources(detailResources, captureResources) {
|
|
1796
|
+
const captured = new Map(
|
|
1797
|
+
(captureResources || []).map((resource, index) => {
|
|
1798
|
+
const described = describeAppReleaseResource(
|
|
1799
|
+
resource,
|
|
1800
|
+
index,
|
|
1801
|
+
'capture.resources'
|
|
1802
|
+
);
|
|
1803
|
+
return [described.stableKey, described];
|
|
1804
|
+
})
|
|
1805
|
+
);
|
|
1806
|
+
return (detailResources || []).filter((resource, index) => {
|
|
1807
|
+
const described = describeAppReleaseResource(
|
|
1808
|
+
resource,
|
|
1809
|
+
index,
|
|
1810
|
+
'release.resources'
|
|
1811
|
+
);
|
|
1812
|
+
const active = captured.get(described.stableKey);
|
|
1813
|
+
return (
|
|
1814
|
+
!active ||
|
|
1815
|
+
active.releaseId !== described.releaseId ||
|
|
1816
|
+
active.hash !== described.hash
|
|
1817
|
+
);
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function assertAppReleaseActualGate(target, flags, scope, command) {
|
|
1822
|
+
assertSddReleaseGate(
|
|
1823
|
+
buildExplicitSddReleasePlan(scope.targets, {
|
|
1824
|
+
files: scope.changedFiles || [],
|
|
1825
|
+
commands: [command],
|
|
1826
|
+
changeId: scope.changeId,
|
|
1827
|
+
actualCommands: true,
|
|
1828
|
+
}),
|
|
1829
|
+
flags,
|
|
1830
|
+
command
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1565
1834
|
function stagedAppReleaseProtocolVersion(stagedResources) {
|
|
1566
1835
|
const hasBackendReleaseV2 = (stagedResources || []).some(
|
|
1567
1836
|
resource =>
|
|
@@ -1767,6 +2036,9 @@ async function runAppReleaseCommand(
|
|
|
1767
2036
|
);
|
|
1768
2037
|
} else if (subcommand === 'app-prepare') {
|
|
1769
2038
|
const stagedResources = readStagedAppReleaseResources(flags);
|
|
2039
|
+
if (stagedResources) {
|
|
2040
|
+
assertStagedAppReleaseScope(target, flags, stagedResources);
|
|
2041
|
+
}
|
|
1770
2042
|
const protocolVersion = stagedAppReleaseProtocolVersion(stagedResources);
|
|
1771
2043
|
if (protocolVersion) {
|
|
1772
2044
|
assertAppFunctionSecretCapabilities(
|
|
@@ -1803,6 +2075,15 @@ async function runAppReleaseCommand(
|
|
|
1803
2075
|
target.profileName,
|
|
1804
2076
|
appReleaseApiPath(target, `/${encodeURIComponent(releaseId)}`)
|
|
1805
2077
|
);
|
|
2078
|
+
const detailResources = appReleaseManifestResources(detail);
|
|
2079
|
+
const capture = await fetchAppReleaseCapture(config, target, flags);
|
|
2080
|
+
const changedResources = changedAppReleaseResources(
|
|
2081
|
+
detailResources,
|
|
2082
|
+
capture.resources
|
|
2083
|
+
);
|
|
2084
|
+
if (changedResources.length > 0) {
|
|
2085
|
+
assertStagedAppReleaseScope(target, flags, changedResources);
|
|
2086
|
+
}
|
|
1806
2087
|
const protocolVersion = String(detail?.protocolVersion || '');
|
|
1807
2088
|
if (protocolVersion === APP_FUNCTION_SECRET_CAPABILITIES.atomicActivation) {
|
|
1808
2089
|
assertAppFunctionSecretCapabilities(
|
|
@@ -1820,7 +2101,7 @@ async function runAppReleaseCommand(
|
|
|
1820
2101
|
target,
|
|
1821
2102
|
releaseContext,
|
|
1822
2103
|
detail,
|
|
1823
|
-
|
|
2104
|
+
detailResources,
|
|
1824
2105
|
{ protocolVersion: protocolVersion || undefined }
|
|
1825
2106
|
);
|
|
1826
2107
|
} else if (subcommand === 'app-activate') {
|
|
@@ -1835,6 +2116,18 @@ async function runAppReleaseCommand(
|
|
|
1835
2116
|
const protocolVersion = String(detail?.protocolVersion || '');
|
|
1836
2117
|
const atomicV2 =
|
|
1837
2118
|
protocolVersion === APP_FUNCTION_SECRET_CAPABILITIES.atomicActivation;
|
|
2119
|
+
const activatesStagedChildren =
|
|
2120
|
+
atomicV2 ||
|
|
2121
|
+
flags['activate-staged-children'] === true ||
|
|
2122
|
+
String(flags['activate-staged-children'] || '').toLowerCase() === 'true';
|
|
2123
|
+
if (activatesStagedChildren) {
|
|
2124
|
+
const capture = await fetchAppReleaseCapture(config, target, flags);
|
|
2125
|
+
const changedResources = changedAppReleaseResources(
|
|
2126
|
+
appReleaseManifestResources(detail),
|
|
2127
|
+
capture.resources
|
|
2128
|
+
);
|
|
2129
|
+
assertStagedAppReleaseScope(target, flags, changedResources);
|
|
2130
|
+
}
|
|
1838
2131
|
if (atomicV2) {
|
|
1839
2132
|
assertAppFunctionSecretCapabilities(
|
|
1840
2133
|
await fetchAppFunctionSecretCapabilities(config, target)
|
|
@@ -1853,15 +2146,30 @@ async function runAppReleaseCommand(
|
|
|
1853
2146
|
releaseId,
|
|
1854
2147
|
{
|
|
1855
2148
|
activateStagedChildren:
|
|
1856
|
-
|
|
1857
|
-
flags['activate-staged-children'] === true ||
|
|
1858
|
-
String(flags['activate-staged-children'] || '').toLowerCase() ===
|
|
1859
|
-
'true',
|
|
2149
|
+
activatesStagedChildren,
|
|
1860
2150
|
protocolVersion: protocolVersion || undefined,
|
|
1861
2151
|
}
|
|
1862
2152
|
);
|
|
1863
2153
|
} else if (subcommand === 'app-finalize') {
|
|
1864
2154
|
const stagedResources = readStagedAppReleaseResources(flags);
|
|
2155
|
+
let stagedScope = null;
|
|
2156
|
+
if (stagedResources) {
|
|
2157
|
+
stagedScope = assertStagedAppReleaseScope(
|
|
2158
|
+
target,
|
|
2159
|
+
flags,
|
|
2160
|
+
stagedResources
|
|
2161
|
+
);
|
|
2162
|
+
if (stagedScope.scope) {
|
|
2163
|
+
assertAppReleaseActualGate(
|
|
2164
|
+
target,
|
|
2165
|
+
flags,
|
|
2166
|
+
stagedScope.scope,
|
|
2167
|
+
`openxiangda release app-finalize --staged-resources-json ${String(
|
|
2168
|
+
flags['staged-resources-json']
|
|
2169
|
+
)} --profile ${target.profileName} --change ${stagedScope.changeId}`
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
1865
2173
|
const protocolVersion = stagedAppReleaseProtocolVersion(stagedResources);
|
|
1866
2174
|
if (protocolVersion) {
|
|
1867
2175
|
assertAppFunctionSecretCapabilities(
|
|
@@ -1914,6 +2222,16 @@ async function runAppReleaseCommand(
|
|
|
1914
2222
|
protocolVersion,
|
|
1915
2223
|
}
|
|
1916
2224
|
);
|
|
2225
|
+
let stateReconcileWarning = null;
|
|
2226
|
+
try {
|
|
2227
|
+
const appHead = await loadRuntimeHeadApp(config, target);
|
|
2228
|
+
saveRuntimeHeadState(target, appHead);
|
|
2229
|
+
} catch (error) {
|
|
2230
|
+
stateReconcileWarning = {
|
|
2231
|
+
component: 'RuntimeStateReadback',
|
|
2232
|
+
message: error?.message || String(error),
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
1917
2235
|
data = {
|
|
1918
2236
|
activationModel: stagedResources
|
|
1919
2237
|
? protocolVersion || 'atomic_staged_children_v1'
|
|
@@ -1923,6 +2241,7 @@ async function runAppReleaseCommand(
|
|
|
1923
2241
|
prepared,
|
|
1924
2242
|
verified,
|
|
1925
2243
|
activated,
|
|
2244
|
+
...(stateReconcileWarning ? { stateReconcileWarning } : {}),
|
|
1926
2245
|
};
|
|
1927
2246
|
} else if (subcommand === 'app-rollback') {
|
|
1928
2247
|
if (!releaseId) {
|
|
@@ -3962,7 +4281,19 @@ async function workspace(args) {
|
|
|
3962
4281
|
if (publishOptions.dryRun) {
|
|
3963
4282
|
printWorkspaceReleasePlan(sddPlan);
|
|
3964
4283
|
} else {
|
|
3965
|
-
|
|
4284
|
+
const actualSddPlan = buildExplicitSddReleasePlan(sddPlan.targets, {
|
|
4285
|
+
files: sddPlan.changedFiles || [],
|
|
4286
|
+
commands: [
|
|
4287
|
+
buildWorkspacePublishCommand(flags, profileName),
|
|
4288
|
+
],
|
|
4289
|
+
changeId: publishOptions.changeId,
|
|
4290
|
+
actualCommands: true,
|
|
4291
|
+
});
|
|
4292
|
+
assertSddReleaseGate(
|
|
4293
|
+
actualSddPlan,
|
|
4294
|
+
flags,
|
|
4295
|
+
'openxiangda workspace publish'
|
|
4296
|
+
);
|
|
3966
4297
|
}
|
|
3967
4298
|
await requestWithAuth(
|
|
3968
4299
|
config,
|
|
@@ -6341,6 +6672,7 @@ const SECRET_RESPONSE_FORBIDDEN_KEYS = new Set([
|
|
|
6341
6672
|
'cipher',
|
|
6342
6673
|
'encryptedvalue',
|
|
6343
6674
|
'encrypteddek',
|
|
6675
|
+
'wrappeddek',
|
|
6344
6676
|
'valuehash',
|
|
6345
6677
|
'cipherhash',
|
|
6346
6678
|
'valuedigest',
|
|
@@ -6350,6 +6682,7 @@ const SECRET_RESPONSE_FORBIDDEN_KEYS = new Set([
|
|
|
6350
6682
|
'plaintextlength',
|
|
6351
6683
|
'iv',
|
|
6352
6684
|
'nonce',
|
|
6685
|
+
'authtag',
|
|
6353
6686
|
'tag',
|
|
6354
6687
|
]);
|
|
6355
6688
|
|
|
@@ -8477,8 +8810,16 @@ async function resource(args) {
|
|
|
8477
8810
|
resourceTargets,
|
|
8478
8811
|
{
|
|
8479
8812
|
files: scopedFiles,
|
|
8480
|
-
commands: [
|
|
8813
|
+
commands: [
|
|
8814
|
+
buildResourcePublishCommand(
|
|
8815
|
+
typeFilters,
|
|
8816
|
+
codeFilters,
|
|
8817
|
+
profileName,
|
|
8818
|
+
flags
|
|
8819
|
+
),
|
|
8820
|
+
],
|
|
8481
8821
|
changeId: flags.change,
|
|
8822
|
+
actualCommands: true,
|
|
8482
8823
|
}
|
|
8483
8824
|
),
|
|
8484
8825
|
flags,
|
|
@@ -8528,6 +8869,21 @@ async function resource(args) {
|
|
|
8528
8869
|
if (changeBaselinePreflight) {
|
|
8529
8870
|
result.changeBaselinePreflight = changeBaselinePreflight;
|
|
8530
8871
|
}
|
|
8872
|
+
const stagedResources =
|
|
8873
|
+
Array.isArray(result.stagedResources) && result.stagedResources.length > 0
|
|
8874
|
+
? result.stagedResources
|
|
8875
|
+
: result.stagedResource
|
|
8876
|
+
? [result.stagedResource]
|
|
8877
|
+
: [];
|
|
8878
|
+
const stagedChangeId =
|
|
8879
|
+
flags.change || getStoredChangeBaseline(target)?.changeId;
|
|
8880
|
+
if (stagedResources.length > 0 && stagedChangeId) {
|
|
8881
|
+
result.stagedResourcesFile = recordStagedAppReleaseResources(
|
|
8882
|
+
stagedChangeId,
|
|
8883
|
+
stagedResources,
|
|
8884
|
+
target
|
|
8885
|
+
);
|
|
8886
|
+
}
|
|
8531
8887
|
result.timings = telemetry.stop();
|
|
8532
8888
|
if (flags.json) return writeJson(result);
|
|
8533
8889
|
printResourceResult(result);
|
|
@@ -8584,7 +8940,12 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
|
|
|
8584
8940
|
.map(action => action.code)
|
|
8585
8941
|
.filter(Boolean)
|
|
8586
8942
|
).sort();
|
|
8587
|
-
const codeResourceKeys = new Set([
|
|
8943
|
+
const codeResourceKeys = new Set([
|
|
8944
|
+
'functions',
|
|
8945
|
+
'automations',
|
|
8946
|
+
'workflows',
|
|
8947
|
+
'formSettings',
|
|
8948
|
+
]);
|
|
8588
8949
|
const hasGenericResources = RESOURCE_SPECS.some(
|
|
8589
8950
|
spec => !codeResourceKeys.has(spec.key) && (manifest[spec.key] || []).length > 0
|
|
8590
8951
|
) || (planActions || []).some(
|
|
@@ -8593,7 +8954,7 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
|
|
|
8593
8954
|
!['function', 'automation', 'workflow'].includes(action.kind)
|
|
8594
8955
|
);
|
|
8595
8956
|
return {
|
|
8596
|
-
forms:
|
|
8957
|
+
forms: codes('formSettings'),
|
|
8597
8958
|
pages: [],
|
|
8598
8959
|
functions: unique([...codes('functions'), ...deletedCodes('function')]).sort(),
|
|
8599
8960
|
automations: unique([
|
|
@@ -8608,20 +8969,51 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
|
|
|
8608
8969
|
};
|
|
8609
8970
|
}
|
|
8610
8971
|
|
|
8611
|
-
function buildResourcePublishCommand(
|
|
8972
|
+
function buildResourcePublishCommand(
|
|
8973
|
+
typeFilters,
|
|
8974
|
+
codeFilters,
|
|
8975
|
+
profileName,
|
|
8976
|
+
flags = {}
|
|
8977
|
+
) {
|
|
8612
8978
|
const args = ['openxiangda resource publish'];
|
|
8613
|
-
if (typeFilters?.size
|
|
8614
|
-
args.push(Array.from(typeFilters)
|
|
8979
|
+
if (typeFilters?.size > 0) {
|
|
8980
|
+
args.push(Array.from(typeFilters).sort().join(','));
|
|
8615
8981
|
}
|
|
8616
8982
|
if (codeFilters?.length) {
|
|
8983
|
+
const qualifierAliases = {
|
|
8984
|
+
functions: 'function',
|
|
8985
|
+
automations: 'automation',
|
|
8986
|
+
workflows: 'workflow',
|
|
8987
|
+
formSettings: 'form-setting',
|
|
8988
|
+
};
|
|
8617
8989
|
const selectors = codeFilters.map(selector =>
|
|
8618
8990
|
(selector.key || selector.qualifier) && (!typeFilters || typeFilters.size !== 1)
|
|
8619
|
-
? `${
|
|
8991
|
+
? `${
|
|
8992
|
+
qualifierAliases[selector.key || selector.qualifier] ||
|
|
8993
|
+
selector.key ||
|
|
8994
|
+
selector.qualifier
|
|
8995
|
+
}:${selector.code}`
|
|
8620
8996
|
: selector.code
|
|
8621
8997
|
);
|
|
8622
8998
|
args.push(`--only ${Array.from(new Set(selectors)).join(',')}`);
|
|
8623
8999
|
}
|
|
8624
9000
|
args.push(`--profile ${profileName}`);
|
|
9001
|
+
if (flags.change) args.push(`--change ${flags.change}`);
|
|
9002
|
+
if (flags['stage-only']) args.push('--stage-only');
|
|
9003
|
+
if (flags.activate) args.push('--activate');
|
|
9004
|
+
if (flags['replace-manifest']) args.push('--replace-manifest');
|
|
9005
|
+
if (flags.reason) args.push(`--reason ${JSON.stringify(String(flags.reason))}`);
|
|
9006
|
+
return args.join(' ');
|
|
9007
|
+
}
|
|
9008
|
+
|
|
9009
|
+
function buildWorkspacePublishCommand(flags = {}, profileName) {
|
|
9010
|
+
const args = ['openxiangda workspace publish', `--profile ${profileName}`];
|
|
9011
|
+
if (flags.change) args.push(`--change ${flags.change}`);
|
|
9012
|
+
if (flags.form) args.push(`--form ${flags.form}`);
|
|
9013
|
+
if (flags.page) args.push(`--page ${flags.page}`);
|
|
9014
|
+
if (flags.only) args.push(`--only ${flags.only}`);
|
|
9015
|
+
if (flags.changed) args.push('--changed');
|
|
9016
|
+
if (flags['include-resources']) args.push('--include-resources');
|
|
8625
9017
|
return args.join(' ');
|
|
8626
9018
|
}
|
|
8627
9019
|
|
|
@@ -8729,9 +9121,10 @@ async function runtime(args) {
|
|
|
8729
9121
|
commands: [
|
|
8730
9122
|
`openxiangda runtime deploy --profile ${target.profileName}${
|
|
8731
9123
|
flags.change ? ` --change ${flags.change}` : ''
|
|
8732
|
-
}`,
|
|
9124
|
+
}${flags['no-activate'] ? ' --no-activate' : ''}`,
|
|
8733
9125
|
],
|
|
8734
9126
|
changeId: flags.change,
|
|
9127
|
+
actualCommands: true,
|
|
8735
9128
|
}
|
|
8736
9129
|
),
|
|
8737
9130
|
flags,
|
|
@@ -8968,7 +9361,46 @@ async function runtime(args) {
|
|
|
8968
9361
|
}
|
|
8969
9362
|
)
|
|
8970
9363
|
);
|
|
8971
|
-
|
|
9364
|
+
const runtimeContentHash = String(data?.contentHash || '')
|
|
9365
|
+
.trim()
|
|
9366
|
+
.toLowerCase();
|
|
9367
|
+
const stagedResource = flags['no-activate']
|
|
9368
|
+
? stripUndefinedValues({
|
|
9369
|
+
kind: 'RuntimeRelease',
|
|
9370
|
+
identity: { releaseId: data?.id },
|
|
9371
|
+
action: 'update',
|
|
9372
|
+
hash: runtimeContentHash,
|
|
9373
|
+
revision: {
|
|
9374
|
+
parentReleaseId:
|
|
9375
|
+
data?.parentReleaseId || runtimeLineage.parentReleaseId || null,
|
|
9376
|
+
},
|
|
9377
|
+
metadata: {
|
|
9378
|
+
buildId: data?.buildId || buildId,
|
|
9379
|
+
releaseStatus: data?.status || 'staged',
|
|
9380
|
+
},
|
|
9381
|
+
})
|
|
9382
|
+
: null;
|
|
9383
|
+
if (
|
|
9384
|
+
flags['no-activate'] &&
|
|
9385
|
+
(!data?.id || !/^[a-f0-9]{64}$/.test(runtimeContentHash))
|
|
9386
|
+
) {
|
|
9387
|
+
fail(
|
|
9388
|
+
'RUNTIME_RELEASE_STAGED_RESOURCE_INVALID: --no-activate 响应缺少 releaseId 或 64 位 contentHash'
|
|
9389
|
+
);
|
|
9390
|
+
}
|
|
9391
|
+
if (!flags['no-activate']) {
|
|
9392
|
+
saveRuntimeReleaseState(target, data);
|
|
9393
|
+
}
|
|
9394
|
+
const stagedChangeId =
|
|
9395
|
+
flags.change || getStoredChangeBaseline(target)?.changeId;
|
|
9396
|
+
const stagedResourcesFile =
|
|
9397
|
+
stagedResource && stagedChangeId
|
|
9398
|
+
? recordStagedAppReleaseResources(
|
|
9399
|
+
stagedChangeId,
|
|
9400
|
+
[stagedResource],
|
|
9401
|
+
target
|
|
9402
|
+
)
|
|
9403
|
+
: null;
|
|
8972
9404
|
const releaseAssetBaseUrl = data?.assetBaseUrl || assetBaseUrl;
|
|
8973
9405
|
const result = {
|
|
8974
9406
|
appType: target.appType,
|
|
@@ -8990,6 +9422,8 @@ async function runtime(args) {
|
|
|
8990
9422
|
releaseFiles.find(file => file.storageProvider)?.storageProvider ||
|
|
8991
9423
|
null,
|
|
8992
9424
|
traceId,
|
|
9425
|
+
...(stagedResource ? { stagedResource } : {}),
|
|
9426
|
+
...(stagedResourcesFile ? { stagedResourcesFile } : {}),
|
|
8993
9427
|
timings: telemetry.stop(),
|
|
8994
9428
|
};
|
|
8995
9429
|
if (flags.json) return writeJson(result);
|
|
@@ -9002,6 +9436,9 @@ async function runtime(args) {
|
|
|
9002
9436
|
`traceId: ${traceId}`,
|
|
9003
9437
|
`assetBase: ${releaseAssetBaseUrl}`,
|
|
9004
9438
|
`active: ${result.activated ? 'yes' : 'no'}`,
|
|
9439
|
+
...(stagedResourcesFile
|
|
9440
|
+
? [`staged resources: ${stagedResourcesFile}`]
|
|
9441
|
+
: []),
|
|
9005
9442
|
].join('\n')
|
|
9006
9443
|
);
|
|
9007
9444
|
return;
|
|
@@ -9482,6 +9919,21 @@ function saveRuntimeReleaseState(target, release) {
|
|
|
9482
9919
|
saveProjectState(target.state);
|
|
9483
9920
|
}
|
|
9484
9921
|
|
|
9922
|
+
function saveRuntimeHeadState(target, app) {
|
|
9923
|
+
const activeReleaseId =
|
|
9924
|
+
app?.activeRuntimeReleaseId || app?.activeRuntime?.releaseId || null;
|
|
9925
|
+
const activeBuildId =
|
|
9926
|
+
app?.activeRuntimeBuildId || app?.activeRuntime?.buildId || null;
|
|
9927
|
+
if (!activeReleaseId && !activeBuildId) return false;
|
|
9928
|
+
saveRuntimeReleaseState(target, {
|
|
9929
|
+
id: activeReleaseId,
|
|
9930
|
+
buildId: activeBuildId,
|
|
9931
|
+
assetBaseUrl:
|
|
9932
|
+
app?.runtimeAssetBaseUrl || app?.activeRuntime?.assetBaseUrl || null,
|
|
9933
|
+
});
|
|
9934
|
+
return true;
|
|
9935
|
+
}
|
|
9936
|
+
|
|
9485
9937
|
function formatBytes(value) {
|
|
9486
9938
|
const bytes = Number(value || 0);
|
|
9487
9939
|
if (bytes < 1024) return `${bytes}B`;
|
|
@@ -12926,22 +13378,18 @@ function collectBackendReleaseCandidates(manifest, options = {}) {
|
|
|
12926
13378
|
const code = item.code || item.functionCode || item.resourceCode;
|
|
12927
13379
|
const action = getPlannedResourceAction(options, planKind, code);
|
|
12928
13380
|
if (!action || !['create', 'update', 'noop'].includes(action.action)) continue;
|
|
12929
|
-
const secretRefsDeclared =
|
|
12930
|
-
kind === 'Function' && functionManifestDeclaresSecretRefs(item);
|
|
12931
|
-
if (action.action === 'create' && !secretRefsDeclared) continue;
|
|
12932
13381
|
if (action.action === 'noop' && options.skipNoop !== false) continue;
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
}
|
|
12944
|
-
candidates.push({ kind, code, item, plannedAction: action.action });
|
|
13382
|
+
const replaceManifest =
|
|
13383
|
+
action.action !== 'create' &&
|
|
13384
|
+
shouldReplaceResourceManifestItem(item, resourceKey, code, options);
|
|
13385
|
+
candidates.push({
|
|
13386
|
+
kind,
|
|
13387
|
+
code,
|
|
13388
|
+
item,
|
|
13389
|
+
plannedAction: action.action,
|
|
13390
|
+
operation: action.action === 'create' ? 'create' : 'update',
|
|
13391
|
+
mode: replaceManifest ? 'manifest_replacement' : 'source_only',
|
|
13392
|
+
});
|
|
12945
13393
|
}
|
|
12946
13394
|
};
|
|
12947
13395
|
append('Function', 'functions', manifest.functions);
|
|
@@ -13116,6 +13564,28 @@ async function buildBackendReleaseResourceInput(
|
|
|
13116
13564
|
const secretRefsDeclared = functionManifestDeclaresSecretRefs(
|
|
13117
13565
|
candidate.item
|
|
13118
13566
|
);
|
|
13567
|
+
const common = {
|
|
13568
|
+
kind: 'Function',
|
|
13569
|
+
code: candidate.code,
|
|
13570
|
+
mode: candidate.mode,
|
|
13571
|
+
operation: candidate.operation,
|
|
13572
|
+
...(secretRefsDeclared
|
|
13573
|
+
? {
|
|
13574
|
+
secretRefs: normalizeFunctionSecretRefs(
|
|
13575
|
+
candidate.item.secretRefs
|
|
13576
|
+
),
|
|
13577
|
+
}
|
|
13578
|
+
: {}),
|
|
13579
|
+
};
|
|
13580
|
+
if (candidate.mode === 'manifest_replacement') {
|
|
13581
|
+
return {
|
|
13582
|
+
...common,
|
|
13583
|
+
replacement: buildBackendReleaseFunctionReplacementInput(
|
|
13584
|
+
candidate.item,
|
|
13585
|
+
definitionJson
|
|
13586
|
+
),
|
|
13587
|
+
};
|
|
13588
|
+
}
|
|
13119
13589
|
const source = backendSourcePlane(
|
|
13120
13590
|
definitionJson,
|
|
13121
13591
|
`Function:${candidate.code}`
|
|
@@ -13124,36 +13594,71 @@ async function buildBackendReleaseResourceInput(
|
|
|
13124
13594
|
source.version = definitionJson.version;
|
|
13125
13595
|
source.runtimeContractVersion = definitionJson.runtimeContractVersion;
|
|
13126
13596
|
}
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
...(candidate.plannedAction === 'create'
|
|
13597
|
+
return {
|
|
13598
|
+
...common,
|
|
13599
|
+
...(candidate.operation === 'create'
|
|
13131
13600
|
? {
|
|
13132
|
-
operation: 'create',
|
|
13133
13601
|
create: buildBackendReleaseFunctionCreateInput(
|
|
13134
13602
|
candidate.item,
|
|
13135
13603
|
definitionJson
|
|
13136
13604
|
),
|
|
13137
13605
|
}
|
|
13138
|
-
: { operation: 'update' }),
|
|
13139
|
-
source,
|
|
13140
|
-
...(secretRefsDeclared
|
|
13141
|
-
? {
|
|
13142
|
-
secretRefs: normalizeFunctionSecretRefs(
|
|
13143
|
-
candidate.item.secretRefs
|
|
13144
|
-
),
|
|
13145
|
-
}
|
|
13146
13606
|
: {}),
|
|
13607
|
+
source,
|
|
13147
13608
|
};
|
|
13148
|
-
return resource;
|
|
13149
13609
|
}
|
|
13150
|
-
|
|
13610
|
+
const viewJson =
|
|
13611
|
+
(await resolveManifestJson(
|
|
13612
|
+
config,
|
|
13613
|
+
target.profileName,
|
|
13614
|
+
candidate.item,
|
|
13615
|
+
'viewJson',
|
|
13616
|
+
'viewFile',
|
|
13617
|
+
true,
|
|
13618
|
+
uploadCache
|
|
13619
|
+
)) ??
|
|
13620
|
+
(await resolveManifestJson(
|
|
13621
|
+
config,
|
|
13622
|
+
target.profileName,
|
|
13623
|
+
candidate.item,
|
|
13624
|
+
'previewJson',
|
|
13625
|
+
'previewFile',
|
|
13626
|
+
true,
|
|
13627
|
+
uploadCache
|
|
13628
|
+
));
|
|
13629
|
+
const common = {
|
|
13151
13630
|
kind: 'Automation',
|
|
13152
13631
|
code: candidate.code,
|
|
13632
|
+
mode: candidate.mode,
|
|
13633
|
+
operation: candidate.operation,
|
|
13634
|
+
};
|
|
13635
|
+
if (candidate.mode === 'manifest_replacement') {
|
|
13636
|
+
return {
|
|
13637
|
+
...common,
|
|
13638
|
+
replacement: buildBackendReleaseAutomationMetadata(
|
|
13639
|
+
target,
|
|
13640
|
+
candidate.item,
|
|
13641
|
+
definitionJson,
|
|
13642
|
+
viewJson
|
|
13643
|
+
),
|
|
13644
|
+
};
|
|
13645
|
+
}
|
|
13646
|
+
return {
|
|
13647
|
+
...common,
|
|
13153
13648
|
sources: buildAutomationBackendReleaseSources(
|
|
13154
13649
|
definitionJson,
|
|
13155
13650
|
`Automation:${candidate.code}`
|
|
13156
13651
|
),
|
|
13652
|
+
...(candidate.operation === 'create'
|
|
13653
|
+
? {
|
|
13654
|
+
create: buildBackendReleaseAutomationMetadata(
|
|
13655
|
+
target,
|
|
13656
|
+
candidate.item,
|
|
13657
|
+
withoutBackendSourcePlanes(definitionJson),
|
|
13658
|
+
viewJson
|
|
13659
|
+
),
|
|
13660
|
+
}
|
|
13661
|
+
: {}),
|
|
13157
13662
|
};
|
|
13158
13663
|
}
|
|
13159
13664
|
|
|
@@ -13187,6 +13692,75 @@ function buildBackendReleaseFunctionCreateInput(functionItem, definitionJson) {
|
|
|
13187
13692
|
});
|
|
13188
13693
|
}
|
|
13189
13694
|
|
|
13695
|
+
function buildBackendReleaseFunctionReplacementInput(
|
|
13696
|
+
functionItem,
|
|
13697
|
+
definitionJson
|
|
13698
|
+
) {
|
|
13699
|
+
const code =
|
|
13700
|
+
functionItem.code || functionItem.functionCode || functionItem.resourceCode;
|
|
13701
|
+
return stripUndefinedValues({
|
|
13702
|
+
name: functionItem.name || definitionJson.name || code,
|
|
13703
|
+
description:
|
|
13704
|
+
functionItem.description !== undefined
|
|
13705
|
+
? functionItem.description
|
|
13706
|
+
: definitionJson.description || '',
|
|
13707
|
+
definitionJson: clonePlainJson(definitionJson),
|
|
13708
|
+
resourceBindings: clonePlainJson(definitionJson.resourceBindings || {}),
|
|
13709
|
+
inputSchema: clonePlainJson(
|
|
13710
|
+
functionItem.inputSchema || definitionJson.inputSchema
|
|
13711
|
+
),
|
|
13712
|
+
outputSchema: clonePlainJson(
|
|
13713
|
+
functionItem.outputSchema || definitionJson.outputSchema
|
|
13714
|
+
),
|
|
13715
|
+
status: functionItem.status || 'active',
|
|
13716
|
+
});
|
|
13717
|
+
}
|
|
13718
|
+
|
|
13719
|
+
function buildBackendReleaseAutomationMetadata(
|
|
13720
|
+
target,
|
|
13721
|
+
automationItem,
|
|
13722
|
+
definitionJson,
|
|
13723
|
+
viewJson
|
|
13724
|
+
) {
|
|
13725
|
+
const payload = resolveAutomationManifestPayload(target, automationItem);
|
|
13726
|
+
const enabled =
|
|
13727
|
+
automationItem.enable !== undefined
|
|
13728
|
+
? Boolean(automationItem.enable)
|
|
13729
|
+
: automationItem.isEnabled !== undefined
|
|
13730
|
+
? Boolean(automationItem.isEnabled)
|
|
13731
|
+
: true;
|
|
13732
|
+
return stripUndefinedValues({
|
|
13733
|
+
name: automationItem.name || automationItem.code,
|
|
13734
|
+
description: automationItem.description || '',
|
|
13735
|
+
formUuid: payload.formUuid,
|
|
13736
|
+
triggerConfig: clonePlainJson(payload.triggerConfig),
|
|
13737
|
+
definitionJson: clonePlainJson(definitionJson),
|
|
13738
|
+
...(viewJson !== undefined ? { viewJson: clonePlainJson(viewJson) } : {}),
|
|
13739
|
+
isPublished: Boolean(automationItem.publish || automationItem.isPublished),
|
|
13740
|
+
isEnabled: enabled,
|
|
13741
|
+
...(automationItem.tags !== undefined
|
|
13742
|
+
? {
|
|
13743
|
+
tags: Array.isArray(automationItem.tags)
|
|
13744
|
+
? automationItem.tags.join(',')
|
|
13745
|
+
: automationItem.tags,
|
|
13746
|
+
}
|
|
13747
|
+
: {}),
|
|
13748
|
+
});
|
|
13749
|
+
}
|
|
13750
|
+
|
|
13751
|
+
function withoutBackendSourcePlanes(value) {
|
|
13752
|
+
if (Array.isArray(value)) return value.map(withoutBackendSourcePlanes);
|
|
13753
|
+
if (!value || typeof value !== 'object') return value;
|
|
13754
|
+
return Object.fromEntries(
|
|
13755
|
+
Object.entries(value)
|
|
13756
|
+
.filter(
|
|
13757
|
+
([key]) =>
|
|
13758
|
+
!['sourceFile', 'sourceType', 'sourceProvenance'].includes(key)
|
|
13759
|
+
)
|
|
13760
|
+
.map(([key, item]) => [key, withoutBackendSourcePlanes(item)])
|
|
13761
|
+
);
|
|
13762
|
+
}
|
|
13763
|
+
|
|
13190
13764
|
function indexCurrentAutomationStates(items, codes) {
|
|
13191
13765
|
const codeSet = new Set(codes);
|
|
13192
13766
|
const groups = new Map();
|
|
@@ -13273,6 +13847,9 @@ async function applyBackendAutomationStateTransitions(
|
|
|
13273
13847
|
if (!current?.id) {
|
|
13274
13848
|
fail(`Backend Release 激活后缺少 Automation:${candidate.code} 当前版本`);
|
|
13275
13849
|
}
|
|
13850
|
+
if (candidate.mode === 'manifest_replacement') {
|
|
13851
|
+
return { code: candidate.code, transitions: [] };
|
|
13852
|
+
}
|
|
13276
13853
|
const plan = getPlannedResourceAction(
|
|
13277
13854
|
options,
|
|
13278
13855
|
'automation',
|
|
@@ -13389,7 +13966,8 @@ function saveBackendReleaseResourceStates(
|
|
|
13389
13966
|
function backendReleaseResultSummary(
|
|
13390
13967
|
release,
|
|
13391
13968
|
stateTransitions = new Map(),
|
|
13392
|
-
protocolVersion = null
|
|
13969
|
+
protocolVersion = null,
|
|
13970
|
+
handledSelectors = []
|
|
13393
13971
|
) {
|
|
13394
13972
|
const resources = release.resources || [];
|
|
13395
13973
|
const resolvedProtocolVersion =
|
|
@@ -13400,8 +13978,17 @@ function backendReleaseResultSummary(
|
|
|
13400
13978
|
action: 'update',
|
|
13401
13979
|
hash: release.verificationHash || release.manifestHash,
|
|
13402
13980
|
metadata: resolvedProtocolVersion
|
|
13403
|
-
? {
|
|
13404
|
-
|
|
13981
|
+
? {
|
|
13982
|
+
protocolVersion: resolvedProtocolVersion,
|
|
13983
|
+
mode: release.mode,
|
|
13984
|
+
parentReleaseId: release.parentReleaseId || null,
|
|
13985
|
+
handledSelectors,
|
|
13986
|
+
}
|
|
13987
|
+
: {
|
|
13988
|
+
mode: release.mode,
|
|
13989
|
+
parentReleaseId: release.parentReleaseId || null,
|
|
13990
|
+
handledSelectors,
|
|
13991
|
+
},
|
|
13405
13992
|
};
|
|
13406
13993
|
const isStaged = ['prepared', 'verified'].includes(
|
|
13407
13994
|
String(release.status || '').toLowerCase()
|
|
@@ -13412,6 +13999,8 @@ function backendReleaseResultSummary(
|
|
|
13412
13999
|
manifestHash: release.manifestHash,
|
|
13413
14000
|
verificationHash: release.verificationHash,
|
|
13414
14001
|
parentReleaseId: release.parentReleaseId || null,
|
|
14002
|
+
mode: release.mode,
|
|
14003
|
+
handledSelectors,
|
|
13415
14004
|
resourceCount: resources.length,
|
|
13416
14005
|
updateCount: resources.filter(resource => resource.action === 'update').length,
|
|
13417
14006
|
noopCount: resources.filter(resource => resource.action === 'noop').length,
|
|
@@ -13444,13 +14033,20 @@ async function publishBackendReleaseResources(
|
|
|
13444
14033
|
}
|
|
13445
14034
|
return;
|
|
13446
14035
|
}
|
|
13447
|
-
const protocolVersion =
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
14036
|
+
const protocolVersion = APP_FUNCTION_SECRET_CAPABILITIES.backendRelease;
|
|
14037
|
+
const handledSelectors = candidates
|
|
14038
|
+
.map(candidate =>
|
|
14039
|
+
`${candidate.kind === 'Function' ? 'function' : 'automation'}:${candidate.code}`
|
|
14040
|
+
)
|
|
14041
|
+
.sort();
|
|
14042
|
+
const resourceModes = unique(candidates.map(candidate => candidate.mode));
|
|
14043
|
+
const releaseMode =
|
|
14044
|
+
resourceModes.length > 1 ? 'mixed' : resourceModes[0] || 'source_only';
|
|
14045
|
+
const replacementReason = String(
|
|
14046
|
+
options.publishLeaseFlags?.reason ||
|
|
14047
|
+
options.publishLeaseFlags?.['replace-reason'] ||
|
|
14048
|
+
''
|
|
14049
|
+
).trim();
|
|
13454
14050
|
|
|
13455
14051
|
let head;
|
|
13456
14052
|
try {
|
|
@@ -13460,19 +14056,26 @@ async function publishBackendReleaseResources(
|
|
|
13460
14056
|
backendReleaseApiPath(target, '/head')
|
|
13461
14057
|
);
|
|
13462
14058
|
} catch (error) {
|
|
13463
|
-
if (
|
|
14059
|
+
if (isBackendReleaseFeatureAbsent(error)) {
|
|
14060
|
+
const secretRefsRequired = candidates.some(
|
|
14061
|
+
candidate =>
|
|
14062
|
+
candidate.kind === 'Function' &&
|
|
14063
|
+
functionManifestDeclaresSecretRefs(candidate.item)
|
|
14064
|
+
);
|
|
14065
|
+
if (!options.stageBackendReleaseOnly && !secretRefsRequired) {
|
|
14066
|
+
const message =
|
|
14067
|
+
'当前平台未提供 Backend Release API(head=404);非 stage-only 发布兼容降级为逐项写入。升级平台后会自动使用 Backend Release v2。';
|
|
14068
|
+
result.warnings.push(message);
|
|
14069
|
+
warn(message);
|
|
14070
|
+
return;
|
|
14071
|
+
}
|
|
13464
14072
|
const missing = new Error(
|
|
13465
|
-
'
|
|
14073
|
+
'BACKEND_RELEASE_V2_REQUIRED: 本次 Function/Automation 发布包含 v2 operation/mode,平台必须支持 Backend Release v2,禁止降级为逐项写入'
|
|
13466
14074
|
);
|
|
13467
|
-
missing.code = '
|
|
14075
|
+
missing.code = 'BACKEND_RELEASE_V2_REQUIRED';
|
|
13468
14076
|
throw missing;
|
|
13469
14077
|
}
|
|
13470
|
-
|
|
13471
|
-
const message =
|
|
13472
|
-
'当前平台未提供 Backend Release API(head=404);本次兼容降级为逐项 source PATCH。升级平台后会自动启用 Function + Automation 单事务发布。';
|
|
13473
|
-
result.warnings.push(message);
|
|
13474
|
-
warn(message);
|
|
13475
|
-
return;
|
|
14078
|
+
throw error;
|
|
13476
14079
|
}
|
|
13477
14080
|
|
|
13478
14081
|
const releaseContext = await ensureBackendReleasePublishContext(
|
|
@@ -13506,7 +14109,10 @@ async function publishBackendReleaseResources(
|
|
|
13506
14109
|
...releaseContext.control,
|
|
13507
14110
|
...(protocolVersion ? { protocolVersion } : {}),
|
|
13508
14111
|
parentReleaseId: head?.activeBackendReleaseId || null,
|
|
13509
|
-
mode:
|
|
14112
|
+
mode: releaseMode,
|
|
14113
|
+
...(releaseMode !== 'source_only'
|
|
14114
|
+
? { replacementReason }
|
|
14115
|
+
: {}),
|
|
13510
14116
|
sourceRepositoryId: releaseContext.sourceRepositoryId,
|
|
13511
14117
|
sourceRevision: releaseContext.sourceRevision,
|
|
13512
14118
|
resources,
|
|
@@ -13575,11 +14181,14 @@ async function publishBackendReleaseResources(
|
|
|
13575
14181
|
result.backendRelease = backendReleaseResultSummary(
|
|
13576
14182
|
stagedRelease,
|
|
13577
14183
|
new Map(),
|
|
13578
|
-
protocolVersion
|
|
14184
|
+
protocolVersion,
|
|
14185
|
+
handledSelectors
|
|
13579
14186
|
);
|
|
13580
14187
|
if (!result.backendRelease.stagedResource) {
|
|
13581
14188
|
fail('Backend Release stage-only 响应缺少 verified stagedResource');
|
|
13582
14189
|
}
|
|
14190
|
+
result.stagedResource = result.backendRelease.stagedResource;
|
|
14191
|
+
result.handledSelectors = handledSelectors;
|
|
13583
14192
|
return;
|
|
13584
14193
|
}
|
|
13585
14194
|
const activated = await requestWithAuth(
|
|
@@ -13660,8 +14269,10 @@ async function publishBackendReleaseResources(
|
|
|
13660
14269
|
result.backendRelease = backendReleaseResultSummary(
|
|
13661
14270
|
activated,
|
|
13662
14271
|
stateTransitions,
|
|
13663
|
-
protocolVersion
|
|
14272
|
+
protocolVersion,
|
|
14273
|
+
handledSelectors
|
|
13664
14274
|
);
|
|
14275
|
+
result.handledSelectors = handledSelectors;
|
|
13665
14276
|
}
|
|
13666
14277
|
|
|
13667
14278
|
async function publishResourceManifest(config, target, manifest, options = {}) {
|
|
@@ -14111,9 +14722,15 @@ async function runResourceChangeBaselinePreflight(
|
|
|
14111
14722
|
const artifactCache = { ...(baseline.localBaseArtifactHashes || {}) };
|
|
14112
14723
|
const missingArtifactTargets = targets.filter(targetItem => {
|
|
14113
14724
|
const frozen = baseline.resourceHeads?.[targetItem.kind]?.[targetItem.code];
|
|
14725
|
+
const cached = artifactCache[
|
|
14726
|
+
resourceBaselineTargetKey(targetItem.kind, targetItem.code)
|
|
14727
|
+
];
|
|
14728
|
+
const hasStableLineage =
|
|
14729
|
+
frozen?.fields?.sourceLineageHash &&
|
|
14730
|
+
frozen.fields.sourceLineageHash !== canonicalJsonSha256(null);
|
|
14114
14731
|
return (
|
|
14115
14732
|
frozen?.fields?.sourceArtifactHash &&
|
|
14116
|
-
!
|
|
14733
|
+
(!cached || (hasStableLineage && !cached.sourceHash))
|
|
14117
14734
|
);
|
|
14118
14735
|
});
|
|
14119
14736
|
if (missingArtifactTargets.length > 0) {
|
|
@@ -14144,7 +14761,26 @@ async function runResourceChangeBaselinePreflight(
|
|
|
14144
14761
|
const artifact = artifactCache[
|
|
14145
14762
|
resourceBaselineTargetKey(targetItem.kind, targetItem.code)
|
|
14146
14763
|
];
|
|
14147
|
-
|
|
14764
|
+
const hasStableLineage =
|
|
14765
|
+
fields.sourceLineageHash &&
|
|
14766
|
+
fields.sourceLineageHash !== canonicalJsonSha256(null);
|
|
14767
|
+
if (hasStableLineage) {
|
|
14768
|
+
if (!artifact?.sourceHash) {
|
|
14769
|
+
throwSourceBaseDiverged(
|
|
14770
|
+
`Git 基线构建未生成稳定源码血缘: ${targetItem.kind}:${targetItem.code}`,
|
|
14771
|
+
[targetItem]
|
|
14772
|
+
);
|
|
14773
|
+
}
|
|
14774
|
+
fields.sourceLineageHash =
|
|
14775
|
+
targetItem.kind === 'Automation'
|
|
14776
|
+
? canonicalJsonSha256([
|
|
14777
|
+
{
|
|
14778
|
+
nodeId: '$definition',
|
|
14779
|
+
sha256: String(artifact.sourceHash).toLowerCase(),
|
|
14780
|
+
},
|
|
14781
|
+
])
|
|
14782
|
+
: String(artifact.sourceHash).toLowerCase();
|
|
14783
|
+
} else if (artifact?.artifactHash && fields.sourceArtifactHash) {
|
|
14148
14784
|
fields.sourceArtifactHash =
|
|
14149
14785
|
targetItem.kind === 'Automation'
|
|
14150
14786
|
? canonicalJsonSha256([
|
|
@@ -15030,6 +15666,54 @@ async function publishFormSettingsResources(
|
|
|
15030
15666
|
{ frozen, artifactId }
|
|
15031
15667
|
);
|
|
15032
15668
|
saveResourceEntry(target, 'formSettings', code, { formUuid });
|
|
15669
|
+
const releaseId = data?.release?.id;
|
|
15670
|
+
const contentHash = String(
|
|
15671
|
+
data?.release?.contentHash || data?.contentHash || ''
|
|
15672
|
+
).toLowerCase();
|
|
15673
|
+
const stagedResource =
|
|
15674
|
+
data?.staged && !data?.active && releaseId
|
|
15675
|
+
? {
|
|
15676
|
+
kind: 'FormRelease',
|
|
15677
|
+
identity: { releaseId, formUuid },
|
|
15678
|
+
action: 'update',
|
|
15679
|
+
hash: contentHash,
|
|
15680
|
+
revision: stripUndefinedValues({
|
|
15681
|
+
parentReleaseId:
|
|
15682
|
+
data?.release?.parentReleaseId ||
|
|
15683
|
+
frozen.expectedParent?.releaseId ||
|
|
15684
|
+
null,
|
|
15685
|
+
baseRevision:
|
|
15686
|
+
data?.release?.baseRevision ?? frozen.expectedParent?.revision,
|
|
15687
|
+
}),
|
|
15688
|
+
metadata: {
|
|
15689
|
+
releaseStatus: data?.releaseStatus || 'staged',
|
|
15690
|
+
formCode: code,
|
|
15691
|
+
},
|
|
15692
|
+
}
|
|
15693
|
+
: null;
|
|
15694
|
+
if (
|
|
15695
|
+
!activate &&
|
|
15696
|
+
!data?.noop &&
|
|
15697
|
+
(!data?.staged || data?.active || !releaseId || !stagedResource)
|
|
15698
|
+
) {
|
|
15699
|
+
fail(
|
|
15700
|
+
`FORM_RELEASE_STAGE_REQUIRED: ${code} 的非激活 bundle 响应必须返回 staged=true、active=false、releaseId 和 contentHash`
|
|
15701
|
+
);
|
|
15702
|
+
}
|
|
15703
|
+
if (stagedResource && !/^[a-f0-9]{64}$/.test(contentHash)) {
|
|
15704
|
+
fail(
|
|
15705
|
+
`FORM_RELEASE_STAGED_RESOURCE_INVALID: ${code} 的 staged FormRelease 缺少 64 位 contentHash`
|
|
15706
|
+
);
|
|
15707
|
+
}
|
|
15708
|
+
if (stagedResource) {
|
|
15709
|
+
result.stagedResources = result.stagedResources || [];
|
|
15710
|
+
result.stagedResources.push(stagedResource);
|
|
15711
|
+
if (result.stagedResources.length === 1) {
|
|
15712
|
+
result.stagedResource = stagedResource;
|
|
15713
|
+
} else {
|
|
15714
|
+
delete result.stagedResource;
|
|
15715
|
+
}
|
|
15716
|
+
}
|
|
15033
15717
|
result.published.push({
|
|
15034
15718
|
kind: 'formSetting',
|
|
15035
15719
|
code,
|
|
@@ -15040,7 +15724,9 @@ async function publishFormSettingsResources(
|
|
|
15040
15724
|
staged: Boolean(data?.staged),
|
|
15041
15725
|
active: Boolean(data?.active),
|
|
15042
15726
|
releaseStatus: data?.releaseStatus,
|
|
15043
|
-
releaseId
|
|
15727
|
+
releaseId,
|
|
15728
|
+
contentHash: contentHash || undefined,
|
|
15729
|
+
...(stagedResource ? { stagedResource } : {}),
|
|
15044
15730
|
});
|
|
15045
15731
|
}
|
|
15046
15732
|
}
|
|
@@ -19013,11 +19699,37 @@ function normalizeJsonForPlan(value, baseDir, keyName) {
|
|
|
19013
19699
|
result.code = result.code === undefined ? '' : result.code;
|
|
19014
19700
|
result.runtimeMode = result.runtimeMode || 'trusted_node';
|
|
19015
19701
|
result.sourceType = result.sourceType || 'file_snapshot';
|
|
19702
|
+
const sourceProvenance = resolveLocalSourceProvenanceForPlan(
|
|
19703
|
+
value.sourceFile.localPath,
|
|
19704
|
+
baseDir
|
|
19705
|
+
);
|
|
19706
|
+
if (sourceProvenance) {
|
|
19707
|
+
result.sourceProvenance = {
|
|
19708
|
+
...(result.sourceProvenance || {}),
|
|
19709
|
+
...sourceProvenance,
|
|
19710
|
+
};
|
|
19711
|
+
}
|
|
19016
19712
|
}
|
|
19017
19713
|
if (result.code === '') delete result.code;
|
|
19018
19714
|
return sortObjectForStableJson(result);
|
|
19019
19715
|
}
|
|
19020
19716
|
|
|
19717
|
+
function resolveLocalSourceProvenanceForPlan(localPath, baseDir) {
|
|
19718
|
+
const rawLocalPath = String(localPath);
|
|
19719
|
+
const baseResolvedPath = path.resolve(baseDir || process.cwd(), rawLocalPath);
|
|
19720
|
+
const cwdResolvedPath = path.resolve(process.cwd(), rawLocalPath);
|
|
19721
|
+
const resolvedLocalPath = fs.existsSync(baseResolvedPath)
|
|
19722
|
+
? baseResolvedPath
|
|
19723
|
+
: cwdResolvedPath;
|
|
19724
|
+
const sourceInfo = getJsCodeSourceInfoForPlan(resolvedLocalPath);
|
|
19725
|
+
if (!sourceInfo) return null;
|
|
19726
|
+
return readJsCodeSourceProvenance(
|
|
19727
|
+
sourceInfo.workspaceRoot,
|
|
19728
|
+
sourceInfo.sourceKind,
|
|
19729
|
+
sourceInfo.scriptCode
|
|
19730
|
+
);
|
|
19731
|
+
}
|
|
19732
|
+
|
|
19021
19733
|
function normalizeLocalSourceFileForPlan(localPath, baseDir) {
|
|
19022
19734
|
const rawLocalPath = String(localPath);
|
|
19023
19735
|
const baseResolvedPath = path.resolve(baseDir || process.cwd(), rawLocalPath);
|
|
@@ -19239,7 +19951,7 @@ async function resolveJsCodeSnapshotFiles(config, profileName, value, baseDir, u
|
|
|
19239
19951
|
typeof value.sourceFile === 'object' &&
|
|
19240
19952
|
value.sourceFile.localPath
|
|
19241
19953
|
) {
|
|
19242
|
-
const
|
|
19954
|
+
const snapshot = await uploadJsCodeSnapshotFile(
|
|
19243
19955
|
config,
|
|
19244
19956
|
profileName,
|
|
19245
19957
|
value.sourceFile.localPath,
|
|
@@ -19249,7 +19961,13 @@ async function resolveJsCodeSnapshotFiles(config, profileName, value, baseDir, u
|
|
|
19249
19961
|
);
|
|
19250
19962
|
value.runtimeMode = value.runtimeMode || 'trusted_node';
|
|
19251
19963
|
value.sourceType = 'file_snapshot';
|
|
19252
|
-
value.sourceFile = sourceFile;
|
|
19964
|
+
value.sourceFile = snapshot.sourceFile;
|
|
19965
|
+
if (snapshot.sourceProvenance) {
|
|
19966
|
+
value.sourceProvenance = {
|
|
19967
|
+
...(value.sourceProvenance || {}),
|
|
19968
|
+
...snapshot.sourceProvenance,
|
|
19969
|
+
};
|
|
19970
|
+
}
|
|
19253
19971
|
value.code = value.code || '';
|
|
19254
19972
|
}
|
|
19255
19973
|
|
|
@@ -19284,6 +20002,14 @@ async function uploadJsCodeSnapshotFile(
|
|
|
19284
20002
|
if (uploadCache.has(cacheKey)) return await uploadCache.get(cacheKey);
|
|
19285
20003
|
|
|
19286
20004
|
const buffer = fs.readFileSync(bundlePath);
|
|
20005
|
+
const sourceInfo = getJsCodeSourceInfoForPlan(resolvedLocalPath);
|
|
20006
|
+
const sourceProvenance = sourceInfo
|
|
20007
|
+
? readJsCodeSourceProvenance(
|
|
20008
|
+
sourceInfo.workspaceRoot,
|
|
20009
|
+
sourceInfo.sourceKind,
|
|
20010
|
+
sourceInfo.scriptCode
|
|
20011
|
+
)
|
|
20012
|
+
: null;
|
|
19287
20013
|
const pending = requestFormWithAuth(
|
|
19288
20014
|
config,
|
|
19289
20015
|
profileName,
|
|
@@ -19303,10 +20029,14 @@ async function uploadJsCodeSnapshotFile(
|
|
|
19303
20029
|
originalName: uploaded.originalName,
|
|
19304
20030
|
contentType: uploaded.contentType,
|
|
19305
20031
|
};
|
|
19306
|
-
|
|
20032
|
+
const snapshot = {
|
|
20033
|
+
sourceFile,
|
|
20034
|
+
...(sourceProvenance ? { sourceProvenance } : {}),
|
|
20035
|
+
};
|
|
20036
|
+
uploadCache.set(cacheKey, snapshot);
|
|
19307
20037
|
// Progress belongs on stderr so `--json` keeps stdout machine-readable.
|
|
19308
20038
|
warn(`已上传JS_CODE快照: ${path.relative(process.cwd(), bundlePath)} (${sourceFile.sha256})`);
|
|
19309
|
-
return
|
|
20039
|
+
return snapshot;
|
|
19310
20040
|
});
|
|
19311
20041
|
uploadCache.set(cacheKey, pending);
|
|
19312
20042
|
try {
|
|
@@ -19317,6 +20047,38 @@ async function uploadJsCodeSnapshotFile(
|
|
|
19317
20047
|
}
|
|
19318
20048
|
}
|
|
19319
20049
|
|
|
20050
|
+
function readJsCodeSourceProvenance(workspaceRoot, sourceKind, scriptCode) {
|
|
20051
|
+
const key = `${sourceKind}/${scriptCode}`;
|
|
20052
|
+
for (const filename of [
|
|
20053
|
+
'build-cache.json',
|
|
20054
|
+
'build-cache.cli-v4.json',
|
|
20055
|
+
'build-cache.cli-v3.json',
|
|
20056
|
+
]) {
|
|
20057
|
+
try {
|
|
20058
|
+
const cache = JSON.parse(
|
|
20059
|
+
fs.readFileSync(
|
|
20060
|
+
path.join(workspaceRoot, '.openxiangda', filename),
|
|
20061
|
+
'utf8'
|
|
20062
|
+
)
|
|
20063
|
+
);
|
|
20064
|
+
const metadata = cache?.targets?.[key];
|
|
20065
|
+
const sourceHash = String(metadata?.sourceHash || '').toLowerCase();
|
|
20066
|
+
if (!/^[a-f0-9]{64}$/.test(sourceHash)) continue;
|
|
20067
|
+
return {
|
|
20068
|
+
contractVersion: 'source_lineage_v1',
|
|
20069
|
+
sourceHash,
|
|
20070
|
+
entryPath: `src/${sourceKind}/${scriptCode}/index.ts`,
|
|
20071
|
+
dependencies: Array.isArray(metadata.dependencies)
|
|
20072
|
+
? metadata.dependencies.slice().sort()
|
|
20073
|
+
: [],
|
|
20074
|
+
};
|
|
20075
|
+
} catch {
|
|
20076
|
+
// Custom workspace builders are compatible but may not emit lineage.
|
|
20077
|
+
}
|
|
20078
|
+
}
|
|
20079
|
+
return null;
|
|
20080
|
+
}
|
|
20081
|
+
|
|
19320
20082
|
async function resolveJsCodeBundlePath(localPath, scriptCode) {
|
|
19321
20083
|
const extension = path.extname(localPath).toLowerCase();
|
|
19322
20084
|
if (['.js', '.cjs', '.mjs'].includes(extension)) {
|
|
@@ -19378,10 +20140,10 @@ async function resolveJsCodeBundlePath(localPath, scriptCode) {
|
|
|
19378
20140
|
}
|
|
19379
20141
|
|
|
19380
20142
|
function runWorkspaceJsCodeBuild(workspaceRoot, resolvedScriptCode, sourceKind) {
|
|
19381
|
-
return
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
20143
|
+
return runWorkspaceJsCodeBuildBatch(
|
|
20144
|
+
workspaceRoot,
|
|
20145
|
+
[{ sourceKind, scriptCode: resolvedScriptCode }]
|
|
20146
|
+
);
|
|
19385
20147
|
}
|
|
19386
20148
|
|
|
19387
20149
|
function isUnsupportedFunctionsBuildScript(result) {
|
|
@@ -19695,26 +20457,7 @@ function getChangedManifestResourceCodes(file, resourceKey, baseRef = 'HEAD') {
|
|
|
19695
20457
|
}
|
|
19696
20458
|
|
|
19697
20459
|
function shouldReplaceResourceManifestItem(item, resourceKey, code, options = {}) {
|
|
19698
|
-
|
|
19699
|
-
if (!item?.__source) return false;
|
|
19700
|
-
const relativeFile = normalizeWorkspaceRelativePath(
|
|
19701
|
-
path.relative(process.cwd(), path.resolve(item.__source))
|
|
19702
|
-
);
|
|
19703
|
-
const scopedFiles = new Set(
|
|
19704
|
-
(options.scopedFiles || []).map(normalizeWorkspaceRelativePath)
|
|
19705
|
-
);
|
|
19706
|
-
if (!scopedFiles.has(relativeFile)) return false;
|
|
19707
|
-
const changedCodes = getChangedManifestResourceCodes(
|
|
19708
|
-
relativeFile,
|
|
19709
|
-
resourceKey,
|
|
19710
|
-
options.baseRef || 'HEAD'
|
|
19711
|
-
);
|
|
19712
|
-
if (changedCodes.length === 0) {
|
|
19713
|
-
// The SDD/change scope may refer to a committed manifest change that no
|
|
19714
|
-
// longer differs from HEAD inside the release worktree.
|
|
19715
|
-
return true;
|
|
19716
|
-
}
|
|
19717
|
-
return changedCodes.includes(String(code));
|
|
20460
|
+
return options.replaceManifest === true;
|
|
19718
20461
|
}
|
|
19719
20462
|
|
|
19720
20463
|
function buildAutomationSourcePatchSources(existingDefinition, desiredDefinition) {
|
|
@@ -20068,19 +20811,27 @@ function scanWorkspaceReliabilityIssues(files) {
|
|
|
20068
20811
|
|
|
20069
20812
|
function buildTargetedResourceReleaseCommands(targets, profileArg, changeId) {
|
|
20070
20813
|
const changeArg = changeId ? ` --change ${changeId}` : '';
|
|
20071
|
-
if (targets.resources || (targets.jsCodeNodes || []).length > 0) {
|
|
20072
|
-
return [`openxiangda resource publish --profile ${profileArg}${changeArg}`];
|
|
20073
|
-
}
|
|
20074
20814
|
const commands = [];
|
|
20075
|
-
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
[
|
|
20079
|
-
|
|
20080
|
-
|
|
20081
|
-
|
|
20815
|
+
const functions = targets.functions || [];
|
|
20816
|
+
const automations = targets.automations || [];
|
|
20817
|
+
if (functions.length > 0 || automations.length > 0) {
|
|
20818
|
+
const resourceTypes = [
|
|
20819
|
+
...(functions.length > 0 ? ['function'] : []),
|
|
20820
|
+
...(automations.length > 0 ? ['automation'] : []),
|
|
20821
|
+
];
|
|
20822
|
+
const qualify = resourceTypes.length > 1;
|
|
20823
|
+
const selectors = [
|
|
20824
|
+
...functions.map(code => (qualify ? `function:${code}` : code)),
|
|
20825
|
+
...automations.map(code => (qualify ? `automation:${code}` : code)),
|
|
20826
|
+
];
|
|
20827
|
+
commands.push(
|
|
20828
|
+
`openxiangda resource publish ${resourceTypes.join(',')} --only ${selectors.join(',')} --stage-only --profile ${profileArg}${changeArg}`
|
|
20829
|
+
);
|
|
20830
|
+
}
|
|
20831
|
+
const workflows = targets.workflows || [];
|
|
20832
|
+
if (workflows.length > 0) {
|
|
20082
20833
|
commands.push(
|
|
20083
|
-
`openxiangda resource publish
|
|
20834
|
+
`openxiangda resource publish workflow --only ${workflows.join(',')} --profile ${profileArg}${changeArg}`
|
|
20084
20835
|
);
|
|
20085
20836
|
}
|
|
20086
20837
|
return commands;
|
|
@@ -20096,9 +20847,9 @@ function buildWorkspaceReleaseCommands(targets, runtimeMode, profile, changeId)
|
|
|
20096
20847
|
(targets.workflows || []).length > 0 ||
|
|
20097
20848
|
(targets.jsCodeNodes || []).length > 0;
|
|
20098
20849
|
if (runtimeMode === 'react-spa') {
|
|
20099
|
-
|
|
20850
|
+
if (targets.forms.length > 0) {
|
|
20100
20851
|
commands.push(
|
|
20101
|
-
`openxiangda
|
|
20852
|
+
`openxiangda resource publish form-setting --only ${targets.forms.join(',')} --profile ${profileArg}${changeId ? ` --change ${changeId}` : ''}`
|
|
20102
20853
|
);
|
|
20103
20854
|
}
|
|
20104
20855
|
if (hasResourceTargets) {
|
|
@@ -20106,7 +20857,17 @@ function buildWorkspaceReleaseCommands(targets, runtimeMode, profile, changeId)
|
|
|
20106
20857
|
}
|
|
20107
20858
|
if (targets.runtime) {
|
|
20108
20859
|
commands.push(
|
|
20109
|
-
`openxiangda runtime deploy --profile ${profileArg}${changeId ? ` --change ${changeId}` : ''}`
|
|
20860
|
+
`openxiangda runtime deploy --no-activate --profile ${profileArg}${changeId ? ` --change ${changeId}` : ''}`
|
|
20861
|
+
);
|
|
20862
|
+
}
|
|
20863
|
+
if (
|
|
20864
|
+
targets.forms.length > 0 ||
|
|
20865
|
+
(targets.functions || []).length > 0 ||
|
|
20866
|
+
(targets.automations || []).length > 0 ||
|
|
20867
|
+
targets.runtime
|
|
20868
|
+
) {
|
|
20869
|
+
commands.push(
|
|
20870
|
+
`openxiangda release app-finalize --staged-resources-json .openxiangda/releases/${changeId || 'change'}/staged-resources.json --profile ${profileArg}${changeId ? ` --change ${changeId}` : ''}`
|
|
20110
20871
|
);
|
|
20111
20872
|
}
|
|
20112
20873
|
return commands;
|
|
@@ -20170,12 +20931,35 @@ function buildWorkspaceReleasePlan(options = {}) {
|
|
|
20170
20931
|
name: issue.rule,
|
|
20171
20932
|
message: `${issue.file}:${issue.line} ${issue.message}`,
|
|
20172
20933
|
}));
|
|
20173
|
-
const
|
|
20934
|
+
const generatedCommands = buildWorkspaceReleaseCommands(
|
|
20174
20935
|
targets,
|
|
20175
20936
|
runtimeMode,
|
|
20176
20937
|
options.profile,
|
|
20177
20938
|
changeScope?.changeId
|
|
20178
20939
|
);
|
|
20940
|
+
const commands =
|
|
20941
|
+
changeScope?.commands?.length > 0
|
|
20942
|
+
? [...changeScope.commands]
|
|
20943
|
+
: generatedCommands;
|
|
20944
|
+
if (!changeScope?.commands?.length && targets.resources) {
|
|
20945
|
+
errors.push({
|
|
20946
|
+
name: 'sdd-release-generic-resource-selector-required',
|
|
20947
|
+
message:
|
|
20948
|
+
'变更包含尚未归一化为 code 的通用资源;已保留可确定的 Function/Automation/Workflow 命令,请在 release.json.commands 中补一条精确 --only/--code 命令覆盖剩余资源。',
|
|
20949
|
+
});
|
|
20950
|
+
}
|
|
20951
|
+
if (
|
|
20952
|
+
!changeScope?.commands?.length &&
|
|
20953
|
+
(targets.jsCodeNodes || []).length > 0 &&
|
|
20954
|
+
(targets.functions || []).length === 0 &&
|
|
20955
|
+
(targets.automations || []).length === 0 &&
|
|
20956
|
+
(targets.workflows || []).length === 0
|
|
20957
|
+
) {
|
|
20958
|
+
errors.push({
|
|
20959
|
+
name: 'sdd-release-js-code-owner-required',
|
|
20960
|
+
message: `JS_CODE 源码尚未映射到可发布资源: ${targets.jsCodeNodes.join(', ')};请在 coverage/release 中声明所属 Function、Automation 或 Workflow。`,
|
|
20961
|
+
});
|
|
20962
|
+
}
|
|
20179
20963
|
const sdd = options.skipSdd
|
|
20180
20964
|
? null
|
|
20181
20965
|
: buildSddGovernanceReport({
|
|
@@ -20290,6 +21074,7 @@ function buildExplicitSddReleasePlan(targets, options = {}) {
|
|
|
20290
21074
|
commands,
|
|
20291
21075
|
changeId: options.changeId,
|
|
20292
21076
|
verifyStage: options.verifyStage || 'prepublish',
|
|
21077
|
+
actualCommands: options.actualCommands === true,
|
|
20293
21078
|
});
|
|
20294
21079
|
return {
|
|
20295
21080
|
targets,
|
|
@@ -20301,6 +21086,21 @@ function buildExplicitSddReleasePlan(targets, options = {}) {
|
|
|
20301
21086
|
|
|
20302
21087
|
function assertSddReleaseGate(plan, flags, command) {
|
|
20303
21088
|
if (!plan?.sdd || plan.sdd.errors.length === 0) return;
|
|
21089
|
+
const nonBypassable = new Set([
|
|
21090
|
+
'sdd-release-direct-form-schema-forbidden',
|
|
21091
|
+
'sdd-release-direct-form-activation-forbidden',
|
|
21092
|
+
'sdd-release-resource-scope-missing',
|
|
21093
|
+
'sdd-release-runtime-direct-activation-forbidden',
|
|
21094
|
+
'sdd-release-backend-direct-activation-forbidden',
|
|
21095
|
+
'sdd-release-backend-scope-inexact',
|
|
21096
|
+
'sdd-release-form-scope-inexact',
|
|
21097
|
+
]);
|
|
21098
|
+
const hardFailure = plan.sdd.errors.find(error =>
|
|
21099
|
+
nonBypassable.has(error.name)
|
|
21100
|
+
);
|
|
21101
|
+
if (hardFailure) {
|
|
21102
|
+
fail(`${hardFailure.message}\n该原子发布安全门禁不可通过 --sdd-bypass 绕过。`);
|
|
21103
|
+
}
|
|
20304
21104
|
if (flags['sdd-bypass']) {
|
|
20305
21105
|
const bypass = appendBypassLog({
|
|
20306
21106
|
cwd: process.cwd(),
|