openxiangda 1.0.234 → 1.0.236

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.
@@ -232,6 +232,11 @@ function createDeliveryV2Executor(dependencies) {
232
232
  activeRun,
233
233
  input.targets
234
234
  );
235
+ restoreDeliveryCheckpointFiles(
236
+ input.executionRoot,
237
+ activeRun,
238
+ executionTarget
239
+ );
235
240
  activeRun = await checkpoint(
236
241
  input.config,
237
242
  executionTarget,
@@ -297,7 +302,11 @@ function createDeliveryV2Executor(dependencies) {
297
302
  completed: index + 1,
298
303
  total: steps.length,
299
304
  },
300
- result: compactStepResult(step.id, result),
305
+ result: compactStepResult(step.id, result, {
306
+ executionRoot: input.executionRoot,
307
+ run: activeRun,
308
+ target: executionTarget,
309
+ }),
301
310
  }
302
311
  );
303
312
  }
@@ -800,7 +809,7 @@ function packageSummary(manifest, previousManifest) {
800
809
  };
801
810
  }
802
811
 
803
- function compactStepResult(stepId, result) {
812
+ function compactStepResult(stepId, result, context = {}) {
804
813
  if (!result || typeof result !== 'object') return { ok: true };
805
814
  if (stepId === 'app-finalize') {
806
815
  return {
@@ -811,10 +820,212 @@ function compactStepResult(stepId, result) {
811
820
  null,
812
821
  };
813
822
  }
814
- return {
823
+ const compact = {
815
824
  ok: true,
816
825
  id: result.id || result.release?.id || null,
817
826
  };
827
+ const stagedResources = normalizeCheckpointStagedResources(
828
+ Array.isArray(result.stagedResources)
829
+ ? result.stagedResources
830
+ : result.stagedResource
831
+ ? [result.stagedResource]
832
+ : []
833
+ );
834
+ if (stagedResources.length > 0) {
835
+ compact.stagedResources = stagedResources;
836
+ }
837
+ const satisfiedSelectors = uniqueStrings(result.satisfiedSelectors);
838
+ if (satisfiedSelectors.length > 0) {
839
+ compact.satisfiedSelectors = satisfiedSelectors;
840
+ }
841
+ if (stepId === 'backend-stage') {
842
+ const bindingContracts = readDeliverySupportFile(
843
+ context.executionRoot,
844
+ context.run,
845
+ context.target,
846
+ 'backend-binding-contracts.json'
847
+ );
848
+ if (bindingContracts) {
849
+ compact.backendBindingContracts = bindingContracts;
850
+ }
851
+ }
852
+ return compact;
853
+ }
854
+
855
+ function restoreDeliveryCheckpointFiles(executionRoot, run, target) {
856
+ const changeId = String(run?.control?.changeId || '').trim();
857
+ if (!changeId) return { stagedResourceCount: 0, restored: false };
858
+ const checkpointResults = Object.values(run?.result || {});
859
+ const staged = new Map();
860
+ const satisfiedSelectors = [];
861
+ let backendBindingContracts = null;
862
+ for (const result of checkpointResults) {
863
+ for (const resource of normalizeCheckpointStagedResources(
864
+ result?.stagedResources || []
865
+ )) {
866
+ staged.set(checkpointStagedResourceKey(resource), resource);
867
+ }
868
+ satisfiedSelectors.push(...uniqueStrings(result?.satisfiedSelectors));
869
+ if (result?.backendBindingContracts) {
870
+ backendBindingContracts = cloneJson(result.backendBindingContracts);
871
+ }
872
+ }
873
+ if (staged.size === 0 && !backendBindingContracts) {
874
+ return { stagedResourceCount: 0, restored: false };
875
+ }
876
+ const releaseDir = deliveryReleaseDirectory(
877
+ executionRoot,
878
+ changeId,
879
+ target?.deploymentId
880
+ );
881
+ fs.mkdirSync(releaseDir, { recursive: true });
882
+ const resources = Array.from(staged.values()).sort((left, right) =>
883
+ checkpointStagedResourceKey(left).localeCompare(
884
+ checkpointStagedResourceKey(right)
885
+ )
886
+ );
887
+ if (resources.length > 0) {
888
+ writeJsonAtomic(
889
+ path.join(releaseDir, 'staged-resources.json'),
890
+ resources
891
+ );
892
+ writeJsonAtomic(
893
+ path.join(releaseDir, 'staged-resources.context.json'),
894
+ {
895
+ contractVersion: 'staged_resources_context_v1',
896
+ appType: target.appType,
897
+ profile: target.profileName,
898
+ changeId,
899
+ deploymentId: target.deploymentId || null,
900
+ baselineId: run.control?.baselineId || null,
901
+ clientSessionId: run.control?.clientSessionId || null,
902
+ initializedAt: new Date().toISOString(),
903
+ restoredFromDeliveryRunId: run.id,
904
+ ...(uniqueStrings(satisfiedSelectors).length > 0
905
+ ? { satisfiedSelectors: uniqueStrings(satisfiedSelectors) }
906
+ : {}),
907
+ }
908
+ );
909
+ }
910
+ if (backendBindingContracts) {
911
+ writeJsonAtomic(
912
+ path.join(releaseDir, 'backend-binding-contracts.json'),
913
+ backendBindingContracts
914
+ );
915
+ }
916
+ return {
917
+ stagedResourceCount: resources.length,
918
+ restored: true,
919
+ backendBindingContracts: Boolean(backendBindingContracts),
920
+ };
921
+ }
922
+
923
+ function readDeliverySupportFile(
924
+ executionRoot,
925
+ run,
926
+ target,
927
+ fileName
928
+ ) {
929
+ const changeId = String(run?.control?.changeId || '').trim();
930
+ if (!changeId || !executionRoot) return null;
931
+ const file = path.join(
932
+ deliveryReleaseDirectory(
933
+ executionRoot,
934
+ changeId,
935
+ target?.deploymentId
936
+ ),
937
+ fileName
938
+ );
939
+ if (!fs.existsSync(file)) return null;
940
+ try {
941
+ return cloneJson(JSON.parse(fs.readFileSync(file, 'utf8')));
942
+ } catch {
943
+ throw deliveryError(
944
+ 'DELIVERY_CHECKPOINT_FILE_INVALID',
945
+ `${fileName} 不是合法 JSON,不能创建可恢复检查点`,
946
+ false
947
+ );
948
+ }
949
+ }
950
+
951
+ function deliveryReleaseDirectory(executionRoot, changeId, deploymentId) {
952
+ const normalizedChangeId = String(changeId || '').trim();
953
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(normalizedChangeId)) {
954
+ throw deliveryError(
955
+ 'DELIVERY_CONTROL_INVALID',
956
+ 'ReleaseRun changeId 不能用于恢复暂存资源',
957
+ false
958
+ );
959
+ }
960
+ const base = path.join(
961
+ path.resolve(executionRoot),
962
+ '.openxiangda',
963
+ 'releases',
964
+ normalizedChangeId
965
+ );
966
+ return deploymentId ? path.join(base, String(deploymentId)) : base;
967
+ }
968
+
969
+ function normalizeCheckpointStagedResources(values) {
970
+ if (!Array.isArray(values)) return [];
971
+ const supportedKinds = new Set([
972
+ 'RuntimeRelease',
973
+ 'PageRelease',
974
+ 'BackendRelease',
975
+ 'WorkflowRelease',
976
+ 'FormRelease',
977
+ ]);
978
+ return values
979
+ .filter(value => value && typeof value === 'object' && !Array.isArray(value))
980
+ .filter(value => supportedKinds.has(String(value.kind || '').trim()))
981
+ .filter(value => {
982
+ const releaseId = String(
983
+ value.identity?.releaseId || value.releaseId || ''
984
+ ).trim();
985
+ const hash = String(value.hash || value.resourceHash || '')
986
+ .trim()
987
+ .toLowerCase();
988
+ const formUuid = String(
989
+ value.identity?.formUuid || value.formUuid || ''
990
+ ).trim();
991
+ return (
992
+ Boolean(releaseId) &&
993
+ /^[a-f0-9]{64}$/.test(hash) &&
994
+ (value.kind !== 'FormRelease' || Boolean(formUuid))
995
+ );
996
+ })
997
+ .map(cloneJson);
998
+ }
999
+
1000
+ function checkpointStagedResourceKey(resource) {
1001
+ if (resource.kind === 'FormRelease') {
1002
+ return `FormRelease:${
1003
+ resource.identity?.formUuid || resource.formUuid
1004
+ }`;
1005
+ }
1006
+ return String(resource.kind);
1007
+ }
1008
+
1009
+ function uniqueStrings(values) {
1010
+ return Array.from(
1011
+ new Set(
1012
+ (Array.isArray(values) ? values : [])
1013
+ .map(value => String(value || '').trim())
1014
+ .filter(Boolean)
1015
+ )
1016
+ ).sort();
1017
+ }
1018
+
1019
+ function writeJsonAtomic(file, value) {
1020
+ const temporary = `${file}.${process.pid}.tmp`;
1021
+ fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, {
1022
+ mode: 0o600,
1023
+ });
1024
+ fs.renameSync(temporary, file);
1025
+ }
1026
+
1027
+ function cloneJson(value) {
1028
+ return JSON.parse(JSON.stringify(value));
818
1029
  }
819
1030
 
820
1031
  function structuredFailure(error, runId, stage, component) {
@@ -949,7 +1160,9 @@ function assertDependencies(value) {
949
1160
 
950
1161
  module.exports = {
951
1162
  CONFIG_TARGET_BY_RESOURCE_TYPE,
1163
+ compactStepResult,
952
1164
  createDeliveryV2Executor,
953
1165
  releaseTargets,
1166
+ restoreDeliveryCheckpointFiles,
954
1167
  structuredFailure,
955
1168
  };
@@ -13,6 +13,7 @@ const ROOT_SOURCE_FILES = new Set([
13
13
  'package.json',
14
14
  'package-lock.json',
15
15
  'pnpm-lock.yaml',
16
+ 'pnpm-workspace.yaml',
16
17
  'yarn.lock',
17
18
  'postcss.config.cjs',
18
19
  'postcss.config.js',
@@ -111,10 +112,11 @@ function shouldIncludeSourceFile(relativePath) {
111
112
  const segments = normalized.split('/');
112
113
  if (segments.some(segment => IGNORED_SEGMENTS.has(segment))) return false;
113
114
  if (ROOT_SOURCE_FILES.has(normalized)) return true;
115
+ if (/^tsconfig(?:\.[^/]+)?\.json$/.test(normalized)) return true;
114
116
  return (
115
117
  normalized.startsWith('src/') ||
116
118
  normalized.startsWith('public/') ||
117
- normalized === 'scripts/build-js-code.mjs'
119
+ normalized.startsWith('scripts/')
118
120
  );
119
121
  }
120
122
 
@@ -251,11 +251,14 @@ function buildTargetedResourceReleaseSteps(
251
251
  function buildDirectConfigurationReleaseSteps(
252
252
  targets,
253
253
  profileArg,
254
- changeId
254
+ changeId,
255
+ options = {}
255
256
  ) {
257
+ const excludedTypes = new Set(options.excludeTypes || []);
256
258
  return Object.entries(targets.resourceSelectors || {})
257
259
  .filter(
258
260
  ([type, selectors]) =>
261
+ !excludedTypes.has(type) &&
259
262
  directResourceTypeForKey(type) &&
260
263
  uniqueSorted(selectors || []).length > 0
261
264
  )
@@ -318,11 +321,24 @@ function buildWorkspaceReleaseSteps(
318
321
  Object.values(targets.resourceSelectors || {}).some(
319
322
  selectors => (selectors || []).length > 0
320
323
  );
324
+ const formPermissionGroups = uniqueSorted(
325
+ targets.resourceSelectors?.formPermissionGroups || []
326
+ );
327
+ const stagesFormRelease =
328
+ runtimeMode === 'react-spa' &&
329
+ (targets.forms.length > 0 || formPermissionGroups.length > 0);
321
330
  const directConfigurationSteps =
322
331
  buildDirectConfigurationReleaseSteps(
323
332
  targets,
324
333
  profileArg,
325
- changeId
334
+ changeId,
335
+ {
336
+ // A FormRelease is a single immutable snapshot. Publishing form
337
+ // settings and permission groups as sibling steps from the same live
338
+ // parent lets the second descriptor replace the first and discard its
339
+ // schema. Stage both resource types in one bundle instead.
340
+ excludeTypes: stagesFormRelease ? ['formPermissionGroups'] : [],
341
+ }
326
342
  );
327
343
  if (runtimeMode === 'react-spa') {
328
344
  if (targets.forms.length > 0) {
@@ -336,6 +352,23 @@ function buildWorkspaceReleaseSteps(
336
352
  )
337
353
  )
338
354
  );
355
+ }
356
+ if (stagesFormRelease) {
357
+ const resourceTypes = [
358
+ ...(targets.forms.length > 0 ? ['form-setting'] : []),
359
+ ...(formPermissionGroups.length > 0
360
+ ? ['form-permission-group']
361
+ : []),
362
+ ];
363
+ const qualify = resourceTypes.length > 1;
364
+ const selectors = [
365
+ ...targets.forms.map(code =>
366
+ qualify ? `form-setting:${code}` : code
367
+ ),
368
+ ...formPermissionGroups.map(code =>
369
+ qualify ? `form-permission-group:${code}` : code
370
+ ),
371
+ ];
339
372
  steps.push(
340
373
  createStep(
341
374
  'form-stage',
@@ -343,9 +376,9 @@ function buildWorkspaceReleaseSteps(
343
376
  [
344
377
  'resource',
345
378
  'publish',
346
- 'form-setting',
379
+ resourceTypes.join(','),
347
380
  '--only',
348
- targets.forms.join(','),
381
+ selectors.join(','),
349
382
  ],
350
383
  profileArg,
351
384
  changeId
@@ -378,7 +411,7 @@ function buildWorkspaceReleaseSteps(
378
411
  );
379
412
  }
380
413
  if (
381
- targets.forms.length > 0 ||
414
+ stagesFormRelease ||
382
415
  targets.functions.length > 0 ||
383
416
  targets.automations.length > 0 ||
384
417
  targets.workflows.length > 0 ||
@@ -386,7 +419,7 @@ function buildWorkspaceReleaseSteps(
386
419
  directConfigurationSteps.length > 0
387
420
  ) {
388
421
  const hasStagedChildren =
389
- targets.forms.length > 0 ||
422
+ stagesFormRelease ||
390
423
  targets.functions.length > 0 ||
391
424
  targets.automations.length > 0 ||
392
425
  targets.workflows.length > 0 ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.234",
3
+ "version": "1.0.236",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {