openxiangda 1.0.144 → 1.0.146
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 +124 -24
- 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) {
|
|
@@ -4677,7 +4682,8 @@ async function runtime(args) {
|
|
|
4677
4682
|
' openxiangda runtime releases --profile <name>',
|
|
4678
4683
|
'说明:',
|
|
4679
4684
|
' - deploy 上传当前工作区 dist/ 并激活 React SPA runtime release。',
|
|
4680
|
-
' - 默认 --upload-mode auto;multipart 上传遇到 403
|
|
4685
|
+
' - 默认 --upload-mode auto;multipart 上传遇到 403 时自动切换到平台内置对象存储直传。',
|
|
4686
|
+
' - --upload-mode oss-direct 是兼容参数名,实际 provider 由平台内置存储配置决定。',
|
|
4681
4687
|
' - 如果平台服务较旧不支持分片上传,可临时使用 --upload-mode legacy-json。',
|
|
4682
4688
|
' - runtime 只发布前端产物;表单、流程、通知等资源仍走 resource publish。',
|
|
4683
4689
|
].join('\n'));
|
|
@@ -4747,8 +4753,12 @@ async function runtime(args) {
|
|
|
4747
4753
|
assetBaseUrl = plan.assetBaseUrl;
|
|
4748
4754
|
if (uploadMode === 'auto') {
|
|
4749
4755
|
effectiveUploadMode = 'oss-direct';
|
|
4756
|
+
const storageProvider = resolveRuntimeStorageProvider(
|
|
4757
|
+
plan.storageProvider,
|
|
4758
|
+
plan.provider
|
|
4759
|
+
);
|
|
4750
4760
|
printRuntimeProgress(
|
|
4751
|
-
`runtime builtin
|
|
4761
|
+
`runtime builtin storage available; using ${formatRuntimeStorageProvider(storageProvider)} direct upload traceId=${traceId}`
|
|
4752
4762
|
);
|
|
4753
4763
|
}
|
|
4754
4764
|
}
|
|
@@ -4759,7 +4769,7 @@ async function runtime(args) {
|
|
|
4759
4769
|
effectiveUploadMode = 'auto';
|
|
4760
4770
|
assetBaseUrl = gatewayAssetBaseUrl;
|
|
4761
4771
|
printRuntimeProgress(
|
|
4762
|
-
`runtime builtin
|
|
4772
|
+
`runtime builtin storage preflight unavailable; using staged upload first traceId=${traceId}`
|
|
4763
4773
|
);
|
|
4764
4774
|
}
|
|
4765
4775
|
}
|
|
@@ -4816,7 +4826,7 @@ async function runtime(args) {
|
|
|
4816
4826
|
}
|
|
4817
4827
|
effectiveUploadMode = 'oss-direct';
|
|
4818
4828
|
printRuntimeProgress(
|
|
4819
|
-
`runtime staged upload blocked by HTTP 403; fallback to
|
|
4829
|
+
`runtime staged upload blocked by HTTP 403; fallback to builtin storage direct upload traceId=${traceId}`
|
|
4820
4830
|
);
|
|
4821
4831
|
releaseFiles = await uploadRuntimeDistFilesOssDirect({
|
|
4822
4832
|
config,
|
|
@@ -4858,6 +4868,10 @@ async function runtime(args) {
|
|
|
4858
4868
|
assetBaseUrl: releaseAssetBaseUrl,
|
|
4859
4869
|
activated: !flags['no-activate'],
|
|
4860
4870
|
uploadMode: effectiveUploadMode,
|
|
4871
|
+
storageProvider:
|
|
4872
|
+
data?.storageProvider ||
|
|
4873
|
+
releaseFiles.find(file => file.storageProvider)?.storageProvider ||
|
|
4874
|
+
null,
|
|
4861
4875
|
traceId,
|
|
4862
4876
|
};
|
|
4863
4877
|
if (flags.json) return writeJson(result);
|
|
@@ -5014,6 +5028,10 @@ async function uploadRuntimeDistFilesOssDirect(options) {
|
|
|
5014
5028
|
);
|
|
5015
5029
|
const uploadFiles = Array.isArray(plan?.files) ? plan.files : [];
|
|
5016
5030
|
const uploadByPath = new Map(uploadFiles.map(file => [file.path, file]));
|
|
5031
|
+
const planStorageProvider = resolveRuntimeStorageProvider(
|
|
5032
|
+
plan?.storageProvider,
|
|
5033
|
+
plan?.provider
|
|
5034
|
+
);
|
|
5017
5035
|
let completed = 0;
|
|
5018
5036
|
const results = await runWithConcurrency(
|
|
5019
5037
|
files,
|
|
@@ -5021,14 +5039,19 @@ async function uploadRuntimeDistFilesOssDirect(options) {
|
|
|
5021
5039
|
async file => {
|
|
5022
5040
|
const uploadInfo = uploadByPath.get(file.path);
|
|
5023
5041
|
if (!uploadInfo?.uploadUrl) {
|
|
5024
|
-
fail(`runtime
|
|
5042
|
+
fail(`runtime 内置对象存储上传计划缺少文件: ${file.path}`);
|
|
5025
5043
|
}
|
|
5044
|
+
const storageProvider = resolveRuntimeStorageProvider(
|
|
5045
|
+
uploadInfo.storageProvider,
|
|
5046
|
+
uploadInfo.provider,
|
|
5047
|
+
planStorageProvider
|
|
5048
|
+
);
|
|
5026
5049
|
const uploaded = await uploadRuntimeDistFileToSignedUrl(options, file, uploadInfo);
|
|
5027
5050
|
completed += 1;
|
|
5028
5051
|
printRuntimeProgress(
|
|
5029
|
-
`runtime
|
|
5052
|
+
`runtime ${formatRuntimeStorageProvider(storageProvider)} file uploaded [${completed}/${files.length}] ${file.path} ${formatBytes(file.size)} traceId=${options.traceId}`
|
|
5030
5053
|
);
|
|
5031
|
-
return uploaded;
|
|
5054
|
+
return { ...uploaded, storageProvider };
|
|
5032
5055
|
}
|
|
5033
5056
|
);
|
|
5034
5057
|
return results.map(file => ({
|
|
@@ -5036,7 +5059,7 @@ async function uploadRuntimeDistFilesOssDirect(options) {
|
|
|
5036
5059
|
size: file.size,
|
|
5037
5060
|
sha256: file.sha256,
|
|
5038
5061
|
contentType: file.contentType,
|
|
5039
|
-
storageProvider:
|
|
5062
|
+
storageProvider: resolveRuntimeStorageProvider(file.storageProvider),
|
|
5040
5063
|
objectName: file.objectName,
|
|
5041
5064
|
bucketName: file.bucketName,
|
|
5042
5065
|
}));
|
|
@@ -5056,16 +5079,16 @@ async function uploadRuntimeDistFileToSignedUrl(options, file, uploadInfo) {
|
|
|
5056
5079
|
});
|
|
5057
5080
|
} catch (error) {
|
|
5058
5081
|
if (isAbortError(error)) {
|
|
5059
|
-
throw new Error(`runtime
|
|
5082
|
+
throw new Error(`runtime 内置对象存储文件上传超时: ${file.path}, timeout=${timeoutMs}ms`);
|
|
5060
5083
|
}
|
|
5061
|
-
throw new Error(formatFetchError(error, `runtime
|
|
5084
|
+
throw new Error(formatFetchError(error, `runtime builtin storage upload ${file.path}`));
|
|
5062
5085
|
} finally {
|
|
5063
5086
|
clearTimeout(timer);
|
|
5064
5087
|
}
|
|
5065
5088
|
if (!response.ok) {
|
|
5066
5089
|
const text = await response.text().catch(() => '');
|
|
5067
5090
|
throw new Error(
|
|
5068
|
-
`runtime
|
|
5091
|
+
`runtime 内置对象存储文件上传失败: ${file.path}, HTTP ${response.status}: ${
|
|
5069
5092
|
text || response.statusText || 'request failed'
|
|
5070
5093
|
}`
|
|
5071
5094
|
);
|
|
@@ -5147,6 +5170,21 @@ function normalizeRuntimeUploadMode(value) {
|
|
|
5147
5170
|
fail(`--upload-mode 只支持 auto、staged、oss-direct 或 legacy-json,当前: ${value}`);
|
|
5148
5171
|
}
|
|
5149
5172
|
|
|
5173
|
+
function resolveRuntimeStorageProvider(...values) {
|
|
5174
|
+
for (const value of values) {
|
|
5175
|
+
const provider = String(value || '').trim().toLowerCase();
|
|
5176
|
+
if (provider) return provider;
|
|
5177
|
+
}
|
|
5178
|
+
return 'oss';
|
|
5179
|
+
}
|
|
5180
|
+
|
|
5181
|
+
function formatRuntimeStorageProvider(value) {
|
|
5182
|
+
const provider = resolveRuntimeStorageProvider(value);
|
|
5183
|
+
if (provider === 'oss') return 'OSS';
|
|
5184
|
+
if (provider === 'cos') return 'COS';
|
|
5185
|
+
return provider.toUpperCase();
|
|
5186
|
+
}
|
|
5187
|
+
|
|
5150
5188
|
function isRuntimeStagedUploadBlocked(error) {
|
|
5151
5189
|
return /^HTTP 403\b/.test(String(error?.message || ''));
|
|
5152
5190
|
}
|
|
@@ -12493,12 +12531,35 @@ function getAllWorkspaceSourceFiles() {
|
|
|
12493
12531
|
return files.sort();
|
|
12494
12532
|
}
|
|
12495
12533
|
|
|
12534
|
+
function addCodeResourceTargetFromPath(targets, file, prefix, key) {
|
|
12535
|
+
if (!file.startsWith(prefix)) return false;
|
|
12536
|
+
const rest = file.slice(prefix.length);
|
|
12537
|
+
const first = rest.split('/').filter(Boolean)[0] || '';
|
|
12538
|
+
const code = first.replace(/\.json$/i, '');
|
|
12539
|
+
if (code) targets[key].add(code);
|
|
12540
|
+
return true;
|
|
12541
|
+
}
|
|
12542
|
+
|
|
12543
|
+
function isRuntimeSourceFile(file) {
|
|
12544
|
+
return /\.(ts|tsx|js|jsx|mjs|cjs|css|scss|sass|less|html|json|svg|png|jpe?g|gif|webp|avif|ico)$/i.test(file);
|
|
12545
|
+
}
|
|
12546
|
+
|
|
12496
12547
|
function classifyWorkspaceFiles(files, runtimeMode) {
|
|
12497
12548
|
const forms = new Set();
|
|
12498
12549
|
const pages = new Set();
|
|
12550
|
+
const functions = new Set();
|
|
12551
|
+
const automations = new Set();
|
|
12552
|
+
const workflows = new Set();
|
|
12553
|
+
const jsCodeNodes = new Set();
|
|
12499
12554
|
let resources = false;
|
|
12500
12555
|
let runtime = false;
|
|
12501
12556
|
const other = [];
|
|
12557
|
+
const codeResourceTargets = {
|
|
12558
|
+
functions,
|
|
12559
|
+
automations,
|
|
12560
|
+
workflows,
|
|
12561
|
+
jsCodeNodes,
|
|
12562
|
+
};
|
|
12502
12563
|
|
|
12503
12564
|
for (const file of files) {
|
|
12504
12565
|
const formMatch = file.match(/^src\/forms\/([^/]+)/);
|
|
@@ -12515,6 +12576,30 @@ function classifyWorkspaceFiles(files, runtimeMode) {
|
|
|
12515
12576
|
}
|
|
12516
12577
|
continue;
|
|
12517
12578
|
}
|
|
12579
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/functions/', 'functions')) {
|
|
12580
|
+
continue;
|
|
12581
|
+
}
|
|
12582
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/automations/', 'automations')) {
|
|
12583
|
+
continue;
|
|
12584
|
+
}
|
|
12585
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/workflows/', 'workflows')) {
|
|
12586
|
+
continue;
|
|
12587
|
+
}
|
|
12588
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/js-code-nodes/', 'jsCodeNodes')) {
|
|
12589
|
+
continue;
|
|
12590
|
+
}
|
|
12591
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/functions/', 'functions')) {
|
|
12592
|
+
continue;
|
|
12593
|
+
}
|
|
12594
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/automations/', 'automations')) {
|
|
12595
|
+
continue;
|
|
12596
|
+
}
|
|
12597
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/workflows/', 'workflows')) {
|
|
12598
|
+
continue;
|
|
12599
|
+
}
|
|
12600
|
+
if (addCodeResourceTargetFromPath(codeResourceTargets, file, 'src/resources/js-code-nodes/', 'jsCodeNodes')) {
|
|
12601
|
+
continue;
|
|
12602
|
+
}
|
|
12518
12603
|
if (file.startsWith('src/resources/')) {
|
|
12519
12604
|
resources = true;
|
|
12520
12605
|
continue;
|
|
@@ -12523,7 +12608,8 @@ function classifyWorkspaceFiles(files, runtimeMode) {
|
|
|
12523
12608
|
file === 'package.json' ||
|
|
12524
12609
|
file === 'vite.config.ts' ||
|
|
12525
12610
|
file === 'index.html' ||
|
|
12526
|
-
file.startsWith('src/')
|
|
12611
|
+
file.startsWith('src/runtime/') ||
|
|
12612
|
+
(file.startsWith('src/') && isRuntimeSourceFile(file))
|
|
12527
12613
|
) {
|
|
12528
12614
|
runtime = runtimeMode === 'react-spa' || runtime;
|
|
12529
12615
|
continue;
|
|
@@ -12534,6 +12620,10 @@ function classifyWorkspaceFiles(files, runtimeMode) {
|
|
|
12534
12620
|
return {
|
|
12535
12621
|
forms: Array.from(forms).sort(),
|
|
12536
12622
|
pages: Array.from(pages).sort(),
|
|
12623
|
+
functions: Array.from(functions).sort(),
|
|
12624
|
+
automations: Array.from(automations).sort(),
|
|
12625
|
+
workflows: Array.from(workflows).sort(),
|
|
12626
|
+
jsCodeNodes: Array.from(jsCodeNodes).sort(),
|
|
12537
12627
|
resources,
|
|
12538
12628
|
runtime,
|
|
12539
12629
|
other,
|
|
@@ -12621,11 +12711,17 @@ function scanWorkspaceReliabilityIssues(files) {
|
|
|
12621
12711
|
function buildWorkspaceReleaseCommands(targets, runtimeMode, profile) {
|
|
12622
12712
|
const profileArg = profile || '<profile>';
|
|
12623
12713
|
const commands = [];
|
|
12714
|
+
const hasResourceTargets =
|
|
12715
|
+
Boolean(targets.resources) ||
|
|
12716
|
+
(targets.functions || []).length > 0 ||
|
|
12717
|
+
(targets.automations || []).length > 0 ||
|
|
12718
|
+
(targets.workflows || []).length > 0 ||
|
|
12719
|
+
(targets.jsCodeNodes || []).length > 0;
|
|
12624
12720
|
if (runtimeMode === 'react-spa') {
|
|
12625
12721
|
for (const formCode of targets.forms) {
|
|
12626
12722
|
commands.push(`openxiangda workspace publish --profile ${profileArg} --form ${formCode}`);
|
|
12627
12723
|
}
|
|
12628
|
-
if (
|
|
12724
|
+
if (hasResourceTargets) {
|
|
12629
12725
|
commands.push(`openxiangda resource publish --profile ${profileArg}`);
|
|
12630
12726
|
}
|
|
12631
12727
|
if (targets.runtime) {
|
|
@@ -12641,10 +12737,10 @@ function buildWorkspaceReleaseCommands(targets, runtimeMode, profile) {
|
|
|
12641
12737
|
if (onlyTargets.length > 0) {
|
|
12642
12738
|
commands.push(
|
|
12643
12739
|
`openxiangda workspace publish --profile ${profileArg} --only ${onlyTargets.join(',')}${
|
|
12644
|
-
|
|
12740
|
+
hasResourceTargets ? ' --resources' : ''
|
|
12645
12741
|
}`
|
|
12646
12742
|
);
|
|
12647
|
-
} else if (
|
|
12743
|
+
} else if (hasResourceTargets) {
|
|
12648
12744
|
commands.push(`openxiangda resource publish --profile ${profileArg}`);
|
|
12649
12745
|
}
|
|
12650
12746
|
return commands;
|
|
@@ -12734,6 +12830,10 @@ function printWorkspaceReleasePlan(plan) {
|
|
|
12734
12830
|
`compatibility: apiContracts=${plan.workspace.compatibility.apiContracts}, legacyFallbacks=${plan.workspace.compatibility.legacyFallbacks}`,
|
|
12735
12831
|
`forms: ${plan.targets.forms.length ? plan.targets.forms.join(', ') : '(none)'}`,
|
|
12736
12832
|
`pages: ${plan.targets.pages.length ? plan.targets.pages.join(', ') : '(none)'}`,
|
|
12833
|
+
`functions: ${(plan.targets.functions || []).length ? plan.targets.functions.join(', ') : '(none)'}`,
|
|
12834
|
+
`automations: ${(plan.targets.automations || []).length ? plan.targets.automations.join(', ') : '(none)'}`,
|
|
12835
|
+
`workflows: ${(plan.targets.workflows || []).length ? plan.targets.workflows.join(', ') : '(none)'}`,
|
|
12836
|
+
`jsCodeNodes: ${(plan.targets.jsCodeNodes || []).length ? plan.targets.jsCodeNodes.join(', ') : '(none)'}`,
|
|
12737
12837
|
`resources: ${plan.targets.resources ? 'yes' : 'no'}`,
|
|
12738
12838
|
`runtime deploy: ${plan.targets.runtime ? 'yes' : 'no'}`,
|
|
12739
12839
|
];
|
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
|
};
|