openxiangda 1.0.197 → 1.0.199
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/README.md +18 -6
- package/lib/application-environments.js +130 -0
- package/lib/cli.js +811 -36
- package/lib/release-plan.js +139 -14
- package/openxiangda-skills/SKILL.md +6 -6
- package/openxiangda-skills/references/openxiangda-api.md +4 -0
- package/openxiangda-skills/references/workspace-state.md +11 -3
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +6 -6
- package/package.json +4 -2
- package/templates/openxiangda-react-spa/AGENTS.md +8 -10
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +2 -2
- package/templates/sy-lowcode-app-workspace/AGENTS.md +1 -1
package/lib/release-plan.js
CHANGED
|
@@ -16,6 +16,11 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
|
|
|
16
16
|
.map(([type, values]) => [type, uniqueSorted(values || [])])
|
|
17
17
|
.filter(([, values]) => values.length > 0)
|
|
18
18
|
);
|
|
19
|
+
const resourceDeletes = Object.fromEntries(
|
|
20
|
+
Object.entries(targets.resourceDeletes || {})
|
|
21
|
+
.map(([type, values]) => [type, uniqueSorted(values || [])])
|
|
22
|
+
.filter(([, values]) => values.length > 0)
|
|
23
|
+
);
|
|
19
24
|
const logicalTargets = {
|
|
20
25
|
forms: uniqueSorted(targets.forms || []),
|
|
21
26
|
pages: uniqueSorted(targets.pages || []),
|
|
@@ -25,6 +30,7 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
|
|
|
25
30
|
jsCodeNodes: uniqueSorted(targets.jsCodeNodes || []),
|
|
26
31
|
resources: Boolean(targets.resources),
|
|
27
32
|
resourceSelectors,
|
|
33
|
+
resourceDeletes,
|
|
28
34
|
runtime: Boolean(targets.runtime),
|
|
29
35
|
other: uniqueSorted(targets.other || []),
|
|
30
36
|
};
|
|
@@ -45,11 +51,25 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
|
|
|
45
51
|
function unsupportedAtomicReleaseTargets(targets = {}) {
|
|
46
52
|
const unsupported = [];
|
|
47
53
|
if (targets.resources) unsupported.push('generic-resources');
|
|
48
|
-
for (const [type, selectors] of Object.entries(
|
|
49
|
-
|
|
54
|
+
for (const [type, selectors] of Object.entries(
|
|
55
|
+
targets.resourceSelectors || {}
|
|
56
|
+
)) {
|
|
57
|
+
if (
|
|
58
|
+
(selectors || []).length > 0 &&
|
|
59
|
+
(!DIRECT_RESOURCE_TYPE_BY_KEY[type] || type === 'unknown')
|
|
60
|
+
) {
|
|
50
61
|
unsupported.push(`${type}:${selectors.join(',')}`);
|
|
51
62
|
}
|
|
52
63
|
}
|
|
64
|
+
for (const [type, selectors] of Object.entries(
|
|
65
|
+
targets.resourceDeletes || {}
|
|
66
|
+
)) {
|
|
67
|
+
if ((selectors || []).length > 0) {
|
|
68
|
+
unsupported.push(
|
|
69
|
+
`destructive-resource-delete:${type}:${selectors.join(',')}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
53
73
|
if (
|
|
54
74
|
(targets.jsCodeNodes || []).length > 0 &&
|
|
55
75
|
(targets.functions || []).length === 0 &&
|
|
@@ -60,6 +80,40 @@ function unsupportedAtomicReleaseTargets(targets = {}) {
|
|
|
60
80
|
return unsupported;
|
|
61
81
|
}
|
|
62
82
|
|
|
83
|
+
const DIRECT_RESOURCE_TYPE_BY_KEY = Object.freeze({
|
|
84
|
+
roles: 'role',
|
|
85
|
+
connectors: 'connector',
|
|
86
|
+
notifications: 'notification',
|
|
87
|
+
menus: 'menu',
|
|
88
|
+
dataViews: 'data-view',
|
|
89
|
+
storageConfigs: 'storage',
|
|
90
|
+
authConfigs: 'auth-config',
|
|
91
|
+
routes: 'route',
|
|
92
|
+
publicAccessPolicies: 'public-access',
|
|
93
|
+
pagePermissionGroups: 'page-permission-group',
|
|
94
|
+
formPermissionGroups: 'form-permission-group',
|
|
95
|
+
scopeDimensions: 'scope-dimension',
|
|
96
|
+
scopeGrantSources: 'scope-grant-source',
|
|
97
|
+
dataScopePolicies: 'data-scope-policy',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const DIRECT_RESOURCE_RELEASE_ORDER = Object.freeze([
|
|
101
|
+
'roles',
|
|
102
|
+
'connectors',
|
|
103
|
+
'storageConfigs',
|
|
104
|
+
'authConfigs',
|
|
105
|
+
'routes',
|
|
106
|
+
'publicAccessPolicies',
|
|
107
|
+
'menus',
|
|
108
|
+
'dataViews',
|
|
109
|
+
'scopeDimensions',
|
|
110
|
+
'scopeGrantSources',
|
|
111
|
+
'dataScopePolicies',
|
|
112
|
+
'pagePermissionGroups',
|
|
113
|
+
'formPermissionGroups',
|
|
114
|
+
'notifications',
|
|
115
|
+
]);
|
|
116
|
+
|
|
63
117
|
function assertAtomicReleaseTargetsSupported(targets) {
|
|
64
118
|
const unsupported = unsupportedAtomicReleaseTargets(targets);
|
|
65
119
|
if (unsupported.length === 0) return;
|
|
@@ -128,18 +182,23 @@ function buildTargetedResourceReleaseSteps(
|
|
|
128
182
|
...functions.map(code => (qualify ? `function:${code}` : code)),
|
|
129
183
|
...automations.map(code => (qualify ? `automation:${code}` : code)),
|
|
130
184
|
];
|
|
185
|
+
const stagedFormContracts = uniqueSorted(targets.forms || []);
|
|
186
|
+
const args = [
|
|
187
|
+
'resource',
|
|
188
|
+
'publish',
|
|
189
|
+
resourceTypes.join(','),
|
|
190
|
+
'--only',
|
|
191
|
+
selectors.join(','),
|
|
192
|
+
'--stage-only',
|
|
193
|
+
...(stagedFormContracts.length > 0
|
|
194
|
+
? ['--staged-form-contracts', stagedFormContracts.join(',')]
|
|
195
|
+
: []),
|
|
196
|
+
];
|
|
131
197
|
steps.push(
|
|
132
198
|
createStep(
|
|
133
199
|
'backend-stage',
|
|
134
200
|
appendProfileAndChange(
|
|
135
|
-
|
|
136
|
-
'resource',
|
|
137
|
-
'publish',
|
|
138
|
-
resourceTypes.join(','),
|
|
139
|
-
'--only',
|
|
140
|
-
selectors.join(','),
|
|
141
|
-
'--stage-only',
|
|
142
|
-
],
|
|
201
|
+
args,
|
|
143
202
|
profileArg,
|
|
144
203
|
changeId
|
|
145
204
|
),
|
|
@@ -171,6 +230,40 @@ function buildTargetedResourceReleaseSteps(
|
|
|
171
230
|
return steps;
|
|
172
231
|
}
|
|
173
232
|
|
|
233
|
+
function buildDirectConfigurationReleaseSteps(
|
|
234
|
+
targets,
|
|
235
|
+
profileArg,
|
|
236
|
+
changeId
|
|
237
|
+
) {
|
|
238
|
+
return Object.entries(targets.resourceSelectors || {})
|
|
239
|
+
.filter(
|
|
240
|
+
([type, selectors]) =>
|
|
241
|
+
DIRECT_RESOURCE_TYPE_BY_KEY[type] &&
|
|
242
|
+
uniqueSorted(selectors || []).length > 0
|
|
243
|
+
)
|
|
244
|
+
.sort(
|
|
245
|
+
([left], [right]) =>
|
|
246
|
+
DIRECT_RESOURCE_RELEASE_ORDER.indexOf(left) -
|
|
247
|
+
DIRECT_RESOURCE_RELEASE_ORDER.indexOf(right)
|
|
248
|
+
)
|
|
249
|
+
.map(([type, selectors]) =>
|
|
250
|
+
createStep(
|
|
251
|
+
`config-${DIRECT_RESOURCE_TYPE_BY_KEY[type]}`,
|
|
252
|
+
appendProfileAndChange(
|
|
253
|
+
[
|
|
254
|
+
'resource',
|
|
255
|
+
'publish',
|
|
256
|
+
DIRECT_RESOURCE_TYPE_BY_KEY[type],
|
|
257
|
+
'--only',
|
|
258
|
+
uniqueSorted(selectors).join(','),
|
|
259
|
+
],
|
|
260
|
+
profileArg,
|
|
261
|
+
changeId
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
174
267
|
function buildWorkspaceReleaseCommands(
|
|
175
268
|
targetsInput,
|
|
176
269
|
runtimeMode,
|
|
@@ -203,9 +296,28 @@ function buildWorkspaceReleaseSteps(
|
|
|
203
296
|
targets.functions.length > 0 ||
|
|
204
297
|
targets.automations.length > 0 ||
|
|
205
298
|
targets.workflows.length > 0 ||
|
|
206
|
-
targets.jsCodeNodes.length > 0
|
|
299
|
+
targets.jsCodeNodes.length > 0 ||
|
|
300
|
+
Object.values(targets.resourceSelectors || {}).some(
|
|
301
|
+
selectors => (selectors || []).length > 0
|
|
302
|
+
);
|
|
303
|
+
const directConfigurationSteps =
|
|
304
|
+
buildDirectConfigurationReleaseSteps(
|
|
305
|
+
targets,
|
|
306
|
+
profileArg,
|
|
307
|
+
changeId
|
|
308
|
+
);
|
|
207
309
|
if (runtimeMode === 'react-spa') {
|
|
208
310
|
if (targets.forms.length > 0) {
|
|
311
|
+
steps.push(
|
|
312
|
+
createStep(
|
|
313
|
+
'form-ensure',
|
|
314
|
+
appendProfileAndChange(
|
|
315
|
+
['form', 'ensure', '--only', targets.forms.join(',')],
|
|
316
|
+
profileArg,
|
|
317
|
+
changeId
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
);
|
|
209
321
|
steps.push(
|
|
210
322
|
createStep(
|
|
211
323
|
'form-stage',
|
|
@@ -224,6 +336,7 @@ function buildWorkspaceReleaseSteps(
|
|
|
224
336
|
)
|
|
225
337
|
);
|
|
226
338
|
}
|
|
339
|
+
steps.push(...directConfigurationSteps);
|
|
227
340
|
if (hasResourceTargets) {
|
|
228
341
|
steps.push(
|
|
229
342
|
...buildTargetedResourceReleaseSteps(
|
|
@@ -251,8 +364,15 @@ function buildWorkspaceReleaseSteps(
|
|
|
251
364
|
targets.functions.length > 0 ||
|
|
252
365
|
targets.automations.length > 0 ||
|
|
253
366
|
targets.workflows.length > 0 ||
|
|
254
|
-
targets.runtime
|
|
367
|
+
targets.runtime ||
|
|
368
|
+
directConfigurationSteps.length > 0
|
|
255
369
|
) {
|
|
370
|
+
const hasStagedChildren =
|
|
371
|
+
targets.forms.length > 0 ||
|
|
372
|
+
targets.functions.length > 0 ||
|
|
373
|
+
targets.automations.length > 0 ||
|
|
374
|
+
targets.workflows.length > 0 ||
|
|
375
|
+
targets.runtime;
|
|
256
376
|
steps.push(
|
|
257
377
|
createStep(
|
|
258
378
|
'app-finalize',
|
|
@@ -260,8 +380,12 @@ function buildWorkspaceReleaseSteps(
|
|
|
260
380
|
[
|
|
261
381
|
'release',
|
|
262
382
|
'app-finalize',
|
|
263
|
-
|
|
264
|
-
|
|
383
|
+
...(hasStagedChildren
|
|
384
|
+
? [
|
|
385
|
+
'--staged-resources-json',
|
|
386
|
+
`.openxiangda/releases/${changeId || 'change'}/staged-resources.json`,
|
|
387
|
+
]
|
|
388
|
+
: []),
|
|
265
389
|
'--wait',
|
|
266
390
|
],
|
|
267
391
|
profileArg,
|
|
@@ -296,6 +420,7 @@ function buildWorkspaceReleaseSteps(
|
|
|
296
420
|
);
|
|
297
421
|
}
|
|
298
422
|
if (hasResourceTargets) {
|
|
423
|
+
steps.push(...directConfigurationSteps);
|
|
299
424
|
steps.push(
|
|
300
425
|
...buildTargetedResourceReleaseSteps(targets, profileArg, changeId)
|
|
301
426
|
);
|
|
@@ -98,17 +98,17 @@ openxiangda release publish --change <release-change> --profile <name>
|
|
|
98
98
|
Environment-managed application:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
openxiangda release
|
|
102
|
-
|
|
103
|
-
openxiangda release
|
|
104
|
-
|
|
101
|
+
openxiangda release ship --change <release-change> --profile <name>
|
|
102
|
+
# Stop here by default and validate the preproduction environment.
|
|
103
|
+
openxiangda release ship --change <release-change> --profile <name> \
|
|
104
|
+
--confirm-production
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
For a workspace registered by `openxiangda environment init` or connected to an existing set with `openxiangda environment attach`, production is never a direct publish target.
|
|
107
|
+
For a workspace registered by `openxiangda environment init` or connected to an existing set with `openxiangda environment attach`, production is never a direct publish target. Use the two-phase `release ship` fast path by default. Its first invocation creates/deploys the immutable candidate to preproduction and always returns `awaiting_production_confirmation`; it cannot promote. A second invocation with `--confirm-production` promotes the exact same candidate hash. Real human acceptance is the recommended normal practice and can be recorded with optional `--acceptance-note`, but do not invent an acceptance note and do not treat it as a universal hard platform gate when the user explicitly authorizes a low-risk or emergency promotion. The lower-level candidate/deploy/test/promote commands are recovery primitives, not the normal agent loop. Preproduction and production keep independent app/resource/data identities in `.openxiangda/state.json`; do not copy IDs between targets. `environment attach` may seed only the matching legacy app binding into preproduction and must leave production empty. Only during authorized commissioning/reclassification may `environment swap --reason "..." --confirm-production` atomically exchange the two existing roles; it preserves each app's data and Release Heads, remaps local resource IDs by appType, and does not enable production side effects by default. To change only one environment's side-effect policy, run `environment policy update <kind|id> --side-effect-policy-json <JSON|file> --reason "..." --dry-run` first, then repeat without `--dry-run`; never use swap for this. The update is revision-CAS and patch-preserving unless `--full-replace` is explicit, production needs `--confirm-production`, and `organizationWrites=explicit_capability_only` still requires `app:organization:manage`. Use `openxiangda studio` for the loopback-only developer view of bindings, drift, evidence, and safe next actions.
|
|
108
108
|
|
|
109
109
|
`resource plan` and publish dry-runs are strictly GET/HEAD-only. `READ_ONLY_AUTH_REQUIRED` means the access token expired; run `openxiangda auth refresh --profile <name>` or log in again before retrying. Never add an automatic refresh POST inside a plan.
|
|
110
110
|
|
|
111
|
-
`release publish` is the default promotion entrypoint only for legacy unmanaged workspaces. It verifies without rewriting reviewed `change.json`/`release.json`, waits for the app lease, freezes the App capture after ownership is acquired, executes deterministic exact staged steps, resumes from `.openxiangda/releases/<change>/execution.json`, atomically finalizes, verifies mainline integration, and releases the lease. Environment-managed applications use candidate/deploy/test/promote,
|
|
111
|
+
`release publish` is the default promotion entrypoint only for legacy unmanaged workspaces. It verifies without rewriting reviewed `change.json`/`release.json`, waits for the app lease, freezes the App capture after ownership is acquired, executes deterministic exact staged steps, resumes from `.openxiangda/releases/<change>/execution.json`, atomically finalizes, verifies mainline integration, and releases the lease. Environment-managed applications use the two-phase `release ship`; candidate/deploy/test/promote, `release begin`, and child commands remain recovery/diagnostic primitives.
|
|
112
112
|
|
|
113
113
|
Reviewed bundle commands may retain `<profile>` as a template. The explicit real `release publish --profile <name>` value is bound to actual child argv without rewriting tracked SDD. React SPA page codes are logical coverage targets and activate through one Runtime child; they do not require PageRelease. `release app-head` and `runtime releases` are compact by default; use `--full` only when the complete manifest is required.
|
|
114
114
|
|
|
@@ -866,6 +866,10 @@ Binds the missing `preproduction` or `production` app identity. An appType can b
|
|
|
866
866
|
|
|
867
867
|
Atomically exchanges the roles of the two existing app identities during an explicitly confirmed commissioning/reclassification operation. The request requires `confirmation=SWAP_PREPRODUCTION_AND_PRODUCTION` and a reason. Application data and Release Heads stay with their appType; local target resources are remapped by appType. Existing side-effect restrictions remain unless the request explicitly supplies replacement policies.
|
|
868
868
|
|
|
869
|
+
### POST `/environment-sets/:code/environments/:kindOrId/policy`
|
|
870
|
+
|
|
871
|
+
Updates only one environment's side-effect policy. The request requires `expectedRevision`, `reason`, and a strictly validated `sideEffectPolicy` patch; `fullReplace=true` is the only way to replace rather than merge. Production additionally requires `confirmProduction=true`. A successful transaction increments that environment revision and writes a durable audit containing actor, environment identity, reason, and secret-free before/after policies. It does not change either environment role, business data, or any App/Runtime/Backend/Page/Workflow Release Head. `externalDingTalkDepartmentRootId` accepts a string or safe integer and is stored as a string. `organizationWrites=explicit_capability_only` still requires the normal `app:organization:manage` permission.
|
|
872
|
+
|
|
869
873
|
### GET `/environment-sets/:code/status`
|
|
870
874
|
|
|
871
875
|
Returns each target's active heads, latest deployment, side-effect policy, and candidate drift state for the local Developer Center.
|
|
@@ -68,9 +68,16 @@ Environment-managed workspaces add a logical application and target-specific bin
|
|
|
68
68
|
"environmentId": "PRE_ENV_UUID",
|
|
69
69
|
"kind": "preproduction",
|
|
70
70
|
"appType": "APP_PRE",
|
|
71
|
+
"revision": 4,
|
|
71
72
|
"sideEffectPolicy": {
|
|
72
|
-
"
|
|
73
|
-
"
|
|
73
|
+
"notifications": "tester_allowlist",
|
|
74
|
+
"notificationAllowlist": [],
|
|
75
|
+
"organizationWrites": "deny",
|
|
76
|
+
"scheduledAutomations": "disabled",
|
|
77
|
+
"externalWrites": "allowlist",
|
|
78
|
+
"payments": "deny",
|
|
79
|
+
"publicIndexing": "deny",
|
|
80
|
+
"environmentBanner": true
|
|
74
81
|
},
|
|
75
82
|
"resources": {}
|
|
76
83
|
},
|
|
@@ -101,5 +108,6 @@ Environment-managed workspaces add a logical application and target-specific bin
|
|
|
101
108
|
- This protects the ID map from truncation/lost updates. Concurrent development still uses separate Git worktrees/branches; `.openxiangda/worktree-owner.json` binds one worktree to one active `CODEX_THREAD_ID` and blocks a second task at SDD context/publish time.
|
|
102
109
|
- A prod publish must not read dev IDs.
|
|
103
110
|
- Never copy target resource bindings. Use `openxiangda environment use <target>` or pass `--environment <target>` and let the CLI populate that target from its own deployment.
|
|
104
|
-
- Managed targets cannot use direct `release publish`; use
|
|
111
|
+
- Managed targets cannot use direct `release publish`; use two-phase `release ship`. The first invocation only prepares preproduction and stops; after confirming the preproduction result, a separate invocation with `--confirm-production` promotes the same candidate. Human acceptance is recommended and may be recorded with optional `--acceptance-note`.
|
|
112
|
+
- Change a single target's side-effect policy with `openxiangda environment policy update <kind|id> --side-effect-policy-json <JSON|file> --reason "..." --dry-run`, inspect the exact diff, then repeat without `--dry-run`. The write uses the server revision as CAS and updates local `revision`; it does not swap roles or alter Release Heads. Omitted fields are preserved unless `--full-replace` is explicit, and production additionally requires `--confirm-production`.
|
|
105
113
|
- Before publishing to another platform, run `openxiangda workspace bind --profile <name> --app-type <APP_XXX>`.
|
|
@@ -99,15 +99,15 @@ openxiangda release publish --change <release-change> --profile <name>
|
|
|
99
99
|
Environment-managed application:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
openxiangda release
|
|
103
|
-
|
|
104
|
-
openxiangda release
|
|
105
|
-
|
|
102
|
+
openxiangda release ship --change <release-change> --profile <name>
|
|
103
|
+
# Stop by default and validate preproduction.
|
|
104
|
+
openxiangda release ship --change <release-change> --profile <name> \
|
|
105
|
+
--confirm-production
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
Once `environment init` registers a logical application, or `environment attach` connects an existing workspace, `preproduction` and `production` are separate target bindings with separate app/resource/data IDs. Never copy IDs between them. Existing legacy resource mappings may seed only the target whose appType matches (normally preproduction); production stays empty. The
|
|
108
|
+
Once `environment init` registers a logical application, or `environment attach` connects an existing workspace, `preproduction` and `production` are separate target bindings with separate app/resource/data IDs. Never copy IDs between them. Existing legacy resource mappings may seed only the target whose appType matches (normally preproduction); production stays empty. The normal path is the two-phase `release ship`. Its first invocation creates and deploys the immutable candidate only to preproduction and stops at `awaiting_production_confirmation`. A different, later invocation with `--confirm-production` promotes the exact same candidate and fresh deployment-bound evidence. Human acceptance is recommended and an optional `--acceptance-note` records it, but it is not a universal hard platform gate for explicitly authorized low-risk or emergency releases. Direct `release publish` to either managed target fails closed. Only authorized commissioning/reclassification may run `environment swap --reason "..." --confirm-production`; the atomic operation preserves each app's data and Release Heads, remaps local resources by appType, and keeps existing side-effect restrictions unless explicit replacement policies are supplied. A single environment policy change uses `environment policy update <kind|id>` with a GET-only `--dry-run` first; it uses revision CAS, preserves omitted fields unless `--full-replace` is explicit, and never swaps roles or Release Heads. Production writes require `--confirm-production`. `organizationWrites=explicit_capability_only` only removes the environment deny and never bypasses `app:organization:manage`. `openxiangda studio` is the local loopback-only developer view and exposes only registered safe actions.
|
|
109
109
|
|
|
110
|
-
`release publish` is the normal whole-app entrypoint for legacy unmanaged workspaces: it verifies SDD without mutating reviewed files, waits for the promotion lease, freezes one App capture, stages the exact Form/Backend/Runtime children, resumes from a private execution journal, finalizes once, and releases the lease. Managed applications use
|
|
110
|
+
`release publish` is the normal whole-app entrypoint for legacy unmanaged workspaces: it verifies SDD without mutating reviewed files, waits for the promotion lease, freezes one App capture, stages the exact Form/Backend/Runtime children, resumes from a private execution journal, finalizes once, and releases the lease. Managed applications use two-phase `release ship` and its deployment-scoped journal. Individual candidate/deploy/test/promote commands are recovery/diagnostic primitives.
|
|
111
111
|
|
|
112
112
|
Reviewed bundle commands may retain `<profile>` as a template. The explicit real `release publish --profile <name>` value is bound to actual child argv without rewriting tracked SDD. React SPA page codes remain logical coverage targets and activate through the single Runtime child; they do not require PageRelease. If local lease state disappears, `release end --change <id>` reconciles a self-owned remote lease from the private execution journal and never reports inactive while a remote lease is active.
|
|
113
113
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.199",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"build:sdk": "tsup --config packages/sdk/tsup.config.ts",
|
|
65
65
|
"check": "node --check bin/openxiangda.js && node --check lib/*.js && node --check packages/sdk/src/build-source/src/cli.mjs && node --check packages/sdk/src/build-source/scripts/*.mjs && node --check packages/sdk/src/build-source/scripts/utils/*.mjs",
|
|
66
66
|
"test": "npm run test:fast",
|
|
67
|
-
"test:fast": "npm run check && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs && node scripts/release-plan-smoke.mjs && node scripts/typed-resource-plan-smoke.mjs && node scripts/integration-bundle-smoke.mjs && node scripts/content-cache-smoke.mjs && node scripts/dependency-capsule-smoke.mjs && node scripts/sdd-stages-smoke.mjs && node scripts/environment-swap-cli-smoke.mjs",
|
|
67
|
+
"test:fast": "npm run check && node scripts/http-proxy-smoke.mjs && node scripts/form-export-cli-smoke.mjs && node scripts/open-api-cli-smoke.mjs && node scripts/release-plan-smoke.mjs && node scripts/typed-resource-plan-smoke.mjs && node scripts/integration-bundle-smoke.mjs && node scripts/content-cache-smoke.mjs && node scripts/dependency-capsule-smoke.mjs && node scripts/sdd-stages-smoke.mjs && node scripts/environment-swap-cli-smoke.mjs && node scripts/environment-policy-cli-smoke.mjs",
|
|
68
68
|
"test:changed": "npm run check && node scripts/run-test-suite.mjs --changed --evidence",
|
|
69
69
|
"test:contract": "npm run check && node scripts/run-test-suite.mjs --contract --evidence",
|
|
70
70
|
"test:release": "npm run check && node scripts/run-test-suite.mjs --release --evidence",
|
|
@@ -112,7 +112,9 @@
|
|
|
112
112
|
"test:page-release-cli": "node scripts/page-release-cli-smoke.mjs",
|
|
113
113
|
"test:app-release-cli": "node scripts/app-release-cli-smoke.mjs",
|
|
114
114
|
"test:application-environments": "node scripts/application-environments-smoke.mjs",
|
|
115
|
+
"test:release-ship": "node scripts/release-ship-gate-smoke.mjs",
|
|
115
116
|
"test:environment-swap": "node scripts/environment-swap-cli-smoke.mjs",
|
|
117
|
+
"test:environment-policy": "node scripts/environment-policy-cli-smoke.mjs",
|
|
116
118
|
"test:developer-center": "node scripts/developer-center-smoke.mjs",
|
|
117
119
|
"test:form-release-cas": "node scripts/form-release-cas-smoke.mjs",
|
|
118
120
|
"test:source-dependencies": "node scripts/source-dependencies-smoke.mjs",
|
|
@@ -43,17 +43,16 @@ openxiangda sdd verify <change> --changed --stage implementation
|
|
|
43
43
|
openxiangda resource plan <type> --only <code1,code2> --profile <name>
|
|
44
44
|
openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
45
45
|
openxiangda release publish --change <release-change> --profile <name>
|
|
46
|
-
openxiangda release
|
|
47
|
-
|
|
48
|
-
openxiangda release
|
|
49
|
-
openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
|
|
46
|
+
openxiangda release ship --change <release-change> --profile <name>
|
|
47
|
+
# 确认预发结果后,另一次命令晋级同一 candidate;验收备注可选
|
|
48
|
+
openxiangda release ship --change <release-change> --profile <name> --confirm-production
|
|
50
49
|
openxiangda studio
|
|
51
50
|
openxiangda commands --json
|
|
52
51
|
```
|
|
53
52
|
|
|
54
53
|
模板已停用无范围的 `pnpm deploy` 聚合入口。日常变更必须使用 `resource plan|publish <type> --only <codes>`(单资源可用 `--code <code>`)。Form bundle、Backend Release 和 Runtime 都先暂存;CLI 会按 `--change` 自动聚合 `.openxiangda/releases/<change>/staged-resources.json`,最后只由一次 Root App finalize 原子激活。不要使用 `workspace publish --form`、单独 `runtime activate`、`pnpm publish:all`、`pnpm openxiangda:publish` 或 `lowcode-workspace publish-all`。
|
|
55
54
|
|
|
56
|
-
工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish`
|
|
55
|
+
工作区一旦通过 `environment init` 登记,或通过 `environment attach` 接入已有环境组,`release publish` 即不再是入口。日常使用两段式 `release ship`:第一次冻结不可变 candidate 并只部署到 preproduction,停止在 `awaiting_production_confirmation`;确认预发结果后,第二次命令提供 `--confirm-production`,把同一 candidate 晋级 production,不会重新构建。真实人工验收是默认建议,可用可选的 `--acceptance-note` 留痕;但用户明确授权的低风险或紧急发布不受僵硬审批门禁阻塞。两套环境的 appType、资源 ID、数据和副作用策略完全独立;旧单目标资源映射只允许迁移到 appType 相同的预发 target,正式 target 必须为空。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。单独调整某个环境的副作用策略时,先运行 `environment policy update <kind|id> ... --dry-run` 查看差异,再按 CAS revision 写入;不得借用 swap,遗漏字段默认保留,正式写入另需 `--confirm-production`。`organizationWrites=explicit_capability_only` 仍强制 `app:organization:manage`。`openxiangda studio` 提供仅本机访问的开发者页面,用于查看绑定、漂移、候选、部署、证据和下一安全动作。
|
|
57
56
|
|
|
58
57
|
`resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
|
|
59
58
|
|
|
@@ -64,11 +63,10 @@ openxiangda commands --json
|
|
|
64
63
|
openxiangda sdd bundle <release-change> --changes <change-a,change-b>
|
|
65
64
|
# 未登记环境的旧工作区:commit/push bundle 后由一个可恢复命令完成原子发布
|
|
66
65
|
openxiangda release publish --change <release-change> --profile <name>
|
|
67
|
-
#
|
|
68
|
-
openxiangda release
|
|
69
|
-
|
|
70
|
-
openxiangda release
|
|
71
|
-
openxiangda release promote --candidate <candidate-id> --environment production --confirm-production
|
|
66
|
+
# 已登记环境的工作区:第一次只准备预发
|
|
67
|
+
openxiangda release ship --change <release-change> --profile <name>
|
|
68
|
+
# 确认预发结果后,第二次晋级同一候选
|
|
69
|
+
openxiangda release ship --change <release-change> --profile <name> --confirm-production
|
|
72
70
|
```
|
|
73
71
|
|
|
74
72
|
`pnpm build-js-code` 会检查并打包 `src/js-code-nodes/<code>/index.ts`、`src/automations/<code>/index.ts`、`src/functions/<code>/index.ts`,供 JS_CODE V2、代码自动化和 App Function 资源发布使用。批量目标使用 `pnpm build-js-code --scripts functions:a,functions:b,automations:c`,也兼容重复的 `--script a --script b --source functions`。正式 `resource plan/publish` 由 CLI 内置 scoped builder 一次批量构建选中入口及其传递/shared/ambient 依赖,不再为每个资源启动工作区 `pnpm`。缓存写入 `.openxiangda/build-cache.cli-v4.json`,稳定 `source_lineage_v1` 只按 authored source/dependencies 比较,不因构建器升级制造假冲突;需要强制重建时追加 `--force`。
|
|
@@ -6,8 +6,8 @@ const lines = [
|
|
|
6
6
|
'',
|
|
7
7
|
'请使用一个显式 change 和精确资源 selector:',
|
|
8
8
|
' openxiangda resource plan <type> --only <codes> --profile <name>',
|
|
9
|
-
' 环境托管应用:openxiangda release
|
|
10
|
-
'
|
|
9
|
+
' 环境托管应用:openxiangda release ship --change <change> --profile <name>',
|
|
10
|
+
' 确认预发结果后,再执行 release ship --change <change> --profile <name> --confirm-production;可选 --acceptance-note 留痕。',
|
|
11
11
|
' 未登记环境的旧工作区:openxiangda release publish --change <change> --profile <name>',
|
|
12
12
|
'',
|
|
13
13
|
'该命令只接受已经 merge/fast-forward 并 push 的权威主分支,自动暂存并原子激活。',
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
- ✅ `resource plan` 与 publish dry-run 严格只允许 GET/HEAD;遇到 `READ_ONLY_AUTH_REQUIRED` 时先执行 `openxiangda auth refresh --profile <name>` 或重新登录,不得在 plan 内自动 POST 刷新 token。
|
|
58
58
|
- ✅ Function/Automation 使用 Backend Release v2;正式多资源发布用精确 `--only/--code` 加 `--stage-only` 暂存,同一 child 可混合 create、source-only update 和显式 manifest replacement,再由 Root App finalize 原子激活。整包 manifest 替换必须另加 `--replace-manifest --reason "..."`。
|
|
59
59
|
- ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
|
|
60
|
-
- ✅ 已通过 `environment init` 登记或 `environment attach`
|
|
60
|
+
- ✅ 已通过 `environment init` 登记或 `environment attach` 接入的工作区默认使用两段式 `release ship`。第一次命令只冻结 candidate 并部署 preproduction,停止等待正式晋级确认;确认预发结果后,另一次命令提供 `--confirm-production`,即可晋级同一 candidate。人工验收是默认建议,可用可选的 `--acceptance-note` 留痕,但不是所有低风险或紧急发布的硬审批门禁。preproduction / production 的 appType、资源 ID、数据和副作用策略完全隔离,严禁跨环境复制 ID 或直接 `release publish`。旧单目标映射只允许按相同 appType 迁移到预发。仅在用户明确授权的投产前重分类中使用 `environment swap --reason "..." --confirm-production` 原子交换两个既有应用的环境角色;数据和 Release Head 不移动,副作用默认不放开。单环境副作用策略用 `environment policy update <kind|id> ... --dry-run` 预览后按 revision CAS 写入,禁止借用 swap;默认保留未指定字段,正式写入需 `--confirm-production`,且 `organizationWrites=explicit_capability_only` 不绕过 `app:organization:manage`。
|
|
61
61
|
- ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
|
|
62
62
|
|
|
63
63
|
## 严禁
|