openxiangda 1.0.164 → 1.0.166
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 +9 -7
- package/lib/change-baseline.js +74 -6
- package/lib/cli.js +2508 -167
- package/lib/sdd.js +320 -6
- package/lib/utils.js +76 -2
- package/openxiangda-skills/SKILL.md +13 -6
- package/openxiangda-skills/references/pages/publish-flow.md +15 -16
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +24 -2
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +14 -6
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +10 -9
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +2 -0
- package/package.json +2 -1
- package/packages/sdk/dist/runtime/index.cjs +164 -48
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/index.mjs +164 -48
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +164 -48
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +79 -1
- package/packages/sdk/dist/runtime/react.d.ts +79 -1
- package/packages/sdk/dist/runtime/react.mjs +164 -48
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +1 -0
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +1 -0
- package/templates/openxiangda-react-spa/AGENTS.md +12 -10
- package/templates/openxiangda-react-spa/scripts/build-js-code.mjs +20 -2
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +2 -2
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +2 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +1 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +5 -3
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +20 -2
package/README.md
CHANGED
|
@@ -36,14 +36,14 @@ openxiangda design gates --topic public-access --json
|
|
|
36
36
|
openxiangda sdd context --change add-customer-page --changed --json
|
|
37
37
|
openxiangda sdd propose add-customer-page --pages customer --runtime
|
|
38
38
|
openxiangda sdd approve add-customer-page --summary "用户确认"
|
|
39
|
-
openxiangda sdd verify add-customer-page --changed --stage implementation
|
|
39
|
+
openxiangda sdd verify add-customer-page --changed --stage implementation --profile dev
|
|
40
40
|
openxiangda resource validate function --only customer_get,customer_save --profile dev
|
|
41
41
|
openxiangda resource plan function --only customer_get,customer_save --profile dev
|
|
42
42
|
openxiangda runtime deploy --change add-customer-page --profile dev --no-activate --json
|
|
43
|
-
openxiangda sdd verify add-customer-page --changed --stage prepublish
|
|
43
|
+
openxiangda sdd verify add-customer-page --changed --stage prepublish --profile dev
|
|
44
44
|
openxiangda release begin --change add-customer-page --profile dev
|
|
45
|
-
openxiangda resource publish function --only customer_get,customer_save --change add-customer-page --profile dev
|
|
46
|
-
openxiangda
|
|
45
|
+
openxiangda resource publish function --only customer_get,customer_save --stage-only --change add-customer-page --profile dev
|
|
46
|
+
openxiangda release app-finalize --staged-resources-json .openxiangda/releases/add-customer-page/staged-resources.json --change add-customer-page --profile dev
|
|
47
47
|
openxiangda release end --profile dev
|
|
48
48
|
openxiangda resource explain public-access --json
|
|
49
49
|
openxiangda inspect app --profile dev --json
|
|
@@ -60,7 +60,7 @@ Concurrent development may still upload immutable previews with `--no-activate`,
|
|
|
60
60
|
|
|
61
61
|
After a live promotion, merge or fast-forward the exact frozen publish commit into the authoritative default branch and push it before closing the task. `openxiangda release integration-status --profile <name>` checks the live remote tip; normal `release end` keeps the reconciliation evidence and returns `RELEASE_MAIN_MERGE_PENDING` until that exact SHA is present. Squash/rebase does not preserve the published commit and therefore does not pass. The authoritative target is always the live default `main`/`master` of `origin` (or the repository's sole remote); CLI flags, environment variables, local-only branches, and lease expiry cannot bypass it. OpenXiangda's own `npm publish` and the platform image release script apply the stronger pre-release form of the same rule: root and pinned submodule commits must already be in their remote default branches.
|
|
62
62
|
|
|
63
|
-
Source-triggered Function and Automation publishing is source-only by default.
|
|
63
|
+
Source-triggered Function and Automation publishing is source-only by default. Backend Release v2 can mix create, source-only update, and manifest replacement update in one immutable child; a stale target therefore causes zero resource writes instead of failing halfway through 88 Functions and 11 Automations. `--stage-only` stops after verification, reports all handled selectors, and never falls back to direct Function/Automation writes when the platform is missing or incompatible. The release preserves online `resourceBindings`, input/output contracts, descriptive metadata, trigger/view configuration, and enabled/published state, and planned noops do not advance resource versions or timestamps. To intentionally make a complete local manifest replace the online definition, select exact codes and opt in explicitly with `--replace-manifest --reason "<why this replacement is safe>"`; `--sdd-bypass` does not widen that scope. Standard workspaces use the CLI-bundled scoped builder without spawning workspace `pnpm`, and `source_lineage_v1` compares authored source/dependencies independently from generated bundle bytes.
|
|
64
64
|
|
|
65
65
|
React SPA templates include stable Vite manual chunk grouping for React, antd, ECharts, editor dependencies, and OpenXiangda runtime/component entrypoints. The SDK keeps large UI dependencies such as `antd-mobile` and `dayjs` external so the application bundler can split them by route. Applications should import from public package entrypoints such as `openxiangda`, `openxiangda/runtime`, and `openxiangda/runtime/react`; do not import `openxiangda/packages/sdk/dist/...` internals to chase bundle size.
|
|
66
66
|
|
|
@@ -94,7 +94,7 @@ Domestic npm mirrors may lag and return an older OpenXiangda version. `openxiang
|
|
|
94
94
|
|
|
95
95
|
Architecture-class requests are plan-gated by default. For new apps, complex pages, login/register, public/no-login access, role/data-scope design, workflow/automation, App Function, connector, notification, and external integration work, AI agents must plan first and implement only after the user confirms the design.
|
|
96
96
|
|
|
97
|
-
Risk is tiered. Read-only/docs/tests are L0. Narrow reversible copy/style or single existing binding fixes are L1 and use `openxiangda sdd quick` to record exact files/resources plus the user's already-explicit intent; do not add a second proposal/approval ceremony to such a small change. Function quick fixes additionally require the complete negative risk assessment. Schema, business Functions, Automation/Workflow, permissions, auth/public access, data writes, and runtime/config are L2; irreversible production or app-wide changes are L3. L2/L3 use an explicit SDD change from context through plan/check/verify/publish/archive.
|
|
97
|
+
Risk is tiered. Read-only/docs/tests are L0. Narrow reversible copy/style or single existing binding fixes are L1 and use `openxiangda sdd quick` to record exact files/resources plus the user's already-explicit intent; do not add a second proposal/approval ceremony to such a small change. Function quick fixes additionally require the complete negative risk assessment. Schema, business Functions, Automation/Workflow, permissions, auth/public access, data writes, and runtime/config are L2; irreversible production or app-wide changes are L3. L2/L3 use an explicit SDD change from context through plan/check/verify/publish/archive. `verify --stage implementation` deliberately does not block coding on unfinished prose/checklists; plain `verify` stops at the prepublish boundary and never demands future postpublish/archive evidence. Exact argv and staged-child scope become strict only at the write/finalize boundary. Emergency releases can still use `--sdd-bypass --reason "..."` and remain auditable.
|
|
98
98
|
|
|
99
99
|
Every concurrent task should use its own Git worktree/branch. `sdd propose|quick|context` automatically claims the current worktree for the active `CODEX_THREAD_ID`; a second task receives `WORKTREE_OWNED_BY_ANOTHER_TASK` before it can publish through the shared directory. Inspect or explicitly release ownership with `openxiangda workspace ownership status|release`; forced takeover requires a reason. Live `resource publish` requires exact `--only <codes>` or `--code <code>`; an app-wide publish needs explicit `--all --reason`, even when SDD is bypassed. The approved change scope, not the checkout's global dirty set, is the release boundary.
|
|
100
100
|
|
|
@@ -127,7 +127,7 @@ For source-only Function/Automation changes, the final command does not reconstr
|
|
|
127
127
|
|
|
128
128
|
Page repair publishing is staged by default. It first freezes `pages/snapshot`, sends the active Page Release parent plus every page revision, and uses revision `0` only for a genuinely new page. Review with `openxiangda page head|releases|detail|diff`; activate an immutable complete release explicitly with `page activate <releaseId> --change <change>`. Historical activation requires `page rollback <releaseId> --rollback --change <change> --reason "..."`. Parent or revision conflicts are never refreshed or retried automatically.
|
|
129
129
|
|
|
130
|
-
Use `openxiangda release app-capture` to read the platform's transactionally consistent whole-app manifest. For a normal multi-resource release, stage the changed Runtime/Page/Backend/Form child releases
|
|
130
|
+
Use `openxiangda release app-capture` to read the platform's transactionally consistent whole-app manifest. For a normal multi-resource release, stage the changed Runtime/Page/Backend/Form child releases; commands carrying the same `--change` atomically accumulate their canonical immutable entries in `.openxiangda/releases/<change>/staged-resources.json`. Then run `app-finalize --change <change> --staged-resources-json <JSON|file>`. The CLI overlays Runtime/Page/Backend by singleton kind and Form by `formUuid`, preserves every unmodified active child from the read-only capture, and sends the complete frozen manifest through `prepare -> verify -> activate` with `activateStagedChildren=true`. Runtime staging never rewrites local active Runtime state, and a malformed staged response fails closed. The child heads and App head therefore switch in one database transaction (`atomic_staged_children_v1`); any parent, revision, asset, or hash drift stops with zero root activation and is never refreshed or retried. `app-prepare` accepts the same overlay for a manual reviewed flow, and `app-activate <releaseId> --activate-staged-children` performs the explicit atomic activation. Calling `app-finalize` without an overlay remains a compatibility-only retrospective aggregation of already-active children. `app-rollback <releaseId> --change <change> --reason "..."` prepares the audited rollback manifest.
|
|
131
131
|
|
|
132
132
|
For small live fixes, diagnosis, or AI command discovery, use first-class resource commands. They all accept `--profile`, `--app-type`, `--json`, `--json-file`, `--dry-run`, and write commands can use `--write-manifest` to avoid repo/platform drift:
|
|
133
133
|
|
|
@@ -423,6 +423,8 @@ const PublicAccessError = ({ error }: { error: { message?: string } }) => (
|
|
|
423
423
|
|
|
424
424
|
后端业务逻辑优先声明为 App Function:源码放在 `src/functions/<functionCode>/index.ts`,资源 manifest 放在 `src/resources/functions/<functionCode>.json`。函数运行在 trusted_node 中,通过 `ctx.form.queryOne/queryMany/getById/createOne/updateOne/updateById`、`ctx.dataView`、`ctx.connector`、`ctx.notification`、`ctx.organization`、`ctx.platform.roles`、`ctx.platform.api` 等受控 API 访问平台能力;自动化、流程和运行时接口都可以调用同一个 function。角色查询和角色成员维护优先使用 `ctx.platform.roles.list/findByCode/addUsers/removeUser`,底层 `ctx.platform.api` 返回 HTTP 包装和平台 envelope,需要业务代码自行解包。运行时页面调用会在 `ctx.operator`、`ctx.currentUser`、`ctx.permissions` 中注入可信的 `roleCodes`、`currentRoleCode`、`hasFullAccess`、`isAppAdmin`、`isPlatformAdmin`,敏感动作必须读取这些服务端上下文做授权,不要信任页面 input 里传入的角色字段。`ctx.form.createOne/updateOne/updateById` 是后端受控写入,授权边界是函数 manifest 的 `resources.forms` 绑定和函数调用授权,不是页面用户对目标表单的直接提交入口;报名、签到、福利选择等内部多表写入应走 App Function,不要为了写内部表单给普通用户开放原始 submit 权限。JS_CODE V2 仍兼容,但新逻辑建议写成 function,再由 `function_call` 节点、`sdk.function.invoke(code, { input })` 或 `/:appType/v1/functions/:code/invoke.json` 调用。直接运行时接口默认要求调用者具备应用自动化管理权限;普通页面调用必须在函数 `definitionJson.runtimeInvoke.audience` 声明 `authenticated`、`page_permission_group`、`app_roles` 或 `scope_policy`,`roleCodes` 只用于当前应用角色精确匹配,不支持 `"*"` / `"all-app-roles"`。自动化/流程内部调用走服务端受控上下文。内部业务表单需要关闭原始写入接口时,在表单 settings 中设置 `runtimeWrite.mode="function_only"`。
|
|
425
425
|
|
|
426
|
+
App Function 访问第三方凭据时使用 `app_function_secrets_v1`:manifest 顶层只声明 `secretRefs: [{ "name": "dingtalk_org_app_key", "required": true }]`,同时使用 `definitionJson.version="function_v2"`、`runtimeContractVersion="trusted_node_v2"`;源码通过 `await ctx.secrets.get(name)` 解析,并通过 `ctx.utils.http` 访问受控公网 HTTPS(该桥接不会携带平台 Runtime token)。值只能经 `openxiangda secret create|rotate --value-stdin --change <id> --profile <name>` 或隐藏 TTY 输入,禁止进入 Git、manifest、源码、构建产物、plan、日志或异常。带 `secretRefs` 的 Function 必须走 `backend_release_v2`;需要整应用原子发布时先执行 `resource publish function --only <code> --stage-only`,再把返回的真实 `stagedResource` 交给 `release app-finalize --staged-resources-json ...` 完成 `atomic_staged_children_v2`。默认直接激活的 Backend Release 只返回 `activeResource`,不会伪装成 staged;旧平台 capability 不完整时 CLI 会失败关闭,绝不忽略绑定。
|
|
427
|
+
|
|
426
428
|
平台部门和账号管理走 app-scoped organization 能力。调用者必须在目标应用拥有 `app:organization:manage`;平台管理员天然可用,普通应用角色需要显式授权。Runtime service principal 不会直接放行,`ctx.organization` 会按真实操作人 / audit actor 校验权限。新接口只提供创建、更新、查询和密码专用操作,不提供删除;`account-update` 不能携带 `password`,重置他人密码必须走 `account-reset-password`,当前用户改密用 SDK / `ctx.organization.accounts.changeMyPassword({ oldPassword, newPassword })`。
|
|
427
429
|
|
|
428
430
|
CLI 写操作必须加 `--force`:
|
package/lib/change-baseline.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const { saveProjectState } = require('./config');
|
|
8
|
+
const { runWorkspaceJsCodeBuildBatch } = require('./js-code-build');
|
|
8
9
|
|
|
9
10
|
const SOURCE_KIND_BY_RESOURCE_KIND = Object.freeze({
|
|
10
11
|
Function: 'functions',
|
|
@@ -262,15 +263,35 @@ function buildGitBaseSourceArtifacts({
|
|
|
262
263
|
const specs = normalizedTargets
|
|
263
264
|
.map(target => `${target.sourceKind}:${target.code}`)
|
|
264
265
|
.join(',');
|
|
265
|
-
const
|
|
266
|
-
packageManager,
|
|
267
|
-
['build-js-code', '--scripts', specs],
|
|
266
|
+
const batchProcess = runWorkspaceJsCodeBuildBatch(
|
|
268
267
|
buildRoot,
|
|
269
|
-
|
|
268
|
+
normalizedTargets.map(target => ({
|
|
269
|
+
sourceKind: target.sourceKind,
|
|
270
|
+
scriptCode: target.code,
|
|
271
|
+
})),
|
|
272
|
+
{
|
|
273
|
+
packageManager,
|
|
274
|
+
spawnOptions: {
|
|
275
|
+
env: buildEnv,
|
|
276
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
277
|
+
},
|
|
278
|
+
}
|
|
270
279
|
);
|
|
280
|
+
const batch = normalizeBuildProcessResult(batchProcess, [
|
|
281
|
+
packageManager,
|
|
282
|
+
'build-js-code',
|
|
283
|
+
'--scripts',
|
|
284
|
+
specs,
|
|
285
|
+
]);
|
|
271
286
|
|
|
272
|
-
let buildMode =
|
|
287
|
+
let buildMode =
|
|
288
|
+
batchProcess.openxiangdaBuildMode === 'canonical-scoped'
|
|
289
|
+
? 'canonical-scoped'
|
|
290
|
+
: 'batch';
|
|
273
291
|
if (!batch.ok) {
|
|
292
|
+
if (batchProcess.openxiangdaBuildMode === 'canonical-scoped') {
|
|
293
|
+
throw buildFailureError(normalizedTargets[0], batch);
|
|
294
|
+
}
|
|
274
295
|
buildMode = 'legacy-fallback';
|
|
275
296
|
for (const target of normalizedTargets) {
|
|
276
297
|
const legacy = runBuildCommand(
|
|
@@ -303,6 +324,11 @@ function buildGitBaseSourceArtifacts({
|
|
|
303
324
|
const artifactPath = path.resolve(buildRoot, ...relativePath.split('/'));
|
|
304
325
|
assertArtifactFileSafe(buildRoot, artifactPath, target);
|
|
305
326
|
const artifactHash = sha256File(artifactPath);
|
|
327
|
+
const sourceHash = readBuiltSourceHash(
|
|
328
|
+
buildRoot,
|
|
329
|
+
target.sourceKind,
|
|
330
|
+
target.code
|
|
331
|
+
);
|
|
306
332
|
const key = `${target.kind}:${target.code}`;
|
|
307
333
|
artifacts[key] = {
|
|
308
334
|
kind: target.kind,
|
|
@@ -311,6 +337,7 @@ function buildGitBaseSourceArtifacts({
|
|
|
311
337
|
relativePath,
|
|
312
338
|
artifactHash,
|
|
313
339
|
sha256: artifactHash,
|
|
340
|
+
...(sourceHash ? { sourceHash } : {}),
|
|
314
341
|
};
|
|
315
342
|
localBaseArtifactHashes[key] = artifactHash;
|
|
316
343
|
}
|
|
@@ -324,6 +351,31 @@ function buildGitBaseSourceArtifacts({
|
|
|
324
351
|
});
|
|
325
352
|
}
|
|
326
353
|
|
|
354
|
+
function readBuiltSourceHash(buildRoot, sourceKind, code) {
|
|
355
|
+
const key = `${sourceKind}/${code}`;
|
|
356
|
+
for (const filename of [
|
|
357
|
+
'build-cache.json',
|
|
358
|
+
'build-cache.cli-v4.json',
|
|
359
|
+
'build-cache.cli-v3.json',
|
|
360
|
+
]) {
|
|
361
|
+
try {
|
|
362
|
+
const cache = JSON.parse(
|
|
363
|
+
fs.readFileSync(
|
|
364
|
+
path.join(buildRoot, '.openxiangda', filename),
|
|
365
|
+
'utf8'
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
const sourceHash = String(
|
|
369
|
+
cache?.targets?.[key]?.sourceHash || ''
|
|
370
|
+
).toLowerCase();
|
|
371
|
+
if (/^[a-f0-9]{64}$/.test(sourceHash)) return sourceHash;
|
|
372
|
+
} catch {
|
|
373
|
+
// Custom builders are allowed to omit the optional lineage cache.
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
|
|
327
379
|
function withGitBaseWorkspace(
|
|
328
380
|
{
|
|
329
381
|
cwd = process.cwd(),
|
|
@@ -751,6 +803,20 @@ function runBuildCommand(packageManager, args, cwd, env) {
|
|
|
751
803
|
};
|
|
752
804
|
}
|
|
753
805
|
|
|
806
|
+
function normalizeBuildProcessResult(result, fallbackCommand) {
|
|
807
|
+
return {
|
|
808
|
+
ok: !result.error && result.status === 0,
|
|
809
|
+
status: result.status,
|
|
810
|
+
error: result.error || null,
|
|
811
|
+
stdout: String(result.stdout || ''),
|
|
812
|
+
stderr: String(result.stderr || ''),
|
|
813
|
+
command:
|
|
814
|
+
Array.isArray(result.spawnargs) && result.spawnargs.length > 0
|
|
815
|
+
? result.spawnargs
|
|
816
|
+
: fallbackCommand,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
|
|
754
820
|
function buildIsolatedEnvironment(
|
|
755
821
|
env,
|
|
756
822
|
{ currentWorkspaceRoot, buildRoot, repositoryRoot, extractRoot }
|
|
@@ -782,7 +848,9 @@ function buildIsolatedEnvironment(
|
|
|
782
848
|
function buildFailureError(target, batch, legacy) {
|
|
783
849
|
const output = [
|
|
784
850
|
summarizeCommandFailure('batch', batch),
|
|
785
|
-
|
|
851
|
+
...(legacy
|
|
852
|
+
? [summarizeCommandFailure(`legacy ${target.kind}:${target.code}`, legacy)]
|
|
853
|
+
: []),
|
|
786
854
|
].join('\n');
|
|
787
855
|
return changeBaselineError(
|
|
788
856
|
'CHANGE_BASELINE_BUILD_FAILED',
|