openxiangda 1.0.115 → 1.0.117
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/cli.js +66 -18
- package/lib/http.js +19 -0
- package/openxiangda-skills/references/workflow-v3.md +3 -1
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +1 -0
- package/package.json +2 -1
- package/packages/sdk/dist/runtime/index.cjs +38 -3
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.mjs +38 -3
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +36 -1
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.mjs +36 -1
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/packages/sdk/dist/workflow/index.cjs +2 -1
- package/packages/sdk/dist/workflow/index.cjs.map +1 -1
- package/packages/sdk/dist/workflow/index.mjs +2 -1
- package/packages/sdk/dist/workflow/index.mjs.map +1 -1
package/lib/cli.js
CHANGED
|
@@ -5338,6 +5338,11 @@ const RESOURCE_TYPE_ALIASES = new Map([
|
|
|
5338
5338
|
['formsettings', 'formSettings'],
|
|
5339
5339
|
]);
|
|
5340
5340
|
|
|
5341
|
+
const RESOURCE_TYPE_ALIAS_GROUPS = new Map([
|
|
5342
|
+
['permission', ['pagePermissionGroups', 'formPermissionGroups']],
|
|
5343
|
+
['permissions', ['pagePermissionGroups', 'formPermissionGroups']],
|
|
5344
|
+
]);
|
|
5345
|
+
|
|
5341
5346
|
function getResourceTypeFilters(positional = [], flags = {}) {
|
|
5342
5347
|
const rawValues = [
|
|
5343
5348
|
...positional,
|
|
@@ -5355,9 +5360,17 @@ function getResourceTypeFilters(positional = [], flags = {}) {
|
|
|
5355
5360
|
const filters = new Set();
|
|
5356
5361
|
for (const rawType of rawValues) {
|
|
5357
5362
|
const normalized = normalizeResourceTypeAlias(rawType);
|
|
5363
|
+
const group = RESOURCE_TYPE_ALIAS_GROUPS.get(normalized);
|
|
5364
|
+
if (group) {
|
|
5365
|
+
for (const key of group) filters.add(key);
|
|
5366
|
+
continue;
|
|
5367
|
+
}
|
|
5358
5368
|
const key = RESOURCE_TYPE_ALIASES.get(normalized);
|
|
5359
5369
|
if (!key) {
|
|
5360
|
-
fail(
|
|
5370
|
+
fail(
|
|
5371
|
+
`未知资源类型: ${rawType}。常用类型包括 workflow, notification, route, menu, role, ` +
|
|
5372
|
+
`page-permission-group, form-permission-group, public-access。`
|
|
5373
|
+
);
|
|
5361
5374
|
}
|
|
5362
5375
|
filters.add(key);
|
|
5363
5376
|
}
|
|
@@ -9804,6 +9817,42 @@ function isUnauthorized(error) {
|
|
|
9804
9817
|
);
|
|
9805
9818
|
}
|
|
9806
9819
|
|
|
9820
|
+
function isTransientNetworkError(error) {
|
|
9821
|
+
const message = String(error?.message || error || '').toLowerCase();
|
|
9822
|
+
return (
|
|
9823
|
+
message.includes('und_err_connect_timeout') ||
|
|
9824
|
+
message.includes('connect_timeout') ||
|
|
9825
|
+
message.includes('econnreset') ||
|
|
9826
|
+
message.includes('socket hang up') ||
|
|
9827
|
+
message.includes('network socket disconnected') ||
|
|
9828
|
+
message.includes('fetch failed')
|
|
9829
|
+
);
|
|
9830
|
+
}
|
|
9831
|
+
|
|
9832
|
+
function requestMethodAllowsTransientRetry(options = {}) {
|
|
9833
|
+
const method = String(options.method || 'GET').toUpperCase();
|
|
9834
|
+
return method === 'GET' || method === 'HEAD';
|
|
9835
|
+
}
|
|
9836
|
+
|
|
9837
|
+
async function retryTransientFetch(fn, options = {}) {
|
|
9838
|
+
const retries = Number.isFinite(Number(options.retries))
|
|
9839
|
+
? Number(options.retries)
|
|
9840
|
+
: 2;
|
|
9841
|
+
let lastError;
|
|
9842
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
9843
|
+
try {
|
|
9844
|
+
return await fn(attempt);
|
|
9845
|
+
} catch (error) {
|
|
9846
|
+
lastError = error;
|
|
9847
|
+
if (attempt >= retries || !isTransientNetworkError(error)) {
|
|
9848
|
+
throw error;
|
|
9849
|
+
}
|
|
9850
|
+
await sleep(Math.min(500 * Math.pow(2, attempt), 3000));
|
|
9851
|
+
}
|
|
9852
|
+
}
|
|
9853
|
+
throw lastError;
|
|
9854
|
+
}
|
|
9855
|
+
|
|
9807
9856
|
async function requestWithAuth(config, profileName, apiPath, options = {}) {
|
|
9808
9857
|
const resolved = getProfile(config, profileName);
|
|
9809
9858
|
const profile = resolved.profile;
|
|
@@ -9811,22 +9860,25 @@ async function requestWithAuth(config, profileName, apiPath, options = {}) {
|
|
|
9811
9860
|
fail(`profile ${resolved.profileName} 未登录,请先执行 openxiangda login --profile ${resolved.profileName}`);
|
|
9812
9861
|
}
|
|
9813
9862
|
const { strictEnvelope, ...requestOptions } = options;
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
const payload = await requestJson(profile.baseUrl, apiPath, {
|
|
9863
|
+
const send = () =>
|
|
9864
|
+
requestJson(profile.baseUrl, apiPath, {
|
|
9817
9865
|
...requestOptions,
|
|
9818
9866
|
accessToken: profile.token.accessToken,
|
|
9819
9867
|
});
|
|
9868
|
+
const sendWithRetry = () =>
|
|
9869
|
+
requestMethodAllowsTransientRetry(requestOptions)
|
|
9870
|
+
? retryTransientFetch(send, requestOptions)
|
|
9871
|
+
: send();
|
|
9872
|
+
|
|
9873
|
+
try {
|
|
9874
|
+
const payload = await sendWithRetry();
|
|
9820
9875
|
return strictEnvelope ? unwrapStrictApi(payload) : unwrapApi(payload);
|
|
9821
9876
|
} catch (error) {
|
|
9822
9877
|
if (!isUnauthorized(error) || !profile.token?.refreshToken) {
|
|
9823
9878
|
throw error;
|
|
9824
9879
|
}
|
|
9825
9880
|
await refreshProfile(config, resolved.profileName);
|
|
9826
|
-
const payload = await
|
|
9827
|
-
...requestOptions,
|
|
9828
|
-
accessToken: profile.token.accessToken,
|
|
9829
|
-
});
|
|
9881
|
+
const payload = await sendWithRetry();
|
|
9830
9882
|
return strictEnvelope ? unwrapStrictApi(payload) : unwrapApi(payload);
|
|
9831
9883
|
}
|
|
9832
9884
|
}
|
|
@@ -9837,28 +9889,24 @@ async function requestFormWithAuth(config, profileName, apiPath, formDataFactory
|
|
|
9837
9889
|
if (!profile.token?.accessToken) {
|
|
9838
9890
|
fail(`profile ${resolved.profileName} 未登录,请先执行 openxiangda login --profile ${resolved.profileName}`);
|
|
9839
9891
|
}
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
const payload = await requestForm(
|
|
9892
|
+
const send = () =>
|
|
9893
|
+
requestForm(
|
|
9843
9894
|
profile.baseUrl,
|
|
9844
9895
|
apiPath,
|
|
9845
9896
|
formDataFactory(),
|
|
9846
9897
|
profile.token.accessToken,
|
|
9847
9898
|
options
|
|
9848
9899
|
);
|
|
9900
|
+
|
|
9901
|
+
try {
|
|
9902
|
+
const payload = await retryTransientFetch(send, options);
|
|
9849
9903
|
return unwrapApi(payload);
|
|
9850
9904
|
} catch (error) {
|
|
9851
9905
|
if (!isUnauthorized(error) || !profile.token?.refreshToken) {
|
|
9852
9906
|
throw error;
|
|
9853
9907
|
}
|
|
9854
9908
|
await refreshProfile(config, resolved.profileName);
|
|
9855
|
-
const payload = await
|
|
9856
|
-
profile.baseUrl,
|
|
9857
|
-
apiPath,
|
|
9858
|
-
formDataFactory(),
|
|
9859
|
-
profile.token.accessToken,
|
|
9860
|
-
options
|
|
9861
|
-
);
|
|
9909
|
+
const payload = await retryTransientFetch(send, options);
|
|
9862
9910
|
return unwrapApi(payload);
|
|
9863
9911
|
}
|
|
9864
9912
|
}
|
package/lib/http.js
CHANGED
|
@@ -2,6 +2,8 @@ const { formatFetchError, maskText } = require('./utils');
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
|
4
4
|
|
|
5
|
+
configureFetchDispatcher(DEFAULT_REQUEST_TIMEOUT_MS);
|
|
6
|
+
|
|
5
7
|
async function requestJson(baseUrl, apiPath, options = {}) {
|
|
6
8
|
if (typeof fetch !== 'function') {
|
|
7
9
|
throw new Error('当前 Node.js 版本不支持 fetch,请使用 Node.js 18 或更高版本');
|
|
@@ -77,3 +79,20 @@ function isAbortError(error) {
|
|
|
77
79
|
module.exports = {
|
|
78
80
|
requestJson,
|
|
79
81
|
};
|
|
82
|
+
|
|
83
|
+
function configureFetchDispatcher(timeoutMs) {
|
|
84
|
+
try {
|
|
85
|
+
// Node fetch uses undici internally. Setting the global dispatcher prevents
|
|
86
|
+
// large resource operations from failing on undici's 10s connect default.
|
|
87
|
+
const { Agent, setGlobalDispatcher } = require('undici');
|
|
88
|
+
setGlobalDispatcher(
|
|
89
|
+
new Agent({
|
|
90
|
+
connect: {
|
|
91
|
+
timeout: timeoutMs,
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
} catch {
|
|
96
|
+
// Keep working on runtimes where undici is not directly importable.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -24,7 +24,7 @@ export default defineWorkflow({
|
|
|
24
24
|
flow.action.transfer(),
|
|
25
25
|
flow.action.return(),
|
|
26
26
|
],
|
|
27
|
-
returnPolicy: { scopeType: "
|
|
27
|
+
returnPolicy: { scopeType: "initiator", resubmitMode: "resume_current" },
|
|
28
28
|
fieldPermissions: [{ fieldId: "amount", fieldBehavior: "READONLY" }],
|
|
29
29
|
});
|
|
30
30
|
const sync = flow.functionCall("sync_budget", {
|
|
@@ -108,6 +108,8 @@ Runtime pages should not guess buttons. Use `sdk.process.resolveCapabilities(...
|
|
|
108
108
|
|
|
109
109
|
For delayed approval start, do not submit a process form and then delete/recreate records. Save the process-form instance without starting workflow via `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`. Later start approval on that same `formInstId` with `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`.
|
|
110
110
|
|
|
111
|
+
For return-to-initiator workflows, prefer `returnPolicy: { scopeType: "initiator", resubmitMode: "resume_current" }` or `flow.action.returnToInitiator()`. The runtime capabilities protocol exposes `resubmit` only on pending `originator_return` tasks. If a user returns to a previous approval node, continuing the flow is a normal `approve` action on that returned approval task.
|
|
112
|
+
|
|
111
113
|
Minimum shape:
|
|
112
114
|
|
|
113
115
|
```json
|
|
@@ -25,6 +25,7 @@ Create a workflow only when the scenario has **real approval semantics**: approv
|
|
|
25
25
|
- ✅ New AI-authored workflows: prefer **semantic DSL** `src/workflows/<code>/workflow.ts` using `defineWorkflow`; compile it to v3 designer JSON with `openxiangda workflow compile`.
|
|
26
26
|
- ✅ Runtime workflow pages: use `sdk.process.resolveCapabilities(...)` or `ProcessActionBar` / `ProcessTimeline` from `openxiangda/runtime/react`; do not hard-code buttons.
|
|
27
27
|
- ✅ Delayed approval start: save the process-form record first with `StandardFormPage submitBehavior="save-draft"` or `sdk.form.create({ formUuid, data, saveAsDraft: true, startProcess: false })`, then start approval in place with `sdk.process.startFromExistingInstance({ formUuid, formInstId })` or `StandardFormPage submitBehavior="start-existing-process"`.
|
|
28
|
+
- ✅ Return-to-initiator flows: use `returnPolicy: { scopeType: "initiator", resubmitMode: "resume_current" | "replay" }` or `flow.action.returnToInitiator()`. The runtime capabilities protocol exposes `resubmit` only for pending `originator_return` tasks; return to a previous approval node continues through the normal `approve` action.
|
|
28
29
|
- ✅ Reusable backend business logic: prefer **App Function** (`src/functions/<functionCode>/index.ts` + `src/resources/functions/<functionCode>.json`), then call it from page `sdk.function.invoke` or automation/workflow `function_call`.
|
|
29
30
|
- ✅ JS_CODE V2 trusted_node: source in TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. Run `pnpm build-js-code --script <code>` (validates with `tsc` first).
|
|
30
31
|
- ✅ Use `trigger_v2` for new automation triggers; CLI fills root `appType` / `formUuid` from active profile when `--form-code` is provided.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.117",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
"qrcode-terminal": "^0.12.0",
|
|
118
118
|
"tailwindcss": "^3.4.17",
|
|
119
119
|
"tsx": "^4.20.0",
|
|
120
|
+
"undici": "^6.27.0",
|
|
120
121
|
"vite": "^6.0.0"
|
|
121
122
|
},
|
|
122
123
|
"peerDependencies": {
|
|
@@ -2560,6 +2560,33 @@ init_cjs_shims();
|
|
|
2560
2560
|
|
|
2561
2561
|
// packages/sdk/src/runtime/core/fetch.ts
|
|
2562
2562
|
init_cjs_shims();
|
|
2563
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2564
|
+
var isTransientFetchError = (error) => {
|
|
2565
|
+
const anyError = error;
|
|
2566
|
+
const message7 = String(anyError?.message || error || "").toLowerCase();
|
|
2567
|
+
const causeCode = String(anyError?.cause?.code || "").toLowerCase();
|
|
2568
|
+
return causeCode === "und_err_connect_timeout" || message7.includes("und_err_connect_timeout") || message7.includes("connect_timeout") || message7.includes("econnreset") || message7.includes("socket hang up") || message7.includes("network socket disconnected") || message7.includes("fetch failed");
|
|
2569
|
+
};
|
|
2570
|
+
var fetchWithTransientRetry = async (fetchImpl, input, init, options = {}) => {
|
|
2571
|
+
if (!options.enabled) {
|
|
2572
|
+
return fetchImpl(input, init);
|
|
2573
|
+
}
|
|
2574
|
+
const retries = Number.isFinite(Number(options.retries)) ? Number(options.retries) : 2;
|
|
2575
|
+
const baseDelayMs = Number.isFinite(Number(options.baseDelayMs)) ? Number(options.baseDelayMs) : 250;
|
|
2576
|
+
let lastError;
|
|
2577
|
+
for (let attempt = 0; attempt <= retries; attempt += 1) {
|
|
2578
|
+
try {
|
|
2579
|
+
return await fetchImpl(input, init);
|
|
2580
|
+
} catch (error) {
|
|
2581
|
+
lastError = error;
|
|
2582
|
+
if (attempt >= retries || !isTransientFetchError(error)) {
|
|
2583
|
+
throw error;
|
|
2584
|
+
}
|
|
2585
|
+
await sleep(Math.min(baseDelayMs * Math.pow(2, attempt), 2e3));
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
throw lastError;
|
|
2589
|
+
};
|
|
2563
2590
|
var createBoundFetch = (fetchImpl) => {
|
|
2564
2591
|
const baseFetch = fetchImpl || globalThis.fetch;
|
|
2565
2592
|
return ((input, init) => baseFetch.call(globalThis, input, init));
|
|
@@ -28079,7 +28106,7 @@ var import_react162 = __toESM(require("react"));
|
|
|
28079
28106
|
|
|
28080
28107
|
// node_modules/antd-mobile/es/utils/sleep.js
|
|
28081
28108
|
init_cjs_shims();
|
|
28082
|
-
var
|
|
28109
|
+
var sleep2 = (time) => new Promise((resolve) => setTimeout(resolve, time));
|
|
28083
28110
|
|
|
28084
28111
|
// node_modules/antd-mobile/es/components/pull-to-refresh/pull-to-refresh.js
|
|
28085
28112
|
var classPrefix63 = `adm-pull-to-refresh`;
|
|
@@ -28152,7 +28179,7 @@ var PullToRefresh = (p) => {
|
|
|
28152
28179
|
throw e2;
|
|
28153
28180
|
}
|
|
28154
28181
|
if (props.completeDelay > 0) {
|
|
28155
|
-
yield
|
|
28182
|
+
yield sleep2(props.completeDelay);
|
|
28156
28183
|
}
|
|
28157
28184
|
reset();
|
|
28158
28185
|
});
|
|
@@ -34333,6 +34360,12 @@ var normalizeMethod2 = (method4) => {
|
|
|
34333
34360
|
const value = String(method4 || "get").toUpperCase();
|
|
34334
34361
|
return ["GET", "POST", "PUT", "DELETE", "PATCH"].includes(value) ? value : "GET";
|
|
34335
34362
|
};
|
|
34363
|
+
var shouldRetryTransportRequest = (payload) => {
|
|
34364
|
+
const method4 = normalizeMethod2(payload?.method);
|
|
34365
|
+
if (method4 === "GET") return true;
|
|
34366
|
+
const path = String(payload?.path || "").split("?")[0];
|
|
34367
|
+
return method4 === "POST" && path === "/workflow/capabilities/resolve";
|
|
34368
|
+
};
|
|
34336
34369
|
var normalizeCssIsolation2 = (value) => {
|
|
34337
34370
|
if (value === "namespace" || value === "shadow" || value === "none") {
|
|
34338
34371
|
return value;
|
|
@@ -34393,11 +34426,13 @@ var createBrowserPageBridge = (options = {}) => {
|
|
|
34393
34426
|
body = JSON.stringify(payload.body);
|
|
34394
34427
|
}
|
|
34395
34428
|
}
|
|
34396
|
-
const response = await fetchImpl
|
|
34429
|
+
const response = await fetchWithTransientRetry(fetchImpl, url2, {
|
|
34397
34430
|
method: normalizeMethod2(payload.method),
|
|
34398
34431
|
headers,
|
|
34399
34432
|
body,
|
|
34400
34433
|
credentials: "include"
|
|
34434
|
+
}, {
|
|
34435
|
+
enabled: shouldRetryTransportRequest(payload)
|
|
34401
34436
|
});
|
|
34402
34437
|
return parseJsonResponse(response);
|
|
34403
34438
|
};
|