openxiangda 1.0.270 → 1.0.271
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/lib/application-environments.js +17 -0
- package/lib/cli.js +343 -78
- package/lib/release-plan.js +76 -63
- package/openxiangda-skills/SKILL.md +1 -1
- package/openxiangda-skills/references/notifications.md +28 -23
- package/openxiangda-skills/references/openxiangda-api.md +10 -30
- package/openxiangda-skills/references/pages/page-sdk.md +1 -1
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +5 -7
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +1 -1
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +2 -2
- package/package.json +22 -23
- package/packages/sdk/dist/runtime/index.cjs +39 -39
- package/packages/sdk/dist/runtime/index.mjs +39 -39
- package/packages/sdk/dist/runtime/react.cjs +39 -39
- package/packages/sdk/dist/runtime/react.d.mts +3 -0
- package/packages/sdk/dist/runtime/react.d.ts +3 -0
- package/packages/sdk/dist/runtime/react.mjs +46 -46
- package/packages/sdk/src/build-source/scripts/publish-all.mjs +33 -2
- package/packages/sdk/src/build-source/scripts/register.mjs +161 -1
- package/templates/openxiangda-react-spa/AGENTS.md +2 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +1 -1
package/lib/release-plan.js
CHANGED
|
@@ -170,10 +170,14 @@ function commandFromArgs(args) {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
function createStep(id, args, options = {}) {
|
|
173
|
+
const env = options.env && Object.keys(options.env).length > 0
|
|
174
|
+
? { env: { ...options.env } }
|
|
175
|
+
: {};
|
|
173
176
|
return {
|
|
174
177
|
id,
|
|
175
178
|
args,
|
|
176
179
|
command: commandFromArgs(args),
|
|
180
|
+
...env,
|
|
177
181
|
writes: options.writes !== false,
|
|
178
182
|
stagedKind: options.stagedKind || null,
|
|
179
183
|
resumeMode: options.resumeMode || 'skip',
|
|
@@ -329,8 +333,7 @@ function buildWorkspaceReleaseSteps(
|
|
|
329
333
|
targets.resourceSelectors?.formPermissionGroups || []
|
|
330
334
|
);
|
|
331
335
|
const stagesFormRelease =
|
|
332
|
-
|
|
333
|
-
(targets.forms.length > 0 || formPermissionGroups.length > 0);
|
|
336
|
+
targets.forms.length > 0 || formPermissionGroups.length > 0;
|
|
334
337
|
const deferredDataViewCodes = stagesFormRelease
|
|
335
338
|
? uniqueSorted(targets.resourceSelectors?.dataViews || [])
|
|
336
339
|
: [];
|
|
@@ -347,58 +350,59 @@ function buildWorkspaceReleaseSteps(
|
|
|
347
350
|
excludeTypes: stagesFormRelease ? ['formPermissionGroups'] : [],
|
|
348
351
|
}
|
|
349
352
|
);
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
changeId
|
|
363
|
-
),
|
|
364
|
-
{ resumeMode: 'replay-local' }
|
|
365
|
-
)
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
if (stagesFormRelease) {
|
|
369
|
-
const resourceTypes = [
|
|
370
|
-
...(targets.forms.length > 0 ? ['form-setting'] : []),
|
|
371
|
-
...(formPermissionGroups.length > 0
|
|
372
|
-
? ['form-permission-group']
|
|
373
|
-
: []),
|
|
374
|
-
];
|
|
375
|
-
const qualify = resourceTypes.length > 1;
|
|
376
|
-
const selectors = [
|
|
377
|
-
...targets.forms.map(code =>
|
|
378
|
-
qualify ? `form-setting:${code}` : code
|
|
353
|
+
const ensuredForms = uniqueSorted([
|
|
354
|
+
...targets.forms,
|
|
355
|
+
...targets.formDependencies,
|
|
356
|
+
]);
|
|
357
|
+
if (ensuredForms.length > 0) {
|
|
358
|
+
steps.push(
|
|
359
|
+
createStep(
|
|
360
|
+
'form-ensure',
|
|
361
|
+
appendProfileAndChange(
|
|
362
|
+
['form', 'ensure', '--only', ensuredForms.join(',')],
|
|
363
|
+
profileArg,
|
|
364
|
+
changeId
|
|
379
365
|
),
|
|
380
|
-
|
|
381
|
-
|
|
366
|
+
{ resumeMode: 'replay-local' }
|
|
367
|
+
)
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (stagesFormRelease) {
|
|
371
|
+
const resourceTypes = [
|
|
372
|
+
...(targets.forms.length > 0 ? ['form-setting'] : []),
|
|
373
|
+
...(formPermissionGroups.length > 0
|
|
374
|
+
? ['form-permission-group']
|
|
375
|
+
: []),
|
|
376
|
+
];
|
|
377
|
+
const qualify = resourceTypes.length > 1;
|
|
378
|
+
const selectors = [
|
|
379
|
+
...targets.forms.map(code =>
|
|
380
|
+
qualify ? `form-setting:${code}` : code
|
|
381
|
+
),
|
|
382
|
+
...formPermissionGroups.map(code =>
|
|
383
|
+
qualify ? `form-permission-group:${code}` : code
|
|
384
|
+
),
|
|
385
|
+
];
|
|
386
|
+
steps.push(
|
|
387
|
+
createStep(
|
|
388
|
+
'form-stage',
|
|
389
|
+
appendProfileAndChange(
|
|
390
|
+
[
|
|
391
|
+
'resource',
|
|
392
|
+
'publish',
|
|
393
|
+
resourceTypes.join(','),
|
|
394
|
+
'--only',
|
|
395
|
+
selectors.join(','),
|
|
396
|
+
],
|
|
397
|
+
profileArg,
|
|
398
|
+
changeId
|
|
382
399
|
),
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
'resource',
|
|
390
|
-
'publish',
|
|
391
|
-
resourceTypes.join(','),
|
|
392
|
-
'--only',
|
|
393
|
-
selectors.join(','),
|
|
394
|
-
],
|
|
395
|
-
profileArg,
|
|
396
|
-
changeId
|
|
397
|
-
),
|
|
398
|
-
{ stagedKind: 'FormRelease' }
|
|
399
|
-
)
|
|
400
|
-
);
|
|
401
|
-
}
|
|
400
|
+
{ stagedKind: 'FormRelease' }
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (runtimeMode === 'react-spa') {
|
|
402
406
|
steps.push(...directConfigurationSteps);
|
|
403
407
|
if (hasResourceTargets) {
|
|
404
408
|
steps.push(
|
|
@@ -466,10 +470,13 @@ function buildWorkspaceReleaseSteps(
|
|
|
466
470
|
return steps;
|
|
467
471
|
}
|
|
468
472
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
+
if (hasResourceTargets) {
|
|
474
|
+
steps.push(...directConfigurationSteps);
|
|
475
|
+
steps.push(
|
|
476
|
+
...buildTargetedResourceReleaseSteps(targets, profileArg, changeId)
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
const onlyTargets = targets.pages.map(code => `pages/${code}`);
|
|
473
480
|
if (onlyTargets.length > 0) {
|
|
474
481
|
steps.push(
|
|
475
482
|
createStep(
|
|
@@ -484,17 +491,17 @@ function buildWorkspaceReleaseSteps(
|
|
|
484
491
|
],
|
|
485
492
|
profileArg,
|
|
486
493
|
changeId
|
|
487
|
-
)
|
|
494
|
+
),
|
|
495
|
+
{
|
|
496
|
+
stagedKind: 'PageRelease',
|
|
497
|
+
env: { OPENXIANGDA_PAGE_STAGE_ONLY: '1' },
|
|
498
|
+
}
|
|
488
499
|
)
|
|
489
500
|
);
|
|
490
501
|
}
|
|
491
|
-
if (hasResourceTargets) {
|
|
492
|
-
steps.push(...directConfigurationSteps);
|
|
493
|
-
steps.push(
|
|
494
|
-
...buildTargetedResourceReleaseSteps(targets, profileArg, changeId)
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
502
|
if (
|
|
503
|
+
stagesFormRelease ||
|
|
504
|
+
targets.pages.length > 0 ||
|
|
498
505
|
targets.functions.length > 0 ||
|
|
499
506
|
targets.automations.length > 0 ||
|
|
500
507
|
targets.workflows.length > 0
|
|
@@ -508,6 +515,12 @@ function buildWorkspaceReleaseSteps(
|
|
|
508
515
|
'app-finalize',
|
|
509
516
|
'--staged-resources-json',
|
|
510
517
|
`.openxiangda/releases/${changeId || 'change'}/staged-resources.json`,
|
|
518
|
+
...(deferredDataViewCodes.length > 0
|
|
519
|
+
? [
|
|
520
|
+
'--finalize-data-views',
|
|
521
|
+
deferredDataViewCodes.join(','),
|
|
522
|
+
]
|
|
523
|
+
: []),
|
|
511
524
|
'--wait',
|
|
512
525
|
],
|
|
513
526
|
profileArg,
|
|
@@ -167,7 +167,7 @@ When the sealed Runtime source intentionally does not descend from the active Ru
|
|
|
167
167
|
|
|
168
168
|
`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.
|
|
169
169
|
|
|
170
|
-
`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. When the same audited historical-lineage condition applies, legacy `release publish` accepts `--adopt-online-baseline --adoption-reason "..."` and forwards the pair only to exact `resource publish --only/--code` stages
|
|
170
|
+
`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. Legacy Page steps receive `OPENXIANGDA_PAGE_STAGE_ONLY=1`; the step environment and staged kind are hashed and journaled, and missing PageRelease evidence blocks Root finalize. Custom page publishers must honor that environment and call the current package CLI so `page publish` records the staged child. When the same audited historical-lineage condition applies, legacy `release publish` accepts `--adopt-online-baseline --adoption-reason "..."` and forwards the pair only to exact `resource publish --only/--code` stages. An intentional manifest replacement may add `--replace-manifest --reason "..."`; the inseparable pair reaches only the unique exact Backend stage. Missing reasons or compatible exact scopes fail before lease acquisition. Environment-managed applications use the two-phase `release ship`; candidate/deploy/test/fail/promote, `release begin`, and child commands remain recovery/diagnostic primitives.
|
|
171
171
|
|
|
172
172
|
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.
|
|
173
173
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Notification Resources
|
|
2
2
|
|
|
3
|
-
Use notification resources when
|
|
3
|
+
Use notification resources when an App Function, workflow, or automation needs reusable message templates.
|
|
4
4
|
|
|
5
|
-
AI generation rule: declare notification resources first, then
|
|
5
|
+
AI generation rule: declare notification resources first, then send only from trusted runtime code through `ctx.notification`. Code pages call a named App Function with a business identifier through `sdk.function.invoke`; they never choose effective recipients, rendered content, or channels. Do not hardcode `/api/notification-config/*` or store channel credentials in source.
|
|
6
6
|
|
|
7
7
|
## Resource Files
|
|
8
8
|
|
|
@@ -135,21 +135,35 @@ Standard card payload variables include `title`, `lastMessage`, `content`, `cont
|
|
|
135
135
|
|
|
136
136
|
## Runtime Calls
|
|
137
137
|
|
|
138
|
-
Code pages
|
|
138
|
+
Code pages invoke a named business action. Pass business identifiers, not an
|
|
139
|
+
effective recipient, rendered message, or channel list:
|
|
139
140
|
|
|
140
141
|
```ts
|
|
141
|
-
await sdk.
|
|
142
|
-
|
|
143
|
-
recipientId: userId,
|
|
144
|
-
payload: {
|
|
145
|
-
title: "预约提醒",
|
|
146
|
-
instrumentName,
|
|
147
|
-
startTime,
|
|
148
|
-
},
|
|
142
|
+
await sdk.function.invoke("send_reservation_reminder", {
|
|
143
|
+
input: { reservationId },
|
|
149
144
|
});
|
|
150
145
|
```
|
|
151
146
|
|
|
152
|
-
|
|
147
|
+
The App Function checks the operator and current reservation state, resolves the
|
|
148
|
+
recipient and template variables from authoritative data, then sends through the
|
|
149
|
+
trusted runtime bridge:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
export default async function sendReservationReminder(ctx) {
|
|
153
|
+
const reservation = await loadAuthorizedReservation(ctx.input.reservationId, ctx);
|
|
154
|
+
await ctx.notification.sendByType({
|
|
155
|
+
notificationType: "reservation_reminder",
|
|
156
|
+
recipientId: reservation.ownerUserId,
|
|
157
|
+
payload: {
|
|
158
|
+
title: "预约提醒",
|
|
159
|
+
instrumentName: reservation.instrumentName,
|
|
160
|
+
startTime: reservation.startTime,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
DingTalk-specific read and preview helpers remain available to pages:
|
|
153
167
|
|
|
154
168
|
```ts
|
|
155
169
|
const capabilities = await sdk.notification.capabilities();
|
|
@@ -161,13 +175,6 @@ const preview = await sdk.notification.previewDingTalk({
|
|
|
161
175
|
},
|
|
162
176
|
});
|
|
163
177
|
|
|
164
|
-
await sdk.notification.sendDingTalk({
|
|
165
|
-
notificationType: "reservation_reminder",
|
|
166
|
-
recipientId: userId,
|
|
167
|
-
payload: {
|
|
168
|
-
title: "预约提醒",
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
178
|
```
|
|
172
179
|
|
|
173
180
|
User-facing in-app message centers should read the platform inbox instead of
|
|
@@ -198,12 +205,10 @@ openxiangda notification preview reservation_reminder --body-json '{"payload":{"
|
|
|
198
205
|
openxiangda notification capabilities --json
|
|
199
206
|
openxiangda notification dingding-preview reservation_reminder --body-json '{"payload":{"title":"测试"}}'
|
|
200
207
|
openxiangda notification dingding-preview --template-code reservation_reminder --body-json '{"payload":{"title":"测试"}}'
|
|
201
|
-
openxiangda
|
|
202
|
-
openxiangda notification send reservation_reminder --body-json '{"recipientId":"USER_ID","payload":{"title":"测试"}}' --force
|
|
203
|
-
openxiangda notification batch-send reservation_reminder --body-json '{"recipients":[{"recipientId":"USER_ID","payload":{"title":"测试"}}]}' --force
|
|
208
|
+
openxiangda function invoke send_reservation_reminder --body-json '{"input":{"reservationId":"RESERVATION_ID"}}'
|
|
204
209
|
```
|
|
205
210
|
|
|
206
|
-
Automation or workflow JS_CODE:
|
|
211
|
+
App Function, Automation, or workflow JS_CODE:
|
|
207
212
|
|
|
208
213
|
```ts
|
|
209
214
|
export default async function notify(ctx) {
|
|
@@ -344,38 +344,16 @@ Requires Bearer token. Updates menu sorting and parent relationships in batch.
|
|
|
344
344
|
|
|
345
345
|
### POST `/apps/:appType/notifications/send-by-type`
|
|
346
346
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
{
|
|
352
|
-
"notificationType": "reservation_reminder",
|
|
353
|
-
"recipientId": "user-id",
|
|
354
|
-
"payload": {
|
|
355
|
-
"title": "预约提醒"
|
|
356
|
-
},
|
|
357
|
-
"channels": ["inapp"]
|
|
358
|
-
}
|
|
359
|
-
```
|
|
347
|
+
Removed. Always returns HTTP/envelope `410` with
|
|
348
|
+
`DIRECT_NOTIFICATION_SEND_REMOVED`. Pages must invoke a named App Function;
|
|
349
|
+
trusted runtime code sends through `ctx.notification` after business
|
|
350
|
+
authorization and recipient resolution.
|
|
360
351
|
|
|
361
352
|
### POST `/apps/:appType/notifications/batch-send-by-type`
|
|
362
353
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
{
|
|
367
|
-
"notificationType": "reservation_reminder",
|
|
368
|
-
"recipients": [
|
|
369
|
-
{
|
|
370
|
-
"recipientId": "user-id",
|
|
371
|
-
"payload": {
|
|
372
|
-
"title": "预约提醒"
|
|
373
|
-
},
|
|
374
|
-
"channels": ["inapp"]
|
|
375
|
-
}
|
|
376
|
-
]
|
|
377
|
-
}
|
|
378
|
-
```
|
|
354
|
+
Removed. Always returns HTTP/envelope `410` with
|
|
355
|
+
`DIRECT_NOTIFICATION_SEND_REMOVED`. Batch recipient selection belongs in a
|
|
356
|
+
bounded, authorized App Function, Workflow, Automation, or trusted JS_CODE.
|
|
379
357
|
|
|
380
358
|
### GET `/apps/:appType/notifications/templates`
|
|
381
359
|
|
|
@@ -426,7 +404,9 @@ or a direct DingTalk config:
|
|
|
426
404
|
|
|
427
405
|
### POST `/apps/:appType/notifications/dingtalk/send`
|
|
428
406
|
|
|
429
|
-
|
|
407
|
+
Removed. Always returns HTTP/envelope `410` with
|
|
408
|
+
`DIRECT_NOTIFICATION_SEND_REMOVED`. The CLI command is also removed; use
|
|
409
|
+
`openxiangda function invoke <functionCode>` for an authorized business action.
|
|
430
410
|
|
|
431
411
|
### GET `/apps/:appType/notifications/type-configs`
|
|
432
412
|
|
|
@@ -15,7 +15,7 @@ Guidelines:
|
|
|
15
15
|
- Use `sdk.dataSource.run()` with a page data source descriptor when the page config should own the data view code, default fields, or default filters.
|
|
16
16
|
- Use `sdk.function.invoke(code, { input })` for reusable backend business logic declared under `src/resources/functions/` and `src/functions/`. Do not implement multi-form orchestration, connector fan-out, notification orchestration, or permission-sensitive backend rules directly in a page component.
|
|
17
17
|
- Use `sdk.connector.invoke`, `sdk.connector.call("connector.api")`, or `sdk.connector.download` for external services. The SDK calls the platform runtime connector endpoint; it must not call third-party domains directly.
|
|
18
|
-
- Use `sdk.
|
|
18
|
+
- Pages must not send notifications directly. Use `sdk.function.invoke(code, { input })` with business identifiers; the named App Function performs authorization, resolves recipients and variables from authoritative data, and sends with trusted `ctx.notification`. Custom notification types must still be declared in `src/resources/notifications/` and published with `openxiangda resource publish`.
|
|
19
19
|
- Use `sdk.export.create` and `sdk.export.get` for asynchronous XLSX exports. Simple pages may send a declarative workbook definition. Complex or reusable exports should send only a published App Function `definitionCode`, the current query snapshot, export scope, and stable selected row IDs. The server executes `structured_export_provider_v1` with fresh user permissions; never upload executable rendering code from the browser. See `docs/structured-export-v1.md`.
|
|
20
20
|
- Use `AttachmentPreviewList`, `ImagePreviewGrid`, or `useFilePreview` from `openxiangda/runtime/react` for in-page attachment previews in a custom React SPA page. They use the current PageSdk context and enforce the platform capability and ticket contracts. Use `sdk.createFileAccessTicket(bucketName, objectName, fileName, "preview", { appType })` only when the page needs a shareable or new-window preview link. `appType` defaults to the current page context. Open `response.result.previewPageUrl`; do not use `previewUrl` or `/service/file/preview-by-ticket/:ticket` as the page entry.
|
|
21
21
|
- Use `sdk.organization.departments.*` and `sdk.organization.accounts.*` only for intentional organization pages. Read-only list/detail pages need `app:organization:read` or `app:organization:manage`; writes and password operations need `app:organization:manage`. Do not call legacy `/user` or `/department` endpoints from pages.
|
|
@@ -552,19 +552,17 @@ const stats = await sdk.dataView.stats("ticket_stats_by_customer", {
|
|
|
552
552
|
}
|
|
553
553
|
```
|
|
554
554
|
|
|
555
|
-
|
|
555
|
+
页面调用具名 App Function,只传业务标识:
|
|
556
556
|
|
|
557
557
|
```ts
|
|
558
|
-
await sdk.
|
|
559
|
-
|
|
560
|
-
recipientId: userId,
|
|
561
|
-
payload: { title: "预约提醒", instrumentName, startTime },
|
|
558
|
+
await sdk.function.invoke("send_reservation_reminder", {
|
|
559
|
+
input: { reservationId },
|
|
562
560
|
});
|
|
563
561
|
```
|
|
564
562
|
|
|
565
|
-
JS_CODE
|
|
563
|
+
App Function / Workflow / Automation / JS_CODE 在完成业务鉴权并解析接收人后调用:`ctx.notification.sendByType({ ... })`。允许的 channels:`inapp` / `email` / `dingding` / `wechat` / `thirdparty_todo`。完整规则见 [`notifications.md`](notifications.md)。
|
|
566
564
|
|
|
567
|
-
|
|
565
|
+
页面可做钉钉卡片预览:`sdk.notification.previewDingTalk({ notificationType, payload })`;CLI 为 `openxiangda notification dingding-preview`。实际发送必须位于具名 App Function 等可信运行时中。
|
|
568
566
|
|
|
569
567
|
## 4. App Function — `src/resources/functions/<functionCode>.json` + `src/functions/<functionCode>/index.ts`
|
|
570
568
|
|
|
@@ -157,7 +157,7 @@ An environment-managed release may intentionally replace complete Function/Autom
|
|
|
157
157
|
|
|
158
158
|
For an audited Runtime source rollback, managed `release ship`, recovery `release deploy`, and `release promote` accept `--allow-runtime-rollback --reason "..."` with a reason of at least 8 characters. This pair is scoped only to `runtime-stage` and never reaches resource stages or `app-finalize`. Ship freezes it in `ship.json`; production confirmation inherits it automatically. The default remains fail closed.
|
|
159
159
|
|
|
160
|
-
`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/
|
|
160
|
+
`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/Page children, resumes from a private execution journal, finalizes once, and releases the lease. A legacy Page step runs with `OPENXIANGDA_PAGE_STAGE_ONLY=1`; its environment and staged kind are part of the plan hash, and missing PageRelease evidence blocks Root finalize. Custom page publishers must honor that environment and invoke the current package CLI. For an approved historical-lineage catch-up, add `--adopt-online-baseline --adoption-reason "..."`; the pair reaches only exact `resource publish --only/--code` stages, never Form ensure, Page, or App finalize. A deliberate Backend manifest replacement may add `--replace-manifest --reason "..."`; the pair reaches only the unique exact Backend stage. Invalid reasons or empty/incompatible scopes fail before lease acquisition. Managed applications use two-phase `release ship` and its deployment-scoped journal. Individual candidate/deploy/test/fail/promote commands are recovery/diagnostic primitives. `release fail --deployment <id> --message <text> [--code <code>] [--details-json <JSON|file>] --environment preproduction` records an audited UAT failure only after verifying the deployment belongs to the selected preproduction environment; production targets and mismatched deployment identities fail before the write.
|
|
161
161
|
|
|
162
162
|
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.
|
|
163
163
|
|
|
@@ -44,7 +44,7 @@ openxiangda workspace publish --profile <name> --page <pageCode>
|
|
|
44
44
|
- ❌ Raw native form controls in AI-authored page code (`<input>`, `<select>`, `<textarea>`, file inputs, hand-written pickers/uploaders). Use platform components or `antd` / `antd-mobile`.
|
|
45
45
|
- ❌ Embed a single `FormProvider` field component temporarily; navigate to a full standard form page or render a complete `StandardFormPage` instead.
|
|
46
46
|
- ❌ Reuse one form UI for both PC and mobile without verifying overlay / picker / bottom-sheet behavior on both viewports.
|
|
47
|
-
- ❌
|
|
47
|
+
- ❌ Send notifications directly from a page or hardcode notification/platform API URLs. Pages invoke a named App Function with business identifiers; trusted runtime code uses `ctx.notification` and `src/resources/notifications/` declarations.
|
|
48
48
|
|
|
49
49
|
## CLI Flow
|
|
50
50
|
|
|
@@ -133,7 +133,7 @@ Read these references only when editing page code:
|
|
|
133
133
|
- Store live `pageId`, `routeKey`, and `legacyFormUuid` under the current profile only.
|
|
134
134
|
- Use `openxiangda/runtime` for platform data access instead of hardcoding backend URLs in page code.
|
|
135
135
|
- 家校关系必须通过 `sdk.organization.schoolContact` / `ctx.organization.schoolContact` 读取;班主任班级使用 `teachers.list` 的 `isHeadTeacher`,不要用全局角色推断具体班级,也不要直连钉钉、查询系统表或用同班成员推断亲属关系。
|
|
136
|
-
- For reminders, alerts, and business messages, declare `src/resources/notifications/` first
|
|
136
|
+
- For reminders, alerts, and business messages, declare `src/resources/notifications/` first. Pages call a named App Function through `sdk.function.invoke`; the function authorizes the business action, derives recipients and variables, and sends through trusted `ctx.notification`. Do not hardcode notification API URLs or pass effective recipients/content/channels from the page.
|
|
137
137
|
- For backend business logic shared by pages, automations, or workflows, declare an App Function and call `sdk.function.invoke`; do not duplicate the same multi-form query/connector/notification orchestration in page code.
|
|
138
138
|
- Before hand-writing mature UI behavior, consult `references/component-guide.md` and use established libraries: platform components for platform data fields, antd/antd-mobile for controls and overlays, ECharts for charts, GSAP for complex animation timelines, and maintained packages such as dnd-kit for drag/drop. Do not rebuild mature controls with raw DOM/native inputs.
|
|
139
139
|
- Named imports from `@ant-design/icons` are supported by the `openxiangda` workspace build proxy, which enumerates icon module exports at runtime.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.271",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -233,43 +233,42 @@
|
|
|
233
233
|
},
|
|
234
234
|
"openxiangdaRelease": {
|
|
235
235
|
"schemaVersion": "openxiangda.release-notes/v1",
|
|
236
|
-
"version": "1.0.
|
|
237
|
-
"title": "OpenXiangda V1
|
|
236
|
+
"version": "1.0.271",
|
|
237
|
+
"title": "OpenXiangda V1 Legacy Atomic Mixed Releases",
|
|
238
238
|
"status": "reviewed",
|
|
239
|
-
"summary": "
|
|
240
|
-
"newFeatures": [
|
|
241
|
-
"新增 src/resources/webhooks/*.json 资源声明、校验、计划、发布、拉取和停用能力。",
|
|
242
|
-
"新增 webhook list/get/create/update/upsert/disable/deliveries/delivery 命令,用于配置和投递诊断。",
|
|
243
|
-
"Webhook Function 获得原始 Body、原始 Query、重复参数数组、安全 Headers、投递 ID 和幂等键。"
|
|
244
|
-
],
|
|
239
|
+
"summary": "Legacy PageRelease 应用现在可以在一次受审发布中同时暂存 FormRelease、BackendRelease 和 PageRelease,并通过一个 Root AppRelease 原子激活。",
|
|
240
|
+
"newFeatures": [],
|
|
245
241
|
"fixes": [
|
|
246
|
-
"
|
|
247
|
-
"
|
|
242
|
+
"Legacy 发布计划不再把 Form 交给 workspace publish 直接处理,而是生成独立的不可变 FormRelease stage。",
|
|
243
|
+
"BackendRelease 在依赖的 staged Form contract 就绪后执行,legacy Page 继续使用 PageRelease,最后只执行一次 app-finalize。",
|
|
244
|
+
"SDD bundle 和 prepublish 校验对 legacy Form、Backend、Page 混合发布使用同一组确定性命令。",
|
|
245
|
+
"Legacy Page step 自动注入 stage-only 环境并记录 PageRelease staged evidence;缺少证据时禁止 Root finalize。",
|
|
246
|
+
"Legacy release publish 的 manifest replacement 权限只透传给唯一的精确 Backend stage。"
|
|
248
247
|
],
|
|
249
248
|
"affectedUsers": [
|
|
250
|
-
"
|
|
249
|
+
"使用 legacy PageRelease 且同一变更同时包含 Form、Function/Automation 或 Page 的 OpenXiangda V1 应用。"
|
|
251
250
|
],
|
|
252
251
|
"compatibility": {
|
|
253
252
|
"node": ">=18;全局 V2 统一入口需要 >=24",
|
|
254
253
|
"workspaceGenerations": [
|
|
255
254
|
"v1"
|
|
256
255
|
],
|
|
257
|
-
"workspacePolicy": "
|
|
258
|
-
"platformPolicy": "
|
|
256
|
+
"workspacePolicy": "React SPA 发布计划保持兼容;legacy 混合发布会改用 FormRelease + PageRelease + Root AppRelease 的原子编排。",
|
|
257
|
+
"platformPolicy": "目标平台需已支持 FormRelease、PageRelease、BackendRelease 和 atomic staged children;无需数据库迁移。"
|
|
259
258
|
},
|
|
260
259
|
"upgradeSteps": [
|
|
261
|
-
"将 V1 项目依赖锁定到 openxiangda@1.0.
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
260
|
+
"将 V1 项目依赖锁定到 openxiangda@1.0.271 或运行同代 update install。",
|
|
261
|
+
"对待发布变更重新生成 mainline release bundle,或至少重新执行 sdd verify --changed --stage prepublish。",
|
|
262
|
+
"确认 legacy 计划中的 workspace publish 只包含 pages/*,Form 使用 resource publish form-setting 单独暂存。",
|
|
263
|
+
"自定义 legacy 页面发布脚本必须遵守 OPENXIANGDA_PAGE_STAGE_ONLY=1,并通过当前包内 CLI 登记 staged PageRelease。",
|
|
264
|
+
"继续通过 release publish 执行一次性 Root finalize,不要顺序激活 Form、Backend 或 Page。"
|
|
265
265
|
],
|
|
266
266
|
"knownLimitations": [
|
|
267
|
-
"
|
|
268
|
-
"
|
|
269
|
-
"投递语义为 at-least-once,应用必须用 input.idempotencyKey 原子保护业务副作用。"
|
|
267
|
+
"1.0.270 生成的错误计划不会被原地信任;必须使用 1.0.271 重新计算并通过 SDD gate。",
|
|
268
|
+
"该补丁不改变网络链路;独立的 ECONNRESET 仍需按请求时间和 ingress 日志排查。"
|
|
270
269
|
],
|
|
271
270
|
"issues": [],
|
|
272
|
-
"sha256": "
|
|
273
|
-
"url": "https://github.com/1377385356/openxiangda-v1/releases/tag/v1.0.
|
|
271
|
+
"sha256": "86b676d7d8382b8618aad501cd8cf576d95ee0a6c29b8435b9b2edc22b43eee9",
|
|
272
|
+
"url": "https://github.com/1377385356/openxiangda-v1/releases/tag/v1.0.271"
|
|
274
273
|
}
|
|
275
274
|
}
|