openxiangda 1.0.203 → 1.0.204
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 +1 -1
- package/lib/cli.js +17 -1
- package/lib/release-plan.js +24 -5
- package/openxiangda-skills/SKILL.md +2 -0
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +2 -0
- package/package.json +1 -1
- package/templates/openxiangda-react-spa/AGENTS.md +2 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +1 -0
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ openxiangda app snapshot APP_XXXX --profile dev --json
|
|
|
82
82
|
|
|
83
83
|
User tokens are stored in `~/.openxiangda/profiles.json` with `0600` permissions. Shared workspace environment values, including `APP_OSS_*`, can live in `~/.openxiangda/.env` and are inherited by new workspaces. Project `.env` files still work and override the global defaults. Project state is stored in `.openxiangda/state.json` and contains only profile-specific resource IDs; CLI writes use a workspace lock plus atomic merge/rename so concurrent processes do not truncate another profile's state.
|
|
84
84
|
|
|
85
|
-
An environment-managed workspace keeps one logical application with independent `preproduction` and `production` targets. Each target owns its own `appType`, resource IDs, release heads, data, and side-effect policy; IDs must never be copied across targets. `release ship` is deliberately two-phase: the first invocation seals the clean authoritative Git revision and deploys one immutable candidate only to preproduction; it always stops at `awaiting_production_confirmation`. A later invocation includes `--confirm-production` and promotes that exact candidate to production without rebuilding it. Real human acceptance is the recommended default; an optional `--acceptance-note` records it in the deployment evidence, but the platform does not force every low-risk or emergency release through a rigid approval gate. It is impossible to prepare and promote in one invocation. For an audited catch-up whose exact non-delete targets are already merged but whose active resources came from several historical release lineages, the first invocation may explicitly add `--adopt-online-baseline --adoption-reason "..."`; ship validates this pair before creating a candidate/deployment, then forwards it only to exact scoped resource stages. Frozen online heads, change/lease ownership, server CAS, staged children, and the single App finalize remain mandatory. The lower-level `candidate/deploy/test/promote` commands remain available for recovery and diagnostics. Direct `release publish` is retained only for legacy, unmanaged workspaces and fails closed for either target of an environment-managed application.
|
|
85
|
+
An environment-managed workspace keeps one logical application with independent `preproduction` and `production` targets. Each target owns its own `appType`, resource IDs, release heads, data, and side-effect policy; IDs must never be copied across targets. `release ship` is deliberately two-phase: the first invocation seals the clean authoritative Git revision and deploys one immutable candidate only to preproduction; it always stops at `awaiting_production_confirmation`. A later invocation includes `--confirm-production` and promotes that exact candidate to production without rebuilding it. Real human acceptance is the recommended default; an optional `--acceptance-note` records it in the deployment evidence, but the platform does not force every low-risk or emergency release through a rigid approval gate. It is impossible to prepare and promote in one invocation. For an audited catch-up whose exact non-delete targets are already merged but whose active resources came from several historical release lineages, the first invocation may explicitly add `--adopt-online-baseline --adoption-reason "..."`; ship validates this pair before creating a candidate/deployment, then forwards it only to exact scoped resource stages. Frozen online heads, change/lease ownership, server CAS, staged children, and the single App finalize remain mandatory. Supported configuration resources use exact `resourceSelectors`; a legacy `resources=true` category marker is narrowed by those selectors, while unknown, wildcard, destructive, and genuinely unscoped generic resources remain blocked. The lower-level `candidate/deploy/test/promote` commands remain available for recovery and diagnostics. Direct `release publish` is retained only for legacy, unmanaged workspaces and fails closed for either target of an environment-managed application.
|
|
86
86
|
|
|
87
87
|
Exact, non-destructive configuration selectors such as Data Views and permission groups are now sequenced automatically inside the same ship journal instead of requiring separate SDD changes. New forms are idempotently ensured per environment before their immutable FormRelease is staged. Unscoped resources and destructive configuration deletes remain fail-closed.
|
|
88
88
|
|
package/lib/cli.js
CHANGED
|
@@ -68,6 +68,7 @@ const {
|
|
|
68
68
|
buildWorkspaceReleaseCommands,
|
|
69
69
|
buildWorkspaceReleaseSteps,
|
|
70
70
|
commandFromArgs,
|
|
71
|
+
directResourceTypeForKey,
|
|
71
72
|
normalizeReleaseTargets,
|
|
72
73
|
releasePlanHash,
|
|
73
74
|
} = require('./release-plan');
|
|
@@ -12921,8 +12922,21 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
|
|
|
12921
12922
|
'workflows',
|
|
12922
12923
|
'formSettings',
|
|
12923
12924
|
]);
|
|
12925
|
+
const resourceSelectors = Object.fromEntries(
|
|
12926
|
+
RESOURCE_SPECS
|
|
12927
|
+
.filter(
|
|
12928
|
+
spec =>
|
|
12929
|
+
!codeResourceKeys.has(spec.key) &&
|
|
12930
|
+
directResourceTypeForKey(spec.key)
|
|
12931
|
+
)
|
|
12932
|
+
.map(spec => [spec.key, codes(spec.key)])
|
|
12933
|
+
.filter(([, selectedCodes]) => selectedCodes.length > 0)
|
|
12934
|
+
);
|
|
12924
12935
|
const hasGenericResources = RESOURCE_SPECS.some(
|
|
12925
|
-
spec =>
|
|
12936
|
+
spec =>
|
|
12937
|
+
!codeResourceKeys.has(spec.key) &&
|
|
12938
|
+
!directResourceTypeForKey(spec.key) &&
|
|
12939
|
+
(manifest[spec.key] || []).length > 0
|
|
12926
12940
|
) || (planActions || []).some(
|
|
12927
12941
|
action =>
|
|
12928
12942
|
action?.action === 'delete' &&
|
|
@@ -12939,6 +12953,7 @@ function buildResourceManifestSddTargets(manifest, planActions = []) {
|
|
|
12939
12953
|
workflows: unique([...codes('workflows'), ...deletedCodes('workflow')]).sort(),
|
|
12940
12954
|
jsCodeNodes: [],
|
|
12941
12955
|
resources: hasGenericResources,
|
|
12956
|
+
resourceSelectors,
|
|
12942
12957
|
runtime: false,
|
|
12943
12958
|
other: [],
|
|
12944
12959
|
};
|
|
@@ -26907,5 +26922,6 @@ function buildWorkspacePublishEnv(
|
|
|
26907
26922
|
}
|
|
26908
26923
|
|
|
26909
26924
|
module.exports = {
|
|
26925
|
+
buildResourceManifestSddTargets,
|
|
26910
26926
|
main,
|
|
26911
26927
|
};
|
package/lib/release-plan.js
CHANGED
|
@@ -21,6 +21,14 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
|
|
|
21
21
|
.map(([type, values]) => [type, uniqueSorted(values || [])])
|
|
22
22
|
.filter(([, values]) => values.length > 0)
|
|
23
23
|
);
|
|
24
|
+
const hasExactDirectResourceScope =
|
|
25
|
+
Object.entries(resourceSelectors).length > 0 &&
|
|
26
|
+
Object.entries(resourceSelectors).every(
|
|
27
|
+
([type, selectors]) =>
|
|
28
|
+
directResourceTypeForKey(type) &&
|
|
29
|
+
type !== 'unknown' &&
|
|
30
|
+
(selectors || []).every(selector => selector !== '*')
|
|
31
|
+
);
|
|
24
32
|
const logicalTargets = {
|
|
25
33
|
forms: uniqueSorted(targets.forms || []),
|
|
26
34
|
pages: uniqueSorted(targets.pages || []),
|
|
@@ -28,7 +36,11 @@ function normalizeReleaseTargets(targets = {}, runtimeMode = 'legacy') {
|
|
|
28
36
|
automations: uniqueSorted(targets.automations || []),
|
|
29
37
|
workflows: uniqueSorted(targets.workflows || []),
|
|
30
38
|
jsCodeNodes: uniqueSorted(targets.jsCodeNodes || []),
|
|
31
|
-
resources
|
|
39
|
+
// Older SDD records used resources=true as a category marker even when
|
|
40
|
+
// they also carried an exact selector. Exact supported selectors are the
|
|
41
|
+
// authoritative boundary; only an unscoped/unknown/wildcard declaration
|
|
42
|
+
// remains a generic resource closure.
|
|
43
|
+
resources: Boolean(targets.resources) && !hasExactDirectResourceScope,
|
|
32
44
|
resourceSelectors,
|
|
33
45
|
resourceDeletes,
|
|
34
46
|
runtime: Boolean(targets.runtime),
|
|
@@ -56,7 +68,9 @@ function unsupportedAtomicReleaseTargets(targets = {}) {
|
|
|
56
68
|
)) {
|
|
57
69
|
if (
|
|
58
70
|
(selectors || []).length > 0 &&
|
|
59
|
-
(!
|
|
71
|
+
(!directResourceTypeForKey(type) ||
|
|
72
|
+
type === 'unknown' ||
|
|
73
|
+
selectors.includes('*'))
|
|
60
74
|
) {
|
|
61
75
|
unsupported.push(`${type}:${selectors.join(',')}`);
|
|
62
76
|
}
|
|
@@ -114,6 +128,10 @@ const DIRECT_RESOURCE_RELEASE_ORDER = Object.freeze([
|
|
|
114
128
|
'notifications',
|
|
115
129
|
]);
|
|
116
130
|
|
|
131
|
+
function directResourceTypeForKey(key) {
|
|
132
|
+
return DIRECT_RESOURCE_TYPE_BY_KEY[key] || null;
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
function assertAtomicReleaseTargetsSupported(targets) {
|
|
118
136
|
const unsupported = unsupportedAtomicReleaseTargets(targets);
|
|
119
137
|
if (unsupported.length === 0) return;
|
|
@@ -238,7 +256,7 @@ function buildDirectConfigurationReleaseSteps(
|
|
|
238
256
|
return Object.entries(targets.resourceSelectors || {})
|
|
239
257
|
.filter(
|
|
240
258
|
([type, selectors]) =>
|
|
241
|
-
|
|
259
|
+
directResourceTypeForKey(type) &&
|
|
242
260
|
uniqueSorted(selectors || []).length > 0
|
|
243
261
|
)
|
|
244
262
|
.sort(
|
|
@@ -248,12 +266,12 @@ function buildDirectConfigurationReleaseSteps(
|
|
|
248
266
|
)
|
|
249
267
|
.map(([type, selectors]) =>
|
|
250
268
|
createStep(
|
|
251
|
-
`config-${
|
|
269
|
+
`config-${directResourceTypeForKey(type)}`,
|
|
252
270
|
appendProfileAndChange(
|
|
253
271
|
[
|
|
254
272
|
'resource',
|
|
255
273
|
'publish',
|
|
256
|
-
|
|
274
|
+
directResourceTypeForKey(type),
|
|
257
275
|
'--only',
|
|
258
276
|
uniqueSorted(selectors).join(','),
|
|
259
277
|
],
|
|
@@ -476,6 +494,7 @@ module.exports = {
|
|
|
476
494
|
buildWorkspaceReleaseCommands,
|
|
477
495
|
buildWorkspaceReleaseSteps,
|
|
478
496
|
commandFromArgs,
|
|
497
|
+
directResourceTypeForKey,
|
|
479
498
|
normalizeReleaseTargets,
|
|
480
499
|
releasePlanHash,
|
|
481
500
|
unsupportedAtomicReleaseTargets,
|
|
@@ -114,6 +114,8 @@ For an audited catch-up whose exact non-delete targets are already merged but wh
|
|
|
114
114
|
|
|
115
115
|
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.
|
|
116
116
|
|
|
117
|
+
Exact `resourceSelectors` are authoritative for supported configuration resources such as `publicAccessPolicies`. A legacy `resources=true` category marker is narrowed by those exact selectors and must not become an app-wide generic-resource release. Missing selectors, unknown resource types, wildcard `*`, deletes, and genuine app-wide resource closures remain fail closed.
|
|
118
|
+
|
|
117
119
|
`release begin --change` freezes a clean committed `HEAD` only when the current branch is the authoritative default `main`/`master` and its commit exactly equals the live remote tip. Before any live write, the CLI preflights the complete target set and rejects source changes during the release. A feature worktree or unpushed main receives `RELEASE_SOURCE_MAINLINE_REQUIRED` / `RELEASE_SOURCE_MAINLINE_NOT_PUSHED`; merge, test, push, and start the one mainline release instead of forcing it. Optional `.git` remote suffix differences are aliases of the same repository; genuinely different remotes still fail closed.
|
|
118
120
|
|
|
119
121
|
Because promotion begins from the already-pushed authoritative mainline, `release integration-status` should pass immediately after activation. Run it, then `release end`; no post-release branch merge is required.
|
|
@@ -113,6 +113,8 @@ When a reviewed catch-up contains exact non-delete targets that are already on a
|
|
|
113
113
|
|
|
114
114
|
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.
|
|
115
115
|
|
|
116
|
+
Exact `resourceSelectors` are authoritative for supported configuration resources such as `publicAccessPolicies`. A legacy `resources=true` category marker is narrowed by those exact selectors and must not become an app-wide generic-resource release. Missing selectors, unknown resource types, wildcard `*`, deletes, and genuine app-wide resource closures remain fail closed.
|
|
117
|
+
|
|
116
118
|
`runtime deploy --no-activate` uploads an immutable preview release from a clean committed mainline `HEAD`. It reads a narrow Runtime head instead of the full app snapshot. Before acquiring a lease, the CLI confirms `package.json#scripts.build` and existing dependencies; it uses `npm run build` to execute the declared script so pnpm worktree symlinks do not trigger a reinstall. Immutable Git-base artifact hashes are cached under the Git common directory. `release begin` requires local main/master and the live remote default tip to be identical, so `integration-status` is already satisfied after activation and `release end` does not wait for a later merge.
|
|
117
119
|
|
|
118
120
|
`release app-head` and `runtime releases` return compact summaries by default. Use `--full` only when the complete manifest is required.
|
package/package.json
CHANGED
|
@@ -57,6 +57,8 @@ openxiangda commands --json
|
|
|
57
57
|
|
|
58
58
|
只有已审计代码早已进入权威主线、而精确非删除目标在线上来自多次历史发布且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`。该参数不会放松冻结 online heads、change/lease、删除/全量拒绝、服务端 CAS、staged children 或单次 App finalize 原子激活。
|
|
59
59
|
|
|
60
|
+
受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为发布边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍必须 fail closed。
|
|
61
|
+
|
|
60
62
|
`resource plan` 与 publish dry-run 严格只允许 GET/HEAD。遇到 `READ_ONLY_AUTH_REQUIRED` 时,先执行 `openxiangda auth refresh --profile <name>` 或重新登录再重试;不得在 plan 内自动 POST 刷新 token。
|
|
61
63
|
|
|
62
64
|
完整发布顺序:
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
- ✅ 未登记环境的旧工作区,正式 promotion 先聚合 mainline bundle 并 commit/push,再运行 `release publish --change <id> --profile <name>`。
|
|
60
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;patch 只校验本次提交字段并原样保留未知历史字段,`--full-replace` 才按完整目标删除遗漏字段,正式写入需 `--confirm-production`,且 `organizationWrites=explicit_capability_only` 不绕过 `app:organization:manage`。发布硬门禁只保留明确 scope/profile/target、权限、干净且已推送主线、不可变版本、CAS/租约与生产确认;文案和人工验收说明默认是建议,只有显式 strict 模式才阻断。
|
|
61
61
|
- ✅ 只有已审计目标早已进入权威主线、线上却由多次历史 lineage 组成且无法对应单一 Git 基线时,第一次 `release ship` 才可增加 `--adopt-online-baseline --adoption-reason "..."`;仅允许精确非删除 selectors,冻结 Head、change/lease、服务端 CAS、staged children 与单次 App finalize 仍是硬门禁。
|
|
62
|
+
- ✅ 受支持的配置资源(如 `publicAccessPolicies`)以精确 `resourceSelectors` 为边界;历史 `resources=true` 类别标记会被精确 selector 收窄。缺失 selector、未知类型、通配符 `*`、删除和真正全量资源仍 fail closed。
|
|
62
63
|
- ✅ 本地开发者可运行 `openxiangda studio` 查看两套环境、差异、候选、部署和测试证据;该页面只监听回环地址且只暴露注册动作,生产操作仍需显式确认。
|
|
63
64
|
|
|
64
65
|
## 严禁
|