openxiangda 1.0.172 → 1.0.174

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.
@@ -0,0 +1,294 @@
1
+ const crypto = require('crypto');
2
+
3
+ function uniqueSorted(values = []) {
4
+ return Array.from(
5
+ new Set(
6
+ values
7
+ .map(value => String(value || '').trim())
8
+ .filter(Boolean)
9
+ )
10
+ ).sort();
11
+ }
12
+
13
+ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
14
+ const logicalTargets = {
15
+ forms: uniqueSorted(targets.forms || []),
16
+ pages: uniqueSorted(targets.pages || []),
17
+ functions: uniqueSorted(targets.functions || []),
18
+ automations: uniqueSorted(targets.automations || []),
19
+ workflows: uniqueSorted(targets.workflows || []),
20
+ jsCodeNodes: uniqueSorted(targets.jsCodeNodes || []),
21
+ resources: Boolean(targets.resources),
22
+ runtime: Boolean(targets.runtime),
23
+ other: uniqueSorted(targets.other || []),
24
+ };
25
+ const activationTargets = {
26
+ ...logicalTargets,
27
+ pages:
28
+ runtimeMode === 'react-spa'
29
+ ? []
30
+ : logicalTargets.pages,
31
+ runtime:
32
+ runtimeMode === 'react-spa'
33
+ ? logicalTargets.runtime || logicalTargets.pages.length > 0
34
+ : logicalTargets.runtime,
35
+ };
36
+ return { logicalTargets, activationTargets };
37
+ }
38
+
39
+ function buildTargetedResourceReleaseCommands(
40
+ targets,
41
+ profileArg,
42
+ changeId
43
+ ) {
44
+ return buildTargetedResourceReleaseSteps(
45
+ targets,
46
+ profileArg,
47
+ changeId
48
+ ).map(step => step.command);
49
+ }
50
+
51
+ function commandFromArgs(args) {
52
+ return ['openxiangda', ...args]
53
+ .map(value => {
54
+ const text = String(value);
55
+ return /^[A-Za-z0-9_./,:@<>=+-]+$/.test(text)
56
+ ? text
57
+ : `'${text.replace(/'/g, `'\\''`)}'`;
58
+ })
59
+ .join(' ');
60
+ }
61
+
62
+ function createStep(id, args, options = {}) {
63
+ return {
64
+ id,
65
+ args,
66
+ command: commandFromArgs(args),
67
+ writes: options.writes !== false,
68
+ stagedKind: options.stagedKind || null,
69
+ };
70
+ }
71
+
72
+ function appendProfileAndChange(args, profileArg, changeId) {
73
+ args.push('--profile', profileArg);
74
+ if (changeId) args.push('--change', changeId);
75
+ return args;
76
+ }
77
+
78
+ function buildTargetedResourceReleaseSteps(
79
+ targets,
80
+ profileArg,
81
+ changeId
82
+ ) {
83
+ const steps = [];
84
+ const functions = targets.functions || [];
85
+ const automations = targets.automations || [];
86
+ if (functions.length > 0 || automations.length > 0) {
87
+ const resourceTypes = [
88
+ ...(functions.length > 0 ? ['function'] : []),
89
+ ...(automations.length > 0 ? ['automation'] : []),
90
+ ];
91
+ const qualify = resourceTypes.length > 1;
92
+ const selectors = [
93
+ ...functions.map(code => (qualify ? `function:${code}` : code)),
94
+ ...automations.map(code => (qualify ? `automation:${code}` : code)),
95
+ ];
96
+ steps.push(
97
+ createStep(
98
+ 'backend-stage',
99
+ appendProfileAndChange(
100
+ [
101
+ 'resource',
102
+ 'publish',
103
+ resourceTypes.join(','),
104
+ '--only',
105
+ selectors.join(','),
106
+ '--stage-only',
107
+ ],
108
+ profileArg,
109
+ changeId
110
+ ),
111
+ { stagedKind: 'BackendRelease' }
112
+ )
113
+ );
114
+ }
115
+ const workflows = targets.workflows || [];
116
+ if (workflows.length > 0) {
117
+ steps.push(
118
+ createStep(
119
+ 'workflow-publish',
120
+ appendProfileAndChange(
121
+ [
122
+ 'resource',
123
+ 'publish',
124
+ 'workflow',
125
+ '--only',
126
+ workflows.join(','),
127
+ ],
128
+ profileArg,
129
+ changeId
130
+ )
131
+ )
132
+ );
133
+ }
134
+ return steps;
135
+ }
136
+
137
+ function buildWorkspaceReleaseCommands(
138
+ targetsInput,
139
+ runtimeMode,
140
+ profile,
141
+ changeId
142
+ ) {
143
+ return buildWorkspaceReleaseSteps(
144
+ targetsInput,
145
+ runtimeMode,
146
+ profile,
147
+ changeId
148
+ ).map(step => step.command);
149
+ }
150
+
151
+ function buildWorkspaceReleaseSteps(
152
+ targetsInput,
153
+ runtimeMode,
154
+ profile,
155
+ changeId
156
+ ) {
157
+ const profileArg = profile || '<profile>';
158
+ const { activationTargets: targets } = normalizeReleaseTargets(
159
+ targetsInput,
160
+ runtimeMode
161
+ );
162
+ const steps = [];
163
+ const hasResourceTargets =
164
+ Boolean(targets.resources) ||
165
+ targets.functions.length > 0 ||
166
+ targets.automations.length > 0 ||
167
+ targets.workflows.length > 0 ||
168
+ targets.jsCodeNodes.length > 0;
169
+ if (runtimeMode === 'react-spa') {
170
+ if (targets.forms.length > 0) {
171
+ steps.push(
172
+ createStep(
173
+ 'form-stage',
174
+ appendProfileAndChange(
175
+ [
176
+ 'resource',
177
+ 'publish',
178
+ 'form-setting',
179
+ '--only',
180
+ targets.forms.join(','),
181
+ ],
182
+ profileArg,
183
+ changeId
184
+ ),
185
+ { stagedKind: 'FormRelease' }
186
+ )
187
+ );
188
+ }
189
+ if (hasResourceTargets) {
190
+ steps.push(
191
+ ...buildTargetedResourceReleaseSteps(
192
+ targets,
193
+ profileArg,
194
+ changeId
195
+ )
196
+ );
197
+ }
198
+ if (targets.runtime) {
199
+ steps.push(
200
+ createStep(
201
+ 'runtime-stage',
202
+ appendProfileAndChange(
203
+ ['runtime', 'deploy', '--no-activate'],
204
+ profileArg,
205
+ changeId
206
+ ),
207
+ { stagedKind: 'RuntimeRelease' }
208
+ )
209
+ );
210
+ }
211
+ if (
212
+ targets.forms.length > 0 ||
213
+ targets.functions.length > 0 ||
214
+ targets.automations.length > 0 ||
215
+ targets.runtime
216
+ ) {
217
+ steps.push(
218
+ createStep(
219
+ 'app-finalize',
220
+ appendProfileAndChange(
221
+ [
222
+ 'release',
223
+ 'app-finalize',
224
+ '--staged-resources-json',
225
+ `.openxiangda/releases/${changeId || 'change'}/staged-resources.json`,
226
+ ],
227
+ profileArg,
228
+ changeId
229
+ )
230
+ )
231
+ );
232
+ }
233
+ return steps;
234
+ }
235
+
236
+ const onlyTargets = [
237
+ ...targets.forms.map(code => `forms/${code}`),
238
+ ...targets.pages.map(code => `pages/${code}`),
239
+ ];
240
+ if (onlyTargets.length > 0) {
241
+ steps.push(
242
+ createStep(
243
+ 'workspace-publish',
244
+ appendProfileAndChange(
245
+ [
246
+ 'workspace',
247
+ 'publish',
248
+ '--only',
249
+ onlyTargets.join(','),
250
+ '--skip-resources',
251
+ ],
252
+ profileArg,
253
+ changeId
254
+ )
255
+ )
256
+ );
257
+ }
258
+ if (hasResourceTargets) {
259
+ steps.push(
260
+ ...buildTargetedResourceReleaseSteps(targets, profileArg, changeId)
261
+ );
262
+ }
263
+ return steps;
264
+ }
265
+
266
+ function releasePlanHash(value) {
267
+ return crypto
268
+ .createHash('sha256')
269
+ .update(canonicalJson(value))
270
+ .digest('hex');
271
+ }
272
+
273
+ function canonicalJson(value) {
274
+ if (Array.isArray(value)) {
275
+ return `[${value.map(item => canonicalJson(item)).join(',')}]`;
276
+ }
277
+ if (value && typeof value === 'object') {
278
+ return `{${Object.keys(value)
279
+ .sort()
280
+ .map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
281
+ .join(',')}}`;
282
+ }
283
+ return JSON.stringify(value);
284
+ }
285
+
286
+ module.exports = {
287
+ buildTargetedResourceReleaseCommands,
288
+ buildTargetedResourceReleaseSteps,
289
+ buildWorkspaceReleaseCommands,
290
+ buildWorkspaceReleaseSteps,
291
+ commandFromArgs,
292
+ normalizeReleaseTargets,
293
+ releasePlanHash,
294
+ };
@@ -2,6 +2,7 @@ const path = require('path');
2
2
  const { spawnSync } = require('child_process');
3
3
 
4
4
  const { readGitSourceBase } = require('./change-baseline');
5
+ const { identitiesIntersect } = require('./git-remote-identity');
5
6
 
6
7
  const MIN_ROLLBACK_REASON_LENGTH = 8;
7
8
  const PURE_RUNTIME_OUTPUT_PREFIXES = Object.freeze([
@@ -58,7 +59,13 @@ function prepareRuntimeDeployLineage(options = {}) {
58
59
 
59
60
  const legacyBootstrap = Boolean(parentReleaseId && !parentSourceRevision);
60
61
  if (parentSourceRevision) {
61
- const sameRepository = parentSourceRevision.repo === sourceRevision.repo;
62
+ const sameRepository = identitiesIntersect(
63
+ [
64
+ parentSourceRevision.repo,
65
+ ...(parentSourceRevision.repoAliases || []),
66
+ ],
67
+ [sourceRevision.repo, ...(sourceRevision.repoAliases || [])]
68
+ );
62
69
  const descendsFromActive =
63
70
  sameRepository &&
64
71
  isGitAncestor(cwd, parentSourceRevision.baseCommit, sourceRevision.baseCommit);
@@ -73,7 +80,13 @@ function prepareRuntimeDeployLineage(options = {}) {
73
80
  );
74
81
  }
75
82
  return stripUndefinedValues({
76
- sourceRevision,
83
+ sourceRevision: sameRepository
84
+ ? {
85
+ ...sourceRevision,
86
+ repo: parentSourceRevision.repo,
87
+ repositoryId: parentSourceRevision.repo,
88
+ }
89
+ : sourceRevision,
77
90
  parentReleaseId,
78
91
  parentSourceRevision,
79
92
  lineageBypassReason: rollback.enabled ? rollback.reason : undefined,
@@ -246,7 +259,14 @@ function normalizeRuntimeSourceRevision(value, options = {}) {
246
259
  '线上 Runtime source revision 缺少有效的 repo/baseCommit/treeHash'
247
260
  );
248
261
  }
249
- return { repo, baseCommit, treeHash };
262
+ const repoAliases = Array.from(
263
+ new Set(
264
+ [repo, ...(Array.isArray(input.repoAliases) ? input.repoAliases : [])]
265
+ .map(item => String(item || '').trim().toLowerCase())
266
+ .filter(item => /^sha256:[0-9a-f]{64}$/.test(item))
267
+ )
268
+ );
269
+ return { repo, repoAliases, baseCommit, treeHash };
250
270
  }
251
271
 
252
272
  function normalizeAppSnapshot(value) {