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.
Files changed (3) hide show
  1. package/lib/cli.js +80 -13
  2. package/lib/sdd.js +53 -5
  3. 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 releasePlan = buildWorkspaceReleasePlan({
628
- changed: flags.changed !== false,
629
- since: flags.since,
630
- profile: flags.profile || '<profile>',
631
- skipSdd: true,
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: true,
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 (targets.resources) {
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
- targets.resources ? ' --resources' : ''
12707
+ hasResourceTargets ? ' --resources' : ''
12645
12708
  }`
12646
12709
  );
12647
- } else if (targets.resources) {
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 updateReleaseManifest(loaded, releasePlan) {
1120
- const release = {
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
- ? updateReleaseManifest(loaded, options.releasePlan)
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
- if (options.writeMetadata) writeJsonFile(loaded.metaFile, verifiedMeta);
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: errors.length === 0,
1284
+ passed,
1285
+ metadataWritten,
1238
1286
  targets,
1239
1287
  highRiskFiles,
1240
1288
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.144",
3
+ "version": "1.0.145",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {