openxiangda 1.0.170 → 1.0.172
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 +10 -9
- package/lib/cli.js +107 -19
- package/lib/release-mainline.js +15 -0
- package/lib/sdd.js +203 -22
- package/lib/workspace-init.js +13 -3
- package/openxiangda-skills/SKILL.md +13 -15
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +10 -13
- package/package.json +1 -1
- 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/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +17 -1
- package/packages/sdk/dist/runtime/react.d.ts +17 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda.mdc +3 -3
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda.md +3 -3
- package/templates/openxiangda-react-spa/AGENTS.md +11 -14
- package/templates/openxiangda-react-spa/app-workspace.config.ts +1 -0
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda.mdc +3 -3
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda.md +3 -3
- package/templates/sy-lowcode-app-workspace/AGENTS.md +3 -3
- package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +1 -0
- package/templates/sy-lowcode-app-workspace/scripts/guard-publish.mjs +2 -1
- package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +1 -0
package/lib/sdd.js
CHANGED
|
@@ -155,6 +155,9 @@ function getSddGovernanceConfig(options = {}) {
|
|
|
155
155
|
configured && /strictHighRisk\s*:\s*false/.test(block)
|
|
156
156
|
? false
|
|
157
157
|
: enabled;
|
|
158
|
+
const strictDocumentation = Boolean(
|
|
159
|
+
configured && /strictDocumentation\s*:\s*true/.test(block)
|
|
160
|
+
);
|
|
158
161
|
const pathMatch = block.match(/path\s*:\s*['"]([^'"]+)['"]/);
|
|
159
162
|
const schemaMatch = block.match(/schemaVersion\s*:\s*['"]([^'"]+)['"]/);
|
|
160
163
|
const sddPath = normalizeRelPath(pathMatch?.[1] || DEFAULT_SDD_DIR);
|
|
@@ -170,6 +173,7 @@ function getSddGovernanceConfig(options = {}) {
|
|
|
170
173
|
return {
|
|
171
174
|
enabled,
|
|
172
175
|
strictHighRisk,
|
|
176
|
+
strictDocumentation,
|
|
173
177
|
path: sddPath,
|
|
174
178
|
rootDir,
|
|
175
179
|
initialized,
|
|
@@ -196,7 +200,9 @@ function buildDefaultConfigYaml() {
|
|
|
196
200
|
' requireApprovalForHighRisk: true',
|
|
197
201
|
' requireAffectedCoverage: true',
|
|
198
202
|
' requireCoverageManifest: true',
|
|
199
|
-
' requireScenarioEvidence:
|
|
203
|
+
' requireScenarioEvidence: false',
|
|
204
|
+
' requireChecklistCompletion: false',
|
|
205
|
+
' releaseModel: publish-from-main-v1',
|
|
200
206
|
' archiveMergesDeltaSpecs: true',
|
|
201
207
|
'',
|
|
202
208
|
].join('\n');
|
|
@@ -821,6 +827,146 @@ function proposeSddChange(options = {}) {
|
|
|
821
827
|
};
|
|
822
828
|
}
|
|
823
829
|
|
|
830
|
+
function createMainlineSddBundle(options = {}) {
|
|
831
|
+
const governance = getSddGovernanceConfig(options);
|
|
832
|
+
if (!governance.initialized) {
|
|
833
|
+
throw new Error('SDD 尚未初始化,不能创建 mainline bundle');
|
|
834
|
+
}
|
|
835
|
+
const changeId = normalizeChangeId(options.changeId);
|
|
836
|
+
const sourceIds = Array.from(
|
|
837
|
+
new Set(splitList(options.changes || options.changeIds).map(normalizeChangeId))
|
|
838
|
+
).filter(id => id !== changeId);
|
|
839
|
+
if (sourceIds.length === 0) {
|
|
840
|
+
throw new Error('mainline bundle 至少需要一个 --changes <change-a,change-b>');
|
|
841
|
+
}
|
|
842
|
+
const sourceChanges = sourceIds.map(id =>
|
|
843
|
+
loadSddChange({ cwd: options.cwd, configText: options.configText, changeId: id })
|
|
844
|
+
);
|
|
845
|
+
const invalid = sourceChanges.find(
|
|
846
|
+
item => item.meta.status !== 'approved' || !item.meta.approvedAt
|
|
847
|
+
);
|
|
848
|
+
if (invalid) {
|
|
849
|
+
const error = new Error(
|
|
850
|
+
`SDD_MAINLINE_BUNDLE_CHANGE_NOT_APPROVED: ${invalid.changeId} 尚未 approved`
|
|
851
|
+
);
|
|
852
|
+
error.code = 'SDD_MAINLINE_BUNDLE_CHANGE_NOT_APPROVED';
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
const nested = sourceChanges.find(item => item.meta.changeMode === 'mainline-bundle');
|
|
856
|
+
if (nested) {
|
|
857
|
+
throw new Error(`mainline bundle 不允许嵌套: ${nested.changeId}`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const affected = mergeAffectedScopes(
|
|
861
|
+
...sourceChanges.flatMap(item => [
|
|
862
|
+
item.meta.affected,
|
|
863
|
+
normalizeCoverageResources(item.coverage, {}),
|
|
864
|
+
item.release?.targets,
|
|
865
|
+
{ files: item.release?.changedFiles || [] },
|
|
866
|
+
])
|
|
867
|
+
);
|
|
868
|
+
if (affected.functions.length > 0 || affected.automations.length > 0) {
|
|
869
|
+
affected.backend = true;
|
|
870
|
+
}
|
|
871
|
+
const createdAt = nowIso();
|
|
872
|
+
const meta = {
|
|
873
|
+
...createChangeMetadata(changeId, {
|
|
874
|
+
title: options.title || `Mainline release: ${sourceIds.join(', ')}`,
|
|
875
|
+
affected,
|
|
876
|
+
changeMode: 'mainline-bundle',
|
|
877
|
+
riskLevel: 'medium',
|
|
878
|
+
}),
|
|
879
|
+
status: 'approved',
|
|
880
|
+
approvedAt: createdAt,
|
|
881
|
+
approvedBy: 'mainline-release-coordinator',
|
|
882
|
+
approvalSummary: `Aggregate approved changes on authoritative mainline: ${sourceIds.join(', ')}`,
|
|
883
|
+
bundledChanges: sourceIds,
|
|
884
|
+
createdAt,
|
|
885
|
+
updatedAt: createdAt,
|
|
886
|
+
};
|
|
887
|
+
const coverage = {
|
|
888
|
+
schemaVersion: SDD_SCHEMA_VERSION,
|
|
889
|
+
changeId,
|
|
890
|
+
generatedAt: createdAt,
|
|
891
|
+
resources: affected,
|
|
892
|
+
requirements: sourceChanges.map(item => ({
|
|
893
|
+
id: `bundle-${slug(item.changeId, 'change')}`,
|
|
894
|
+
title: item.meta.title || item.changeId,
|
|
895
|
+
spec: 'specs/app/spec.md',
|
|
896
|
+
scenarios: [
|
|
897
|
+
{
|
|
898
|
+
id: `${slug(item.changeId, 'change')}-merged`,
|
|
899
|
+
title: `${item.changeId} is included in the mainline release`,
|
|
900
|
+
evidence: [],
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
covers: mergeAffectedScopes(
|
|
904
|
+
item.meta.affected,
|
|
905
|
+
normalizeCoverageResources(item.coverage, {}),
|
|
906
|
+
item.release?.targets,
|
|
907
|
+
{ files: item.release?.changedFiles || [] }
|
|
908
|
+
),
|
|
909
|
+
})),
|
|
910
|
+
};
|
|
911
|
+
const release = {
|
|
912
|
+
schemaVersion: SDD_SCHEMA_VERSION,
|
|
913
|
+
changeId,
|
|
914
|
+
mainlinePolicy: 'publish-from-main-v1',
|
|
915
|
+
bundledChanges: sourceIds,
|
|
916
|
+
targets: {
|
|
917
|
+
forms: affected.forms,
|
|
918
|
+
pages: affected.pages,
|
|
919
|
+
functions: affected.functions,
|
|
920
|
+
automations: affected.automations,
|
|
921
|
+
workflows: affected.workflows,
|
|
922
|
+
jsCodeNodes: affected.jsCodeNodes,
|
|
923
|
+
resources: affected.resources,
|
|
924
|
+
runtime: affected.runtime,
|
|
925
|
+
other: [],
|
|
926
|
+
},
|
|
927
|
+
changedFiles: affected.files,
|
|
928
|
+
commands: [],
|
|
929
|
+
verifiedAt: null,
|
|
930
|
+
};
|
|
931
|
+
const changeDir = path.join(governance.rootDir, 'changes', changeId);
|
|
932
|
+
if (fs.existsSync(changeDir) && !options.force) {
|
|
933
|
+
throw new Error(`change 已存在: ${changeId}`);
|
|
934
|
+
}
|
|
935
|
+
if (fs.existsSync(changeDir)) fs.rmSync(changeDir, { recursive: true, force: true });
|
|
936
|
+
ensureDir(path.join(changeDir, 'specs', 'app'));
|
|
937
|
+
writeJsonFile(path.join(changeDir, CHANGE_META_FILE), meta);
|
|
938
|
+
writeJsonFile(path.join(changeDir, COVERAGE_FILE), coverage);
|
|
939
|
+
writeJsonFile(path.join(changeDir, RELEASE_FILE), release);
|
|
940
|
+
fs.writeFileSync(
|
|
941
|
+
path.join(changeDir, 'proposal.md'),
|
|
942
|
+
`# Mainline Release Bundle: ${changeId}\n\nApproved changes: ${sourceIds.join(', ')}.\n\nThe exact structured scope in coverage.json is authoritative.\n`
|
|
943
|
+
);
|
|
944
|
+
fs.writeFileSync(
|
|
945
|
+
path.join(changeDir, 'design.md'),
|
|
946
|
+
'# Design\n\nAll bundled changes are merged and pushed before one staged, atomic application activation.\n'
|
|
947
|
+
);
|
|
948
|
+
fs.writeFileSync(
|
|
949
|
+
path.join(changeDir, 'tasks.md'),
|
|
950
|
+
'# Tasks\n\n## Implementation\n\n- [x] Aggregate approved changes\n\n## Prepublish\n\n- [x] Generate exact structured release scope\n\n## Postpublish\n\n- [ ] Record publish result\n'
|
|
951
|
+
);
|
|
952
|
+
fs.writeFileSync(
|
|
953
|
+
path.join(changeDir, 'evidence.md'),
|
|
954
|
+
'# Evidence\n\n## Verification\n\n- [x] Source changes were approved before bundling\n\n## Release\n\n- [ ] Record atomic activation result\n'
|
|
955
|
+
);
|
|
956
|
+
fs.writeFileSync(
|
|
957
|
+
path.join(changeDir, 'specs', 'app', 'spec.md'),
|
|
958
|
+
`# Mainline Bundle Spec\n\n## ADDED Requirements\n\n### Requirement: Publish ${sourceIds.length} approved changes together\nThe application SHALL activate the exact bundled scope from one authoritative mainline commit.\n\n#### Scenario: Atomic mainline activation\n- **Given** all bundled changes are approved and merged to the remote default branch\n- **When** the release coordinator publishes bundle ${changeId}\n- **Then** changed child releases activate together and unrelated resources remain unchanged\n`
|
|
959
|
+
);
|
|
960
|
+
return {
|
|
961
|
+
schemaVersion: SDD_SCHEMA_VERSION,
|
|
962
|
+
change: meta,
|
|
963
|
+
coverage,
|
|
964
|
+
release,
|
|
965
|
+
bundledChanges: sourceIds,
|
|
966
|
+
dir: path.relative(options.cwd || process.cwd(), changeDir).replace(/\\/g, '/'),
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
824
970
|
function loadSddChange(options = {}) {
|
|
825
971
|
const governance = getSddGovernanceConfig(options);
|
|
826
972
|
const changeId = normalizeChangeId(options.changeId);
|
|
@@ -1527,9 +1673,20 @@ function readUncheckedTasks(changeDir, stagePolicy = getSddVerifyStagePolicy('de
|
|
|
1527
1673
|
});
|
|
1528
1674
|
}
|
|
1529
1675
|
|
|
1530
|
-
function validateCoverageManifest(
|
|
1676
|
+
function validateCoverageManifest(
|
|
1677
|
+
coverage,
|
|
1678
|
+
meta,
|
|
1679
|
+
targets,
|
|
1680
|
+
highRiskFiles,
|
|
1681
|
+
legacyMode = false,
|
|
1682
|
+
options = {}
|
|
1683
|
+
) {
|
|
1531
1684
|
const errors = [];
|
|
1532
1685
|
const warnings = [];
|
|
1686
|
+
const addDocumentationIssue = issue => {
|
|
1687
|
+
if (options.strictDocumentation) errors.push(issue);
|
|
1688
|
+
else warnings.push(issue);
|
|
1689
|
+
};
|
|
1533
1690
|
if (!coverage || coverage.__invalid) {
|
|
1534
1691
|
const message = coverage?.__invalid
|
|
1535
1692
|
? `coverage.json 无效: ${coverage.error}`
|
|
@@ -1545,15 +1702,24 @@ function validateCoverageManifest(coverage, meta, targets, highRiskFiles, legacy
|
|
|
1545
1702
|
});
|
|
1546
1703
|
}
|
|
1547
1704
|
if (!Array.isArray(coverage.requirements) || coverage.requirements.length === 0) {
|
|
1548
|
-
|
|
1705
|
+
addDocumentationIssue({
|
|
1706
|
+
name: 'sdd-coverage-empty',
|
|
1707
|
+
message: 'coverage.json 建议包含 requirements 映射;streamlined 模式仅以精确资源和文件范围作为发布硬门禁',
|
|
1708
|
+
});
|
|
1549
1709
|
} else {
|
|
1550
1710
|
for (const requirement of coverage.requirements) {
|
|
1551
1711
|
if (!requirement.id || !requirement.title || !requirement.spec) {
|
|
1552
|
-
|
|
1712
|
+
addDocumentationIssue({
|
|
1713
|
+
name: 'sdd-coverage-requirement-invalid',
|
|
1714
|
+
message: 'coverage requirement 缺少 id/title/spec',
|
|
1715
|
+
});
|
|
1553
1716
|
break;
|
|
1554
1717
|
}
|
|
1555
1718
|
if (!Array.isArray(requirement.scenarios) || requirement.scenarios.length === 0) {
|
|
1556
|
-
|
|
1719
|
+
addDocumentationIssue({
|
|
1720
|
+
name: 'sdd-coverage-scenarios-missing',
|
|
1721
|
+
message: `coverage requirement ${requirement.id} 缺少 scenarios`,
|
|
1722
|
+
});
|
|
1557
1723
|
break;
|
|
1558
1724
|
}
|
|
1559
1725
|
}
|
|
@@ -1678,6 +1844,9 @@ function buildUpdatedReleaseManifest(loaded, releasePlan, verificationStage = 'd
|
|
|
1678
1844
|
const reviewedCommands = Array.isArray(loaded.release?.commands)
|
|
1679
1845
|
? loaded.release.commands.filter(command => String(command || '').trim())
|
|
1680
1846
|
: [];
|
|
1847
|
+
const canonicalCommands = Array.isArray(releasePlan?.commands)
|
|
1848
|
+
? releasePlan.commands.filter(command => String(command || '').trim())
|
|
1849
|
+
: [];
|
|
1681
1850
|
return {
|
|
1682
1851
|
schemaVersion: SDD_SCHEMA_VERSION,
|
|
1683
1852
|
changeId: loaded.changeId,
|
|
@@ -1685,9 +1854,9 @@ function buildUpdatedReleaseManifest(loaded, releasePlan, verificationStage = 'd
|
|
|
1685
1854
|
targets: releasePlan?.targets || loaded.release?.targets || {},
|
|
1686
1855
|
changedFiles: releasePlan?.changedFiles || loaded.release?.changedFiles || [],
|
|
1687
1856
|
commands:
|
|
1688
|
-
|
|
1689
|
-
?
|
|
1690
|
-
:
|
|
1857
|
+
canonicalCommands.length > 0
|
|
1858
|
+
? canonicalCommands
|
|
1859
|
+
: reviewedCommands,
|
|
1691
1860
|
verifiedAt,
|
|
1692
1861
|
verificationStage,
|
|
1693
1862
|
verificationStages: {
|
|
@@ -1752,7 +1921,7 @@ function recordSddReleaseSourceRevision(options = {}) {
|
|
|
1752
1921
|
...(loaded.release && !loaded.release.__invalid ? loaded.release : {}),
|
|
1753
1922
|
schemaVersion: SDD_SCHEMA_VERSION,
|
|
1754
1923
|
changeId: loaded.changeId,
|
|
1755
|
-
mainlinePolicy: '
|
|
1924
|
+
mainlinePolicy: 'publish-from-main-v1',
|
|
1756
1925
|
releaseSourceRevision,
|
|
1757
1926
|
};
|
|
1758
1927
|
if (JSON.stringify(release) !== JSON.stringify(loaded.release)) {
|
|
@@ -2079,19 +2248,30 @@ function verifySddChange(options = {}) {
|
|
|
2079
2248
|
message: `affected 未覆盖高风险文件: ${fileCoverage.missing.join(', ')}`,
|
|
2080
2249
|
});
|
|
2081
2250
|
}
|
|
2082
|
-
const
|
|
2251
|
+
const strictDocumentation = Boolean(loaded.governance.strictDocumentation);
|
|
2252
|
+
const coverageValidation = validateCoverageManifest(
|
|
2253
|
+
loaded.coverage,
|
|
2254
|
+
loaded.meta,
|
|
2255
|
+
targets,
|
|
2256
|
+
highRiskFiles,
|
|
2257
|
+
legacyMode,
|
|
2258
|
+
{ strictDocumentation }
|
|
2259
|
+
);
|
|
2083
2260
|
errors.push(...coverageValidation.errors);
|
|
2084
2261
|
warnings.push(...coverageValidation.warnings);
|
|
2085
2262
|
const deltaValidation = validateDeltaSpecs(loaded.changeDir);
|
|
2086
|
-
errors.push(...deltaValidation.errors);
|
|
2263
|
+
if (strictDocumentation) errors.push(...deltaValidation.errors);
|
|
2264
|
+
else warnings.push(...deltaValidation.errors);
|
|
2087
2265
|
warnings.push(...deltaValidation.warnings);
|
|
2088
2266
|
const uncheckedTasks = readUncheckedTasks(loaded.changeDir, stagePolicy);
|
|
2089
2267
|
if (uncheckedTasks.length > 0) {
|
|
2090
|
-
|
|
2268
|
+
const issue = {
|
|
2091
2269
|
name: 'sdd-unfinished-tasks',
|
|
2092
|
-
message: `${uncheckedTasks.length} 个 tasks.md
|
|
2270
|
+
message: `${uncheckedTasks.length} 个 tasks.md 任务未完成;streamlined 模式不阻断主线发布`,
|
|
2093
2271
|
details: uncheckedTasks,
|
|
2094
|
-
}
|
|
2272
|
+
};
|
|
2273
|
+
if (strictDocumentation) errors.push(issue);
|
|
2274
|
+
else warnings.push(issue);
|
|
2095
2275
|
}
|
|
2096
2276
|
const evidenceFile = path.join(loaded.changeDir, 'evidence.md');
|
|
2097
2277
|
const uncheckedEvidence = readUncheckedChecklistForStages(evidenceFile, {
|
|
@@ -2100,14 +2280,16 @@ function verifySddChange(options = {}) {
|
|
|
2100
2280
|
missingMessage: 'evidence.md 缺失',
|
|
2101
2281
|
});
|
|
2102
2282
|
if (uncheckedEvidence.length > 0) {
|
|
2103
|
-
|
|
2283
|
+
const issue = {
|
|
2104
2284
|
name: 'sdd-evidence-incomplete',
|
|
2105
|
-
message: `${uncheckedEvidence.length} 个 evidence.md
|
|
2285
|
+
message: `${uncheckedEvidence.length} 个 evidence.md 证据项未完成;streamlined 模式不阻断主线发布`,
|
|
2106
2286
|
details: uncheckedEvidence,
|
|
2107
|
-
}
|
|
2287
|
+
};
|
|
2288
|
+
if (strictDocumentation) errors.push(issue);
|
|
2289
|
+
else warnings.push(issue);
|
|
2108
2290
|
}
|
|
2109
2291
|
const release =
|
|
2110
|
-
options.
|
|
2292
|
+
options.releasePlan && loaded.release && !loaded.release.__invalid
|
|
2111
2293
|
? buildUpdatedReleaseManifest(loaded, options.releasePlan, verificationStage)
|
|
2112
2294
|
: loaded.release;
|
|
2113
2295
|
if (stagePolicy.requireRelease) {
|
|
@@ -2116,6 +2298,7 @@ function verifySddChange(options = {}) {
|
|
|
2116
2298
|
warnings.push(...releaseValidation.warnings);
|
|
2117
2299
|
const reviewedPlannedCommands = loaded.meta?.release?.plannedCommands;
|
|
2118
2300
|
if (
|
|
2301
|
+
strictDocumentation &&
|
|
2119
2302
|
!legacyMode &&
|
|
2120
2303
|
Array.isArray(reviewedPlannedCommands) &&
|
|
2121
2304
|
reviewedPlannedCommands.length > 0
|
|
@@ -2151,10 +2334,7 @@ function verifySddChange(options = {}) {
|
|
|
2151
2334
|
release: {
|
|
2152
2335
|
...(loaded.meta.release || {}),
|
|
2153
2336
|
plannedCommands:
|
|
2154
|
-
|
|
2155
|
-
loaded.meta.release.plannedCommands.length > 0
|
|
2156
|
-
? loaded.meta.release.plannedCommands
|
|
2157
|
-
: release?.commands || options.releasePlan?.commands || [],
|
|
2337
|
+
release?.commands || options.releasePlan?.commands || [],
|
|
2158
2338
|
lastVerifiedAt: verifiedAt,
|
|
2159
2339
|
},
|
|
2160
2340
|
verification: {
|
|
@@ -2474,6 +2654,7 @@ module.exports = {
|
|
|
2474
2654
|
buildSddGovernanceReport,
|
|
2475
2655
|
buildSddStatus,
|
|
2476
2656
|
createQuickSddChange,
|
|
2657
|
+
createMainlineSddBundle,
|
|
2477
2658
|
getSddGovernanceConfig,
|
|
2478
2659
|
getSddChangeScope,
|
|
2479
2660
|
getSddVerifyStagePolicy,
|
package/lib/workspace-init.js
CHANGED
|
@@ -185,13 +185,23 @@ function buildNextSteps(targetDir, installedDependencies, bound, runtime) {
|
|
|
185
185
|
steps.push(`openxiangda env --profile ${profileArg}`);
|
|
186
186
|
steps.push('git switch -c codex/<change>');
|
|
187
187
|
steps.push('openxiangda sdd propose <change>');
|
|
188
|
+
steps.push('完成开发、测试、approve 并提交 codex/<change>,不要从 feature worktree 发布');
|
|
189
|
+
steps.push(
|
|
190
|
+
'git switch <main-branch> && git merge --no-ff codex/<change> && git push origin <main-branch>'
|
|
191
|
+
);
|
|
192
|
+
steps.push(
|
|
193
|
+
'openxiangda sdd bundle <release-change> --changes <change-a,change-b>'
|
|
194
|
+
);
|
|
195
|
+
steps.push(
|
|
196
|
+
'git add openspec/changes/<release-change> && git commit -m "chore: prepare <release-change>" && git push origin <main-branch>'
|
|
197
|
+
);
|
|
188
198
|
steps.push(
|
|
189
199
|
runtime === 'react-spa'
|
|
190
|
-
? `openxiangda runtime deploy --no-activate --change <change> --profile ${profileArg}`
|
|
191
|
-
: `openxiangda workspace publish --dry-run --only <exact-codes> --change <change> --profile ${profileArg}`
|
|
200
|
+
? `openxiangda runtime deploy --no-activate --change <release-change> --profile ${profileArg}`
|
|
201
|
+
: `openxiangda workspace publish --dry-run --only <exact-codes> --change <release-change> --profile ${profileArg}`
|
|
192
202
|
);
|
|
193
203
|
steps.push(
|
|
194
|
-
|
|
204
|
+
`原子激活后运行 openxiangda release integration-status --profile ${profileArg},再运行 openxiangda release end --profile ${profileArg}`
|
|
195
205
|
);
|
|
196
206
|
return steps;
|
|
197
207
|
}
|
|
@@ -26,7 +26,7 @@ This file is a router and safety card. Read only the one or two subskills select
|
|
|
26
26
|
## Scope before work
|
|
27
27
|
|
|
28
28
|
1. Work from the app workspace root and pass `--profile <name>` to every write or release command.
|
|
29
|
-
2. In a shared or dirty checkout, use an isolated Git worktree/branch
|
|
29
|
+
2. In a shared or dirty checkout, use an isolated Git worktree/branch for development. Merge approved task commits into the authoritative remote default branch before any live release. Formal publish runs once from a clean local `main`/`master` that exactly equals the remote tip; feature worktrees never publish.
|
|
30
30
|
3. Give the task one stable SDD change id. Use `--change <id>` on context, plan, check, verify, publish, and archive commands when supported.
|
|
31
31
|
4. Plan and publish exact resource codes with `--only <codes>` or `--code <code>`; a type-only or full-resource publish is allowed only when the dependency closure intentionally contains the whole type/application.
|
|
32
32
|
5. A React SPA page change rebuilds one application runtime. Commit the complete build input first, upload/preview with `runtime deploy --no-activate`, and promote only from a clean merged release head that descends from the online Runtime source revision.
|
|
@@ -60,9 +60,9 @@ openxiangda sdd quick <change> --kind copy --pages <page> --files <file> --summa
|
|
|
60
60
|
|
|
61
61
|
Function quick changes require the complete `--risk-json` assessment; Automation, Workflow, schema, permissions, auth/public access, migrations, destructive and cross-resource changes cannot use quick mode. Before release run `--stage prepublish`; use `postpublish` only after promotion and `archive` only when all evidence exists.
|
|
62
62
|
|
|
63
|
-
Keep development lightweight:
|
|
63
|
+
Keep development lightweight: structured approval plus exact resource/file scope are authoritative. By default, unfinished tasks/evidence/spec prose produces warnings at every stage and does not block release; set `strictDocumentation: true` only for a workspace that intentionally wants prose as a gate. Actual publish argv, mainline identity, CAS, lease, destructive scope, and atomic activation remain hard gates.
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
The CLI regenerates one canonical command set from structured coverage instead of asking agents to maintain two command copies. A single Function uses `resource publish function --only <code>`; qualified selectors are used only when Function and Automation are mixed. Generated React SPA plans use exact Form bundles, one Backend `--stage-only` command, Runtime `--no-activate`, and Root `app-finalize`. Actual argv is validated again at write time.
|
|
66
66
|
|
|
67
67
|
## Safe release recipes
|
|
68
68
|
|
|
@@ -78,24 +78,22 @@ openxiangda workspace publish --change <change> --profile <name> --only pages/a,
|
|
|
78
78
|
React SPA resources/runtime:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
openxiangda
|
|
84
|
-
|
|
85
|
-
openxiangda
|
|
86
|
-
openxiangda
|
|
87
|
-
|
|
88
|
-
openxiangda release app-finalize --change <change> --staged-resources-json <JSON|file> --profile <name>
|
|
89
|
-
# Merge/fast-forward the frozen release commit into the remote default branch.
|
|
81
|
+
# In task worktrees: implement, test, commit, then merge/fast-forward and push all approved changes.
|
|
82
|
+
# In the clean main checkout:
|
|
83
|
+
openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
84
|
+
# Commit and push the generated bundle on main, then:
|
|
85
|
+
openxiangda sdd verify <release-change> --changed --stage prepublish --profile <name>
|
|
86
|
+
openxiangda release begin --change <release-change> --profile <name>
|
|
87
|
+
# Execute the canonical exact commands printed by workspace plan/verify.
|
|
90
88
|
openxiangda release integration-status --profile <name>
|
|
91
89
|
openxiangda release end --profile <name>
|
|
92
90
|
```
|
|
93
91
|
|
|
94
92
|
`resource plan` and publish dry-runs are strictly GET/HEAD-only. `READ_ONLY_AUTH_REQUIRED` means the access token expired; run `openxiangda auth refresh --profile <name>` or log in again before retrying. Never add an automatic refresh POST inside a plan.
|
|
95
93
|
|
|
96
|
-
`release begin --change`
|
|
94
|
+
`release begin --change` freezes a clean committed `HEAD` only when the current branch is the authoritative default `main`/`master` and its commit exactly equals the live remote tip. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A feature worktree or unpushed main receives `RELEASE_SOURCE_MAINLINE_REQUIRED` / `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`; merge, test, push, and start the one mainline release instead of forcing it.
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
Because promotion begins from the already-pushed authoritative mainline, `release integration-status` should pass immediately after activation. Run it, then `release end`; no post-release branch merge is required.
|
|
99
97
|
|
|
100
98
|
`runtime deploy` separately freezes the clean committed `HEAD` as `sourceRevision` together with the current active Runtime parent before any build or upload. Any deploy whose source does not descend from the online Runtime fails with `RUNTIME_SOURCE_BASE_DIVERGED`, including `--no-activate`; this prevents staging an old preview for later activation. `--no-build` does not bypass this guard. Only an intentional audited rollback may use `--allow-runtime-rollback --reason "<at least 8 characters>"`, and that flag never permits dirty or non-Git input. SDD evidence under `openspec/` and generated/state paths do not make Runtime source dirty.
|
|
101
99
|
|
|
@@ -105,7 +103,7 @@ When a Function or Automation is selected because its TypeScript source changed,
|
|
|
105
103
|
|
|
106
104
|
An App Function may declare metadata-only top-level `secretRefs: [{ name, required }]` only with `function_v2` + `trusted_node_v2`; source resolves values with `await ctx.secrets.get(name)` and uses `ctx.utils.http` for controlled public HTTPS. Create/rotate values through hidden TTY or `openxiangda secret ... --value-stdin --change <change> --profile <name>`. Never put values in arguments, files, manifests, state, plans, logs, errors, or chat. Secret bindings require `backend_release_v2` and whole-app `atomic_staged_children_v2`; a missing capability is fail-closed and never uses the legacy source PATCH. For whole-app activation use exact-scope `resource publish <type> --only <code> --stage-only`, then pass the returned verified `stagedResource` to `release app-finalize`; an active Backend Release is never labeled staged.
|
|
107
105
|
|
|
108
|
-
The lease is app-level promotion ownership, while worktree ownership prevents two Codex tasks from editing
|
|
106
|
+
The lease is app-level promotion ownership, while worktree ownership prevents two Codex tasks from editing through the same source directory. Different worktrees may keep developing and validating; only the clean synchronized main checkout publishes. Use full resource/runtime publish only when the approved bundle intentionally covers the whole dependency closure.
|
|
109
107
|
|
|
110
108
|
## Always
|
|
111
109
|
|
|
@@ -24,9 +24,9 @@ openxiangda sdd context --change <change> --changed --json
|
|
|
24
24
|
|
|
25
25
|
Run `openxiangda update check --json` once per substantial task, on a suspected mismatch, or when the cached check is older than one day. If an update is installed, refresh skills once with `openxiangda skill install --force`.
|
|
26
26
|
|
|
27
|
-
Every write/release command must include `--profile <name>`.
|
|
27
|
+
Every write/release command must include `--profile <name>`. Parallel tasks develop in isolated Git worktrees/branches, but do not publish there. Merge approved commits into the authoritative remote default branch, create one `sdd bundle` for the intended changes, commit/push it, and publish once from a clean local `main`/`master` that exactly equals the remote tip.
|
|
28
28
|
|
|
29
|
-
SDD is
|
|
29
|
+
SDD is streamlined by default: unfinished checklist/evidence/spec prose is a warning, while approval, exact structured scope, actual argv, mainline identity, child CAS, lease, and atomic activation remain hard gates. Set `strictDocumentation: true` only when prose completion must intentionally block a workspace.
|
|
30
30
|
|
|
31
31
|
First-class direct configuration writes (form/page/menu/role/permission/workflow/connector/notification/auth/route/public-access/scope/data-view/storage/settings) also require `--change <id>` or a complete stored session created by `openxiangda release begin --change <id>`. Missing context fails locally with `PUBLISH_CONTEXT_REQUIRED` before any HTTP write. Dry-run, schema planning, validation, query/invoke/export/submit/execution/upload, and other data-plane actions do not acquire a lease. Direct Workflow publish/unpublish/delete additionally requires the frozen `--expected-revision`; never fetch the latest revision merely to replay an older payload.
|
|
32
32
|
|
|
@@ -87,19 +87,16 @@ Do not call `lowcode-workspace publish-all`, `pnpm publish:all`, `pnpm publish:o
|
|
|
87
87
|
Forms, Backend resources, and frontend Runtime are staged children of one Root App release:
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
|
-
openxiangda
|
|
91
|
-
|
|
92
|
-
openxiangda
|
|
93
|
-
openxiangda
|
|
94
|
-
|
|
95
|
-
openxiangda resource publish function,automation --only function:<code>,automation:<code> --stage-only --change <change> --profile <name>
|
|
96
|
-
openxiangda release app-finalize --staged-resources-json .openxiangda/releases/<change>/staged-resources.json --change <change> --profile <name>
|
|
97
|
-
# Merge/fast-forward the frozen release SHA to the remote default branch.
|
|
90
|
+
openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
91
|
+
# Commit and push the bundle on main/master.
|
|
92
|
+
openxiangda sdd verify <release-change> --changed --stage prepublish --profile <name>
|
|
93
|
+
openxiangda release begin --change <release-change> --profile <name>
|
|
94
|
+
# Run the canonical exact staged commands emitted by the release plan.
|
|
98
95
|
openxiangda release integration-status --profile <name>
|
|
99
96
|
openxiangda release end --profile <name>
|
|
100
97
|
```
|
|
101
98
|
|
|
102
|
-
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed `HEAD`. Before
|
|
99
|
+
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. The protected upload plan still runs before the actual build because its provider-specific `assetBaseUrl` is compiled into the bundle. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status` is already satisfied after activation and `release end` does not wait for a later merge.
|
|
103
100
|
|
|
104
101
|
`resource plan`, `resource publish`, and `runtime deploy` report coarse phases on stderr and emit a heartbeat after 15 seconds without completion. JSON stdout stays machine-readable and includes `timings`; use it to identify whether time is spent in plan, preflight, build, upload, write, or activate instead of rerunning an opaque command.
|
|
105
102
|
|
|
@@ -137,8 +134,8 @@ openxiangda workspace bind --profile dev --app-type APP_XXXX
|
|
|
137
134
|
- `RUNTIME_SOURCE_DIRTY` or `RUNTIME_SOURCE_LINEAGE_REQUIRED`: commit all build inputs in an auditable Git repository; generated `dist/` and `.openxiangda/` state do not count, and `--no-build` is not an escape hatch.
|
|
138
135
|
- `PUBLISH_LEASE_HELD`: another change owns promotion; keep developing/testing locally or wait for its release/expiry. Never bypass it with an unleased write.
|
|
139
136
|
- `PUBLISH_LEASE_LOST`: heartbeat renewal failed or the lease expired during a long phase; stop immediately, inspect the active head, re-plan, and start a new release. Never replay the remaining writes with a newly fetched revision.
|
|
140
|
-
- `RELEASE_SOURCE_DIRTY
|
|
141
|
-
- `
|
|
137
|
+
- `RELEASE_SOURCE_DIRTY` or `RELEASE_PUBLISH_REVISION_CHANGED`: commit all build inputs and restart from a stable mainline SHA.
|
|
138
|
+
- `RELEASE_SOURCE_MAINLINE_REQUIRED` or `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`: merge approved task commits and the generated bundle into the remote default branch, switch to the synchronized local main/master, then begin the release.
|
|
142
139
|
- `WORKTREE_OWNED_BY_ANOTHER_TASK`: another Codex task owns this source directory; switch to an isolated worktree. Only force-claim with an explicit reason after confirming the original task ended.
|
|
143
140
|
- Plan unexpectedly includes unrelated resources: do not publish; correct `--change`/`--only` or dependency closure.
|
|
144
141
|
- A write updates state: let the CLI use its lock/atomic merge; do not hand-edit state or run conflicting legacy clients.
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n strictDocumentation?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n governance?: {\n sdd?: {\n enabled?: boolean;\n strictHighRisk?: boolean;\n strictDocumentation?: boolean;\n path?: string;\n };\n };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";AA6CO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
|