openxiangda 1.0.144 → 1.0.145
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/lib/cli.js +80 -13
- package/lib/sdd.js +53 -5
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -542,7 +542,7 @@ async function sdd(args) {
|
|
|
542
542
|
'说明:',
|
|
543
543
|
' - SDD 记录保存在 openspec/,不依赖官方 OpenSpec CLI。',
|
|
544
544
|
' - 新工作区使用 openxiangda-sdd-v2:proposal/specs/design/tasks/evidence 给人读,coverage.json/release.json 给 CLI 校验。',
|
|
545
|
-
' - 高风险发布需要 approved change 的 coverage 覆盖本次 forms/pages/resources/runtime 目标和高风险文件。',
|
|
545
|
+
' - 高风险发布需要 approved change 的 coverage 覆盖本次 forms/pages/functions/automations/workflows/JS_CODE/resources/runtime 目标和高风险文件。',
|
|
546
546
|
].join('\n'));
|
|
547
547
|
return;
|
|
548
548
|
}
|
|
@@ -624,17 +624,22 @@ async function sdd(args) {
|
|
|
624
624
|
|
|
625
625
|
if (subcommand === 'verify') {
|
|
626
626
|
const changeId = positional[0] || flags.change;
|
|
627
|
-
const
|
|
628
|
-
changed
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
627
|
+
const hasExplicitReleaseScope = Boolean(
|
|
628
|
+
flags.changed || flags['changed-only'] || flags.since || flags.all || flags['no-changed']
|
|
629
|
+
);
|
|
630
|
+
const releasePlan = hasExplicitReleaseScope
|
|
631
|
+
? buildWorkspaceReleasePlan({
|
|
632
|
+
changed: Boolean(flags.changed || flags['changed-only'] || flags.since) && flags.changed !== false,
|
|
633
|
+
since: flags.since,
|
|
634
|
+
profile: flags.profile || '<profile>',
|
|
635
|
+
skipSdd: true,
|
|
636
|
+
})
|
|
637
|
+
: null;
|
|
633
638
|
const result = verifySddChange({
|
|
634
639
|
cwd: process.cwd(),
|
|
635
640
|
changeId,
|
|
636
641
|
releasePlan,
|
|
637
|
-
writeMetadata:
|
|
642
|
+
writeMetadata: Boolean(releasePlan),
|
|
638
643
|
});
|
|
639
644
|
if (flags.json) {
|
|
640
645
|
writeJson(result);
|
|
@@ -901,7 +906,7 @@ function printDoctorReport(result) {
|
|
|
901
906
|
}
|
|
902
907
|
if (result.release) {
|
|
903
908
|
lines.push(
|
|
904
|
-
`release plan: forms=${result.release.targets.forms.length}, pages=${result.release.targets.pages.length}, resources=${result.release.targets.resources ? 'yes' : 'no'}, runtime=${result.release.targets.runtime ? 'yes' : 'no'}`
|
|
909
|
+
`release plan: forms=${result.release.targets.forms.length}, pages=${result.release.targets.pages.length}, functions=${(result.release.targets.functions || []).length}, resources=${result.release.targets.resources ? 'yes' : 'no'}, runtime=${result.release.targets.runtime ? 'yes' : 'no'}`
|
|
905
910
|
);
|
|
906
911
|
}
|
|
907
912
|
if (result.sdd) {
|
|
@@ -12493,12 +12498,35 @@ function getAllWorkspaceSourceFiles() {
|
|
|
12493
12498
|
return files.sort();
|
|
12494
12499
|
}
|
|
12495
12500
|
|
|
12501
|
+
function addCodeResourceTargetFromPath(targets, file, prefix, key) {
|
|
12502
|
+
if (!file.startsWith(prefix)) return false;
|
|
12503
|
+
const rest = file.slice(prefix.length);
|
|
12504
|
+
const first = rest.split('/').filter(Boolean)[0] || '';
|
|
12505
|
+
const code = first.replace(/\.json$/i, '');
|
|
12506
|
+
if (code) targets[key].add(code);
|
|
12507
|
+
return true;
|
|
12508
|
+
}
|
|
12509
|
+
|
|
12510
|
+
function isRuntimeSourceFile(file) {
|
|
12511
|
+
return /\.(ts|tsx|js|jsx|mjs|cjs|css|scss|sass|less|html|json|svg|png|jpe?g|gif|webp|avif|ico)$/i.test(file);
|
|
12512
|
+
}
|
|
12513
|
+
|
|
12496
12514
|
function classifyWorkspaceFiles(files, runtimeMode) {
|
|
12497
12515
|
const forms = new Set();
|
|
12498
12516
|
const pages = new Set();
|
|
12517
|
+
const functions = new Set();
|
|
12518
|
+
const automations = new Set();
|
|
12519
|
+
const workflows = new Set();
|
|
12520
|
+
const jsCodeNodes = new Set();
|
|
12499
12521
|
let resources = false;
|
|
12500
12522
|
let runtime = false;
|
|
12501
12523
|
const other = [];
|
|
12524
|
+
const codeResourceTargets = {
|
|
12525
|
+
functions,
|
|
12526
|
+
automations,
|
|
12527
|
+
workflows,
|
|
12528
|
+
jsCodeNodes,
|
|
12529
|
+
};
|
|
12502
12530
|
|
|
12503
12531
|
for (const file of files) {
|
|
12504
12532
|
const formMatch = file.match(/^src\/forms\/([^/]+)/);
|
|
@@ -12515,6 +12543,30 @@ function classifyWorkspaceFiles(files, runtimeMode) {
|
|
|
12515
12543
|
}
|
|
12516
12544
|
continue;
|
|
12517
12545
|
}
|
|
12546
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/functions/', 'functions')) {
|
|
12547
|
+
continue;
|
|
12548
|
+
}
|
|
12549
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/automations/', 'automations')) {
|
|
12550
|
+
continue;
|
|
12551
|
+
}
|
|
12552
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/workflows/', 'workflows')) {
|
|
12553
|
+
continue;
|
|
12554
|
+
}
|
|
12555
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/js-code-nodes/', 'jsCodeNodes')) {
|
|
12556
|
+
continue;
|
|
12557
|
+
}
|
|
12558
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/functions/', 'functions')) {
|
|
12559
|
+
continue;
|
|
12560
|
+
}
|
|
12561
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/automations/', 'automations')) {
|
|
12562
|
+
continue;
|
|
12563
|
+
}
|
|
12564
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/workflows/', 'workflows')) {
|
|
12565
|
+
continue;
|
|
12566
|
+
}
|
|
12567
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/js-code-nodes/', 'jsCodeNodes')) {
|
|
12568
|
+
continue;
|
|
12569
|
+
}
|
|
12518
12570
|
if (file.startsWith('src/resources/')) {
|
|
12519
12571
|
resources = true;
|
|
12520
12572
|
continue;
|
|
@@ -12523,7 +12575,8 @@ function classifyWorkspaceFiles(files, runtimeMode) {
|
|
|
12523
12575
|
file === 'package.json' ||
|
|
12524
12576
|
file === 'vite.config.ts' ||
|
|
12525
12577
|
file === 'index.html' ||
|
|
12526
|
-
file.startsWith('src/')
|
|
12578
|
+
file.startsWith('src/runtime/') ||
|
|
12579
|
+
(file.startsWith('src/') && isRuntimeSourceFile(file))
|
|
12527
12580
|
) {
|
|
12528
12581
|
runtime = runtimeMode === 'react-spa' || runtime;
|
|
12529
12582
|
continue;
|
|
@@ -12534,6 +12587,10 @@ function classifyWorkspaceFiles(files, runtimeMode) {
|
|
|
12534
12587
|
return {
|
|
12535
12588
|
forms: Array.from(forms).sort(),
|
|
12536
12589
|
pages: Array.from(pages).sort(),
|
|
12590
|
+
functions: Array.from(functions).sort(),
|
|
12591
|
+
automations: Array.from(automations).sort(),
|
|
12592
|
+
workflows: Array.from(workflows).sort(),
|
|
12593
|
+
jsCodeNodes: Array.from(jsCodeNodes).sort(),
|
|
12537
12594
|
resources,
|
|
12538
12595
|
runtime,
|
|
12539
12596
|
other,
|
|
@@ -12621,11 +12678,17 @@ function scanWorkspaceReliabilityIssues(files) {
|
|
|
12621
12678
|
function buildWorkspaceReleaseCommands(targets, runtimeMode, profile) {
|
|
12622
12679
|
const profileArg = profile || '<profile>';
|
|
12623
12680
|
const commands = [];
|
|
12681
|
+
const hasResourceTargets =
|
|
12682
|
+
Boolean(targets.resources) ||
|
|
12683
|
+
(targets.functions || []).length > 0 ||
|
|
12684
|
+
(targets.automations || []).length > 0 ||
|
|
12685
|
+
(targets.workflows || []).length > 0 ||
|
|
12686
|
+
(targets.jsCodeNodes || []).length > 0;
|
|
12624
12687
|
if (runtimeMode === 'react-spa') {
|
|
12625
12688
|
for (const formCode of targets.forms) {
|
|
12626
12689
|
commands.push(`openxiangda workspace publish --profile ${profileArg} --form ${formCode}`);
|
|
12627
12690
|
}
|
|
12628
|
-
if (
|
|
12691
|
+
if (hasResourceTargets) {
|
|
12629
12692
|
commands.push(`openxiangda resource publish --profile ${profileArg}`);
|
|
12630
12693
|
}
|
|
12631
12694
|
if (targets.runtime) {
|
|
@@ -12641,10 +12704,10 @@ function buildWorkspaceReleaseCommands(targets, runtimeMode, profile) {
|
|
|
12641
12704
|
if (onlyTargets.length > 0) {
|
|
12642
12705
|
commands.push(
|
|
12643
12706
|
`openxiangda workspace publish --profile ${profileArg} --only ${onlyTargets.join(',')}${
|
|
12644
|
-
|
|
12707
|
+
hasResourceTargets ? ' --resources' : ''
|
|
12645
12708
|
}`
|
|
12646
12709
|
);
|
|
12647
|
-
} else if (
|
|
12710
|
+
} else if (hasResourceTargets) {
|
|
12648
12711
|
commands.push(`openxiangda resource publish --profile ${profileArg}`);
|
|
12649
12712
|
}
|
|
12650
12713
|
return commands;
|
|
@@ -12734,6 +12797,10 @@ function printWorkspaceReleasePlan(plan) {
|
|
|
12734
12797
|
`compatibility: apiContracts=${plan.workspace.compatibility.apiContracts}, legacyFallbacks=${plan.workspace.compatibility.legacyFallbacks}`,
|
|
12735
12798
|
`forms: ${plan.targets.forms.length ? plan.targets.forms.join(', ') : '(none)'}`,
|
|
12736
12799
|
`pages: ${plan.targets.pages.length ? plan.targets.pages.join(', ') : '(none)'}`,
|
|
12800
|
+
`functions: ${(plan.targets.functions || []).length ? plan.targets.functions.join(', ') : '(none)'}`,
|
|
12801
|
+
`automations: ${(plan.targets.automations || []).length ? plan.targets.automations.join(', ') : '(none)'}`,
|
|
12802
|
+
`workflows: ${(plan.targets.workflows || []).length ? plan.targets.workflows.join(', ') : '(none)'}`,
|
|
12803
|
+
`jsCodeNodes: ${(plan.targets.jsCodeNodes || []).length ? plan.targets.jsCodeNodes.join(', ') : '(none)'}`,
|
|
12737
12804
|
`resources: ${plan.targets.resources ? 'yes' : 'no'}`,
|
|
12738
12805
|
`runtime deploy: ${plan.targets.runtime ? 'yes' : 'no'}`,
|
|
12739
12806
|
];
|
package/lib/sdd.js
CHANGED
|
@@ -10,6 +10,7 @@ const RELEASE_FILE = 'release.json';
|
|
|
10
10
|
const BYPASS_LOG_FILE = path.join('changes', 'bypass-log.md');
|
|
11
11
|
const SDD_SCHEMA_VERSION = 'openxiangda-sdd-v2';
|
|
12
12
|
const LEGACY_SDD_SCHEMA_VERSION = 'openxiangda-sdd-v1';
|
|
13
|
+
const CODE_RESOURCE_TARGETS = ['functions', 'automations', 'workflows', 'jsCodeNodes'];
|
|
13
14
|
|
|
14
15
|
const DEFAULT_SPEC_CONTENT = `# Application Specification
|
|
15
16
|
|
|
@@ -310,6 +311,17 @@ function normalizeAffected(input = {}) {
|
|
|
310
311
|
};
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
function extractCodeFromPath(file, prefix) {
|
|
315
|
+
if (!file.startsWith(prefix)) return null;
|
|
316
|
+
const rest = file.slice(prefix.length);
|
|
317
|
+
const first = rest.split('/').filter(Boolean)[0] || '';
|
|
318
|
+
return first.replace(/\.json$/i, '') || null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function coversCodeTarget(normalized, key, code) {
|
|
322
|
+
return Boolean(normalized.resources || normalized.backend || includesAll(normalized[key], [code]));
|
|
323
|
+
}
|
|
324
|
+
|
|
313
325
|
function createChangeMetadata(changeId, options = {}) {
|
|
314
326
|
return {
|
|
315
327
|
schemaVersion: SDD_SCHEMA_VERSION,
|
|
@@ -501,6 +513,10 @@ function createReleaseManifest(meta) {
|
|
|
501
513
|
targets: {
|
|
502
514
|
forms: meta.affected.forms,
|
|
503
515
|
pages: meta.affected.pages,
|
|
516
|
+
functions: meta.affected.functions,
|
|
517
|
+
automations: meta.affected.automations,
|
|
518
|
+
workflows: meta.affected.workflows,
|
|
519
|
+
jsCodeNodes: meta.affected.jsCodeNodes,
|
|
504
520
|
resources: meta.affected.resources,
|
|
505
521
|
runtime: meta.affected.runtime,
|
|
506
522
|
other: [],
|
|
@@ -613,6 +629,7 @@ function hasHighRiskTargets(targets = {}) {
|
|
|
613
629
|
return (
|
|
614
630
|
(targets.forms || []).length > 0 ||
|
|
615
631
|
(targets.pages || []).length > 0 ||
|
|
632
|
+
CODE_RESOURCE_TARGETS.some(key => (targets[key] || []).length > 0) ||
|
|
616
633
|
Boolean(targets.resources) ||
|
|
617
634
|
Boolean(targets.runtime)
|
|
618
635
|
);
|
|
@@ -647,6 +664,12 @@ function affectedCoversTargets(affected, targets = {}) {
|
|
|
647
664
|
if (!includesAll(normalized.pages, targets.pages || [])) {
|
|
648
665
|
missing.push(`pages:${(targets.pages || []).filter(item => !normalized.pages.includes(item)).join(',')}`);
|
|
649
666
|
}
|
|
667
|
+
for (const key of CODE_RESOURCE_TARGETS) {
|
|
668
|
+
const required = targets[key] || [];
|
|
669
|
+
if (required.length === 0) continue;
|
|
670
|
+
if (normalized.resources || normalized.backend || includesAll(normalized[key], required)) continue;
|
|
671
|
+
missing.push(`${key}:${required.filter(item => !normalized[key].includes(item)).join(',')}`);
|
|
672
|
+
}
|
|
650
673
|
if (targets.resources && !normalized.resources && !normalized.backend) missing.push('resources');
|
|
651
674
|
if (targets.runtime && !normalized.runtime) missing.push('runtime');
|
|
652
675
|
return {
|
|
@@ -668,6 +691,18 @@ function affectedCoversHighRiskFiles(affected, files = []) {
|
|
|
668
691
|
} else if (file.startsWith('src/pages/')) {
|
|
669
692
|
const pageCode = file.split('/')[2];
|
|
670
693
|
if (!normalized.runtime && !includesAll(normalized.pages, [pageCode])) missing.push(file);
|
|
694
|
+
} else if (file.startsWith('src/resources/functions/')) {
|
|
695
|
+
const code = extractCodeFromPath(file, 'src/resources/functions/');
|
|
696
|
+
if (code && !coversCodeTarget(normalized, 'functions', code)) missing.push(file);
|
|
697
|
+
} else if (file.startsWith('src/resources/automations/')) {
|
|
698
|
+
const code = extractCodeFromPath(file, 'src/resources/automations/');
|
|
699
|
+
if (code && !coversCodeTarget(normalized, 'automations', code)) missing.push(file);
|
|
700
|
+
} else if (file.startsWith('src/resources/workflows/')) {
|
|
701
|
+
const code = extractCodeFromPath(file, 'src/resources/workflows/');
|
|
702
|
+
if (code && !coversCodeTarget(normalized, 'workflows', code)) missing.push(file);
|
|
703
|
+
} else if (file.startsWith('src/resources/js-code-nodes/')) {
|
|
704
|
+
const code = extractCodeFromPath(file, 'src/resources/js-code-nodes/');
|
|
705
|
+
if (code && !coversCodeTarget(normalized, 'jsCodeNodes', code)) missing.push(file);
|
|
671
706
|
} else if (file.startsWith('src/resources/') && !normalized.resources) {
|
|
672
707
|
missing.push(file);
|
|
673
708
|
} else if (file.startsWith('src/functions/')) {
|
|
@@ -1116,8 +1151,8 @@ function validateDeltaSpecs(changeDir) {
|
|
|
1116
1151
|
};
|
|
1117
1152
|
}
|
|
1118
1153
|
|
|
1119
|
-
function
|
|
1120
|
-
|
|
1154
|
+
function buildUpdatedReleaseManifest(loaded, releasePlan) {
|
|
1155
|
+
return {
|
|
1121
1156
|
schemaVersion: SDD_SCHEMA_VERSION,
|
|
1122
1157
|
changeId: loaded.changeId,
|
|
1123
1158
|
...(loaded.release && !loaded.release.__invalid ? loaded.release : {}),
|
|
@@ -1126,6 +1161,10 @@ function updateReleaseManifest(loaded, releasePlan) {
|
|
|
1126
1161
|
commands: releasePlan?.commands || loaded.release?.commands || [],
|
|
1127
1162
|
verifiedAt: nowIso(),
|
|
1128
1163
|
};
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
function updateReleaseManifest(loaded, releasePlan) {
|
|
1167
|
+
const release = buildUpdatedReleaseManifest(loaded, releasePlan);
|
|
1129
1168
|
writeJsonFile(loaded.releaseFile, release);
|
|
1130
1169
|
return release;
|
|
1131
1170
|
}
|
|
@@ -1210,11 +1249,12 @@ function verifySddChange(options = {}) {
|
|
|
1210
1249
|
}
|
|
1211
1250
|
const release =
|
|
1212
1251
|
options.writeMetadata && options.releasePlan && loaded.release && !loaded.release.__invalid
|
|
1213
|
-
?
|
|
1252
|
+
? buildUpdatedReleaseManifest(loaded, options.releasePlan)
|
|
1214
1253
|
: loaded.release;
|
|
1215
1254
|
const releaseValidation = validateReleaseManifest(release, options.releasePlan, legacyMode);
|
|
1216
1255
|
errors.push(...releaseValidation.errors);
|
|
1217
1256
|
warnings.push(...releaseValidation.warnings);
|
|
1257
|
+
const passed = errors.length === 0;
|
|
1218
1258
|
const verifiedMeta = {
|
|
1219
1259
|
...loaded.meta,
|
|
1220
1260
|
release: {
|
|
@@ -1224,7 +1264,14 @@ function verifySddChange(options = {}) {
|
|
|
1224
1264
|
},
|
|
1225
1265
|
updatedAt: nowIso(),
|
|
1226
1266
|
};
|
|
1227
|
-
|
|
1267
|
+
let metadataWritten = false;
|
|
1268
|
+
if (options.writeMetadata && passed) {
|
|
1269
|
+
if (options.releasePlan && loaded.release && !loaded.release.__invalid) {
|
|
1270
|
+
updateReleaseManifest(loaded, options.releasePlan);
|
|
1271
|
+
}
|
|
1272
|
+
writeJsonFile(loaded.metaFile, verifiedMeta);
|
|
1273
|
+
metadataWritten = true;
|
|
1274
|
+
}
|
|
1228
1275
|
return {
|
|
1229
1276
|
schemaVersion: loaded.meta.schemaVersion || loaded.governance.schemaVersion || LEGACY_SDD_SCHEMA_VERSION,
|
|
1230
1277
|
change: verifiedMeta,
|
|
@@ -1234,7 +1281,8 @@ function verifySddChange(options = {}) {
|
|
|
1234
1281
|
errors,
|
|
1235
1282
|
warnings,
|
|
1236
1283
|
blockingErrors: errors,
|
|
1237
|
-
passed
|
|
1284
|
+
passed,
|
|
1285
|
+
metadataWritten,
|
|
1238
1286
|
targets,
|
|
1239
1287
|
highRiskFiles,
|
|
1240
1288
|
};
|