openxiangda 1.0.81 → 1.0.82

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 CHANGED
@@ -128,6 +128,82 @@ OpenXiangda 使用普通用户登录 token,不需要 AK/SK。
128
128
  JS_CODE V2 使用 trusted_node;AI 源码必须写在 src/js-code-nodes/<scriptCode>/index.ts,代码自动化源码写在 src/automations/<resourceCode>/index.ts。definition-json 中的 sourceFile.localPath 会在 validate/create/publish 时先 TS 校验、再构建并上传为快照。`);
129
129
  }
130
130
 
131
+ const SUBCOMMAND_BOOLEAN_FLAGS = new Set([
132
+ '--dry-run',
133
+ '--enabled',
134
+ '--force',
135
+ '--help',
136
+ '--include-sourcemaps',
137
+ '--json',
138
+ '--legacy-form-bundle',
139
+ '--no-activate',
140
+ '--no-build',
141
+ '--no-runtime-aliases',
142
+ '--prune',
143
+ '--publish',
144
+ '--published',
145
+ '--redact',
146
+ '--resources',
147
+ '--skip-resources',
148
+ '--strict',
149
+ '--summary',
150
+ '--unpublish',
151
+ '--yes',
152
+ '-h',
153
+ ]);
154
+
155
+ function parseSubcommandArgs(args) {
156
+ const leadingFlags = [];
157
+ let index = 0;
158
+ while (index < args.length) {
159
+ const item = args[index];
160
+ if (!isFlagToken(item)) break;
161
+ if (item === '--') {
162
+ index += 1;
163
+ break;
164
+ }
165
+ leadingFlags.push(item);
166
+ if (hasInlineFlagValue(item) || isSubcommandBooleanFlag(item)) {
167
+ index += 1;
168
+ continue;
169
+ }
170
+ const next = args[index + 1];
171
+ if (next && !isFlagToken(next)) {
172
+ leadingFlags.push(next);
173
+ index += 2;
174
+ continue;
175
+ }
176
+ index += 1;
177
+ }
178
+ return {
179
+ subcommand: args[index],
180
+ rest: [...leadingFlags, ...args.slice(index + 1)],
181
+ };
182
+ }
183
+
184
+ function isFlagToken(value) {
185
+ return typeof value === 'string' && value.startsWith('-');
186
+ }
187
+
188
+ function hasInlineFlagValue(value) {
189
+ return typeof value === 'string' && value.startsWith('--') && value.includes('=');
190
+ }
191
+
192
+ function isSubcommandBooleanFlag(value) {
193
+ return SUBCOMMAND_BOOLEAN_FLAGS.has(value) || /^--no-/.test(String(value || ''));
194
+ }
195
+
196
+ function wantsSubcommandHelp(subcommand, flags) {
197
+ return (
198
+ !subcommand ||
199
+ subcommand === 'help' ||
200
+ subcommand === '--help' ||
201
+ subcommand === '-h' ||
202
+ flags.help ||
203
+ flags.h
204
+ );
205
+ }
206
+
131
207
  async function update(args) {
132
208
  const requestedSubcommand = args[0] && !args[0].startsWith('--') ? args[0] : 'check';
133
209
  const parsedArgs = requestedSubcommand === args[0] ? args.slice(1) : args;
@@ -1625,8 +1701,12 @@ async function menu(args) {
1625
1701
  }
1626
1702
 
1627
1703
  async function workflow(args) {
1628
- const [subcommand, ...rest] = args;
1704
+ const { subcommand, rest } = parseSubcommandArgs(args);
1629
1705
  const { flags, positional } = parseArgs(rest);
1706
+ if (wantsSubcommandHelp(subcommand, flags)) {
1707
+ print('用法: openxiangda workflow list|create|bind|pull|publish|delete|validate [--profile name] [--json]');
1708
+ return;
1709
+ }
1630
1710
  const config = loadConfig();
1631
1711
  const profileName = flags.profile || config.currentProfile;
1632
1712
 
@@ -1798,8 +1878,12 @@ async function workflow(args) {
1798
1878
  }
1799
1879
 
1800
1880
  async function automation(args) {
1801
- const [subcommand, ...rest] = args;
1881
+ const { subcommand, rest } = parseSubcommandArgs(args);
1802
1882
  const { flags, positional } = parseArgs(rest);
1883
+ if (wantsSubcommandHelp(subcommand, flags)) {
1884
+ print('用法: openxiangda automation list|create|bind|pull|executions|logs|diagnose|publish|unpublish|enable|disable|delete|validate|cron-validate [--profile name] [--json]');
1885
+ return;
1886
+ }
1803
1887
  const config = loadConfig();
1804
1888
  const profileName = flags.profile || config.currentProfile;
1805
1889
 
@@ -2002,8 +2086,12 @@ async function automation(args) {
2002
2086
  }
2003
2087
 
2004
2088
  async function dataView(args) {
2005
- const [subcommand, ...rest] = args;
2089
+ const { subcommand, rest } = parseSubcommandArgs(args);
2006
2090
  const { flags, positional } = parseArgs(rest);
2091
+ if (wantsSubcommandHelp(subcommand, flags)) {
2092
+ print('用法: openxiangda data-view list|status|refresh|query|stats <dataViewCode> [--profile name] [--json]');
2093
+ return;
2094
+ }
2007
2095
  const config = loadConfig();
2008
2096
  const profileName = flags.profile || config.currentProfile;
2009
2097
  const target = getWorkspaceTarget(config, profileName, flags);
@@ -2580,8 +2668,12 @@ async function settings(args) {
2580
2668
  }
2581
2669
 
2582
2670
  async function resource(args) {
2583
- const [subcommand, ...rest] = args;
2671
+ const { subcommand, rest } = parseSubcommandArgs(args);
2584
2672
  const { flags } = parseArgs(rest);
2673
+ if (wantsSubcommandHelp(subcommand, flags)) {
2674
+ print('用法: openxiangda resource validate|plan|publish|pull|typegen [--profile name] [--json]');
2675
+ return;
2676
+ }
2585
2677
  const config = loadConfig();
2586
2678
  const profileName = flags.profile || config.currentProfile;
2587
2679
 
@@ -2634,8 +2726,12 @@ async function resource(args) {
2634
2726
  }
2635
2727
 
2636
2728
  async function runtime(args) {
2637
- const [subcommand, ...rest] = args;
2729
+ const { subcommand, rest } = parseSubcommandArgs(args);
2638
2730
  const { flags, positional } = parseArgs(rest);
2731
+ if (wantsSubcommandHelp(subcommand, flags)) {
2732
+ print('用法: openxiangda runtime deploy|releases|activate [--profile name] [--json]');
2733
+ return;
2734
+ }
2639
2735
  const config = loadConfig();
2640
2736
  const profileName = flags.profile || config.currentProfile;
2641
2737
  const target = getWorkspaceTarget(config, profileName, flags);
@@ -3523,13 +3619,7 @@ function readResourceItemsFromFile(filePath, spec) {
3523
3619
  __error: '资源项必须是 JSON object',
3524
3620
  };
3525
3621
  }
3526
- const code =
3527
- item.code ||
3528
- item.resourceCode ||
3529
- item.methodName ||
3530
- (spec.key === 'notifications' ? inferNotificationResourceCode(item) : undefined) ||
3531
- item.formCode ||
3532
- (values.length === 1 ? defaultCode : undefined);
3622
+ const code = inferResourceCode(item, spec, values.length === 1 ? defaultCode : undefined);
3533
3623
  return {
3534
3624
  ...item,
3535
3625
  ...(code ? { code } : {}),
@@ -3540,6 +3630,19 @@ function readResourceItemsFromFile(filePath, spec) {
3540
3630
  });
3541
3631
  }
3542
3632
 
3633
+ function inferResourceCode(item, spec, defaultCode) {
3634
+ if (item.code || item.resourceCode || item.methodName) {
3635
+ return item.code || item.resourceCode || item.methodName;
3636
+ }
3637
+ if (spec.key === 'notifications') {
3638
+ return inferNotificationResourceCode(item) || defaultCode;
3639
+ }
3640
+ if (spec.key === 'formSettings') {
3641
+ return item.formCode || defaultCode;
3642
+ }
3643
+ return defaultCode;
3644
+ }
3645
+
3543
3646
  function inferNotificationResourceCode(item) {
3544
3647
  const resourceType = normalizeNotificationResourceType(item);
3545
3648
  if (resourceType === 'template') return item.code || item.templateCode;
package/lib/utils.js CHANGED
@@ -28,6 +28,10 @@ function parseArgs(argv) {
28
28
  const positional = [];
29
29
  for (let index = 0; index < argv.length; index += 1) {
30
30
  const item = argv[index];
31
+ if (item === '-h') {
32
+ flags.h = true;
33
+ continue;
34
+ }
31
35
  if (!item.startsWith('--')) {
32
36
  positional.push(item);
33
37
  continue;
@@ -147,7 +147,9 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
147
147
  - Run write commands that update `.openxiangda/state.json` sequentially within the same workspace. Read-only commands can run in parallel.
148
148
  - JS_CODE is backend-executed workflow/automation logic, not frontend page code. Use it when logic must run after a backend trigger such as fixed cron schedules, form date-field schedules, form submit/update/delete/field-change events, or workflow approval/process events.
149
149
  - Use JS_CODE for node-local cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and backend trigger orchestration. For logic that pages, automations, and workflows should reuse through a stable backend entry, prefer App Function. Do not use JS_CODE for simple UI interactions, ordinary form validation, or display-only page behavior.
150
- - For workflow/automation JS_CODE nodes, prefer V2 `runtimeMode: "trusted_node"`. AI-authored source must be TypeScript under `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. `pnpm build-js-code --script <scriptCode>` runs TypeScript validation before bundling, and `sourceFile.localPath` should point to the TS source; the CLI builds, uploads, and replaces it with snapshot metadata during validate/create.
150
+ - For workflow/automation JS_CODE nodes, prefer V2 `runtimeMode: "trusted_node"`. AI-authored source must be TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. `pnpm build-js-code --script <scriptCode>` runs TypeScript validation before bundling, and `sourceFile.localPath` should point to the TS source; the CLI builds, uploads, and replaces it with snapshot metadata during validate/create.
151
+ - Form permission group resources must have stable local codes. Use unique `code` values such as `ticket_reporter_view` and `ticket_repairer_view`; do not rely on the form code when one form has multiple groups.
152
+ - Workflow resources can be created as drafts. After resource publish, verify `openxiangda workflow list --profile <name> --json` and run `openxiangda workflow publish <workflowCode> --profile <name>` until `isPublished: true` is visible.
151
153
 
152
154
  ## Subskills
153
155
 
@@ -73,6 +73,8 @@ Do not insult the person. Criticize the proposal and explain the technical conse
73
73
  | Live view for unbounded heavy joins | Slow runtime queries | Materialized view with scheduled refresh and indexes |
74
74
  | JS_CODE for reusable business service | Logic gets duplicated in graph nodes | App Function for shared backend logic; JS_CODE only for node-local trigger logic |
75
75
  | Raw native form controls | Inconsistent with platform runtime and validation | OpenXiangda platform components first, Ant Design wrappers second |
76
+ | Multiple form permission groups without stable local codes | CLI state and platform `resourceCode` cannot reliably distinguish groups on the same form | Give every group a unique `code`, for example `ticket_reporter_view` and `ticket_repairer_view` |
77
+ | Assuming resource publish means workflow is active | Workflow resources may exist as drafts until explicitly published | Verify `workflow list` shows `isPublished: true`; run `workflow publish <workflowCode>` when needed |
76
78
 
77
79
  ## Question Gates
78
80
 
@@ -188,6 +190,7 @@ For simple CRUD/admin lists, design can proceed with the appropriate OpenXiangda
188
190
  - App roles use stable local codes.
189
191
  - Page permission groups control entry/menu visibility.
190
192
  - Form permission groups enforce data access.
193
+ - Form permission group resources use unique stable `code` values. Do not use the same form code for multiple groups.
191
194
  - Field permissions hide sensitive fields where backend support exists.
192
195
  - Frontend-only button hiding is UX, not security.
193
196
 
@@ -207,6 +210,8 @@ For simple CRUD/admin lists, design can proceed with the appropriate OpenXiangda
207
210
  - Use automation for backend triggers and schedules.
208
211
  - Use JS_CODE V2 trusted_node for node-local backend logic.
209
212
  - Use App Function for reusable backend services.
213
+ - After publishing workflow resources, verify `workflow list` and record whether `isPublished` is true.
214
+ - In React SPA and classic workspaces, JS_CODE/App Function TypeScript lives under `src/js-code-nodes`, `src/automations`, or `src/functions`; build with `pnpm build-js-code`.
210
215
 
211
216
  ## Final Document Template
212
217
 
@@ -274,6 +279,7 @@ For simple CRUD/admin lists, design can proceed with the appropriate OpenXiangda
274
279
  - Performance/query checks:
275
280
  - Report freshness checks:
276
281
  - Notification/automation checks:
282
+ - Workflow checks: real approval flows show `isPublished: true`.
277
283
 
278
284
  ## 11. Open Questions And Confirmed Assumptions
279
285
 
@@ -287,7 +287,7 @@ Use JS_CODE V2 when the script is local to one automation graph node.
287
287
  }
288
288
  ```
289
289
 
290
- Author source in `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. AI-authored source must be TypeScript. Build with `pnpm build-js-code --script <scriptCode>`; the command runs TypeScript validation before bundling. During validate/create, the CLI uploads the generated bundle, replaces `sourceFile.localPath` with `{ bucketName, objectName, sha256, ... }`, and the backend verifies sha256 before execution.
290
+ Author source in `src/js-code-nodes/<scriptCode>/index.ts`. AI-authored source must be TypeScript. Build with `pnpm build-js-code --script <scriptCode>`; the command runs TypeScript validation before bundling. During validate/create, the CLI uploads the generated bundle, replaces `sourceFile.localPath` with `{ bucketName, objectName, sha256, ... }`, and the backend verifies sha256 before execution.
291
291
 
292
292
  The backend runs the snapshot in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`. Scripts may use `export default async function (ctx) {}`, `module.exports = async (ctx) => {}`, `require`, `process`, `Buffer`, arbitrary HTTP, and `platform.api` for `/openxiangda-api/v1`.
293
293
 
@@ -371,6 +371,8 @@ export default async function (ctx) {
371
371
 
372
372
  ## 10. Form Permission Group — `src/resources/permissions/form-groups/<formCode>/<groupCode>.json`
373
373
 
374
+ 每个表单权限组必须有稳定、唯一的 `code`。同一张表单通常会有多个查看/提交/管理员权限组,不能把 `formCode` 当成权限组 code,否则本地 state 和平台 `resourceCode` 会把不同组混在一起。
375
+
374
376
  ```json
375
377
  {
376
378
  "code": "sales_view",
@@ -455,3 +457,4 @@ export default async function (ctx) {
455
457
  - ❌ 用 data view 代替 `linkedForm` 下拉、单表 CRUD 或写回。**data view 是只读;materialized 有刷新延迟,live 要控制查询边界。**
456
458
  - ❌ 把跨页面/跨流程复用的后端逻辑都塞进 JS_CODE。**优先 App Function,JS_CODE 只做节点内脚本。**
457
459
  - ❌ 在 page 源码里 hardcode `/api/notification-config/*` 或 `/connectors/actions/invoke`。**用 `sdk.notification` / `sdk.connector`。**
460
+ - ❌ 发布 workflow resource 后不检查是否激活。**用 `openxiangda workflow list --profile <name> --json` 确认 `isPublished: true`,需要时再跑 `openxiangda workflow publish <workflowCode>`。**
@@ -133,7 +133,7 @@ File snapshot:
133
133
  }
134
134
  ```
135
135
 
136
- AI-authored JS_CODE source must be TypeScript under `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. When validating or creating, the CLI runs `pnpm build-js-code --script <scriptCode>`, which runs TypeScript validation first, bundles to `dist/js-code-nodes/<scriptCode>/index.cjs`, uploads the bundle, and replaces `sourceFile.localPath` with immutable snapshot metadata. The backend verifies snapshot `sha256`, runs it in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`.
136
+ AI-authored JS_CODE source must be TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`. When validating or creating, the CLI runs `pnpm build-js-code --script <scriptCode>`, which runs TypeScript validation first, bundles to `dist/js-code-nodes/<scriptCode>/index.cjs`, uploads the bundle, and replaces `sourceFile.localPath` with immutable snapshot metadata. The backend verifies snapshot `sha256`, runs it in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores execution logs, and writes the returned value to the node output and `variables.node_<nodeId>`.
137
137
 
138
138
  For reusable backend logic that should be shared by pages, automations, and workflows, prefer an App Function under `src/functions/<functionCode>/index.ts` plus `src/resources/functions/<functionCode>.json`. Workflow graphs that support App Function nodes can call it with:
139
139
 
@@ -24,7 +24,7 @@ Create a workflow only when the scenario has **real approval semantics**: approv
24
24
  - ✅ New AI-authored automations: prefer **code-first** `automation_code_ts` (`src/automations/<code>/index.ts` + `definition.code.json` + `preview.json`).
25
25
  - ✅ New AI-authored workflows that don't need canvas editing: prefer **code-first** `src/workflows/<code>/workflow.ts` using `openxiangda/workflow`.
26
26
  - ✅ 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`.
27
- - ✅ JS_CODE V2 trusted_node: source in TypeScript under `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`. Run `pnpm build-js-code --script <code>` (validates with `tsc` first).
27
+ - ✅ 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).
28
28
  - ✅ Use `trigger_v2` for new automation triggers; CLI fills root `appType` / `formUuid` from active profile when `--form-code` is provided.
29
29
  - ✅ Use logical `workflowCode` / `automationCode` locally; live IDs are profile-isolated under `.openxiangda/state.json`.
30
30
  - ✅ `ctx.logger.debug/info/warn/error(message, data?)` at every important step — inspect via `automation executions` / `automation logs` / `automation diagnose`.
@@ -70,9 +70,10 @@ openxiangda form bind customer --form-uuid FORM_XXX --profile dev
70
70
  4. Publish:
71
71
  ```bash
72
72
  openxiangda workflow publish customer_approval --profile dev
73
+ openxiangda workflow list --profile dev --json
73
74
  ```
74
75
 
75
- Use `workflow pull` to inspect the live definition. Use logical workflow codes locally; never copy a workflow ID from one profile to another.
76
+ Use `workflow pull` to inspect the live definition. Use `workflow list --json` after publishing and confirm the target shows `isPublished: true`; resource publish can create/update a draft without activating it. Use logical workflow codes locally; never copy a workflow ID from one profile to another.
76
77
 
77
78
  ## JS_CODE V2
78
79
 
@@ -86,7 +87,7 @@ For new AI-authored workflows where users do not need canvas editing, prefer com
86
87
 
87
88
  Do not use JS_CODE for simple UI interactions, ordinary form validation, display-only page behavior, or logic that belongs in a normal React code page. For non-trivial backend logic, prefer JS_CODE V2 trusted Node scripts over large inline snippets. AI-authored JS_CODE source must be TypeScript:
88
89
 
89
- 1. Put source in `sy-lowcode-app-workspace/src/js-code-nodes/<scriptCode>/index.ts`.
90
+ 1. Put source in `src/js-code-nodes/<scriptCode>/index.ts`.
90
91
  2. Run `pnpm build-js-code --script <scriptCode>`. This command runs TypeScript validation first and only bundles after `tsc` passes.
91
92
  3. In workflow or automation JSON, use:
92
93
  ```json
@@ -106,7 +107,7 @@ Do not use JS_CODE for simple UI interactions, ordinary form validation, display
106
107
  }
107
108
  ```
108
109
 
109
- The CLI requires `sourceFile.localPath` to point to `src/js-code-nodes/<scriptCode>/index.ts`. During validate/create it runs `pnpm build-js-code --script <scriptCode>`, uploads the generated `dist/js-code-nodes/<scriptCode>/index.cjs` to `/file/js-code-snapshot/upload`, verifies the server snapshot metadata, and replaces it with `{ bucketName, objectName, sha256, ... }`.
110
+ The CLI requires `sourceFile.localPath` to point to TypeScript source in `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. During validate/create it runs `pnpm build-js-code --script <scriptCode>`, uploads the generated bundle to `/file/js-code-snapshot/upload`, verifies the server snapshot metadata, and replaces it with `{ bucketName, objectName, sha256, ... }`.
110
111
 
111
112
  The backend verifies the uploaded snapshot sha256 before execution, runs it in the trusted Node runtime, applies the node timeout (`30000` ms by default), stores console/runtime logs in the execution record, and writes the returned value to the node output and `variables.node_<nodeId>`.
112
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.81",
3
+ "version": "1.0.82",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -18,7 +18,9 @@
18
18
  pnpm install
19
19
  pnpm dev
20
20
  pnpm typecheck
21
+ pnpm typecheck:js-code
21
22
  pnpm build
23
+ pnpm build-js-code
22
24
  openxiangda workspace publish --profile <name> --form <formCode>
23
25
  openxiangda resource plan --profile <name>
24
26
  openxiangda resource publish --profile <name>
@@ -33,6 +35,8 @@ openxiangda resource publish --profile <name>
33
35
  openxiangda runtime deploy --profile <name>
34
36
  ```
35
37
 
38
+ `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 资源发布使用。
39
+
36
40
  `openxiangda runtime deploy` 会构建 `dist/`、上传应用前端包并激活当前版本。不要手工修改 `dist/index.html`。
37
41
 
38
42
  ## 应用结构
@@ -41,6 +45,8 @@ openxiangda runtime deploy --profile <name>
41
45
  - `src/pages/admin/AdminDashboardPage.tsx`:默认首页。
42
46
  - `src/pages/defaults/*`:表单、流程、数据列表、文件预览等默认页。
43
47
  - `src/runtime/default-page-overrides.tsx`:整页覆盖默认页的入口。
48
+ - `src/js-code-nodes/*`、`src/automations/*`、`src/functions/*`:后端执行脚本源码。
49
+ - `scripts/build-js-code.mjs`:JS_CODE V2、代码自动化和 App Function 的 TypeScript 构建脚本。
44
50
 
45
51
  ## 权限资源
46
52
 
@@ -6,11 +6,14 @@
6
6
  "scripts": {
7
7
  "dev": "vite",
8
8
  "build": "vite build",
9
+ "build-js-code": "node scripts/build-js-code.mjs",
10
+ "build:js-code": "node scripts/build-js-code.mjs",
9
11
  "build:forms": "lowcode-workspace build-forms",
10
12
  "sync-schema": "lowcode-workspace sync-schema",
11
13
  "publish:all": "lowcode-workspace publish-all",
12
14
  "openxiangda:publish": "lowcode-workspace publish-all",
13
15
  "typecheck": "tsc -p tsconfig.app.json --noEmit",
16
+ "typecheck:js-code": "tsc -p tsconfig.js-code-nodes.json --noEmit",
14
17
  "check": "pnpm typecheck && pnpm build",
15
18
  "resources:plan": "openxiangda resource plan",
16
19
  "resources:publish": "openxiangda resource publish",
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ import { builtinModules } from "node:module";
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { readdir, stat } from "node:fs/promises";
6
+ import path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { build } from "vite";
9
+
10
+ const rootDir = fileURLToPath(new URL("..", import.meta.url));
11
+ const args = process.argv.slice(2);
12
+
13
+ const sourceKinds = {
14
+ "js-code-nodes": {
15
+ name: "js-code-nodes",
16
+ sourceRoot: path.join(rootDir, "src", "js-code-nodes"),
17
+ outputRoot: path.join(rootDir, "dist", "js-code-nodes"),
18
+ label: "JS_CODE",
19
+ },
20
+ automations: {
21
+ name: "automations",
22
+ sourceRoot: path.join(rootDir, "src", "automations"),
23
+ outputRoot: path.join(rootDir, "dist", "automations"),
24
+ label: "automation code",
25
+ },
26
+ functions: {
27
+ name: "functions",
28
+ sourceRoot: path.join(rootDir, "src", "functions"),
29
+ outputRoot: path.join(rootDir, "dist", "functions"),
30
+ label: "app function",
31
+ },
32
+ };
33
+
34
+ function readArg(name) {
35
+ const prefix = `--${name}=`;
36
+ const inline = args.find((arg) => arg.startsWith(prefix));
37
+ if (inline) return inline.slice(prefix.length);
38
+ const index = args.indexOf(`--${name}`);
39
+ return index >= 0 ? args[index + 1] : undefined;
40
+ }
41
+
42
+ async function listScriptCodes(kind) {
43
+ if (!existsSync(kind.sourceRoot)) return [];
44
+ const entries = await readdir(kind.sourceRoot);
45
+ const result = [];
46
+ for (const entry of entries) {
47
+ const entryDir = path.join(kind.sourceRoot, entry);
48
+ const entryStat = await stat(entryDir);
49
+ if (!entryStat.isDirectory()) continue;
50
+ if (existsSync(path.join(entryDir, "index.ts"))) result.push(entry);
51
+ }
52
+ return result.sort();
53
+ }
54
+
55
+ function typecheckScripts() {
56
+ const result = spawnSync(
57
+ "pnpm",
58
+ ["exec", "tsc", "-p", "tsconfig.js-code-nodes.json", "--noEmit"],
59
+ {
60
+ cwd: rootDir,
61
+ stdio: "inherit",
62
+ },
63
+ );
64
+ if (result.status !== 0) throw new Error("JS_CODE TypeScript validation failed");
65
+ }
66
+
67
+ async function buildScript(kind, scriptCode) {
68
+ const entry = path.join(kind.sourceRoot, scriptCode, "index.ts");
69
+ if (!existsSync(entry)) throw new Error(`${kind.label} script not found: ${entry}`);
70
+
71
+ const outDir = path.join(kind.outputRoot, scriptCode);
72
+ const external = Array.from(
73
+ new Set([...builtinModules, ...builtinModules.map((name) => `node:${name}`)]),
74
+ );
75
+
76
+ await build({
77
+ configFile: false,
78
+ root: rootDir,
79
+ logLevel: "warn",
80
+ resolve: {
81
+ alias: {
82
+ "@": path.join(rootDir, "src"),
83
+ },
84
+ },
85
+ build: {
86
+ ssr: entry,
87
+ outDir,
88
+ emptyOutDir: true,
89
+ target: "node20",
90
+ minify: false,
91
+ sourcemap: true,
92
+ rollupOptions: {
93
+ external,
94
+ output: {
95
+ format: "cjs",
96
+ entryFileNames: "index.cjs",
97
+ inlineDynamicImports: true,
98
+ exports: "auto",
99
+ },
100
+ },
101
+ },
102
+ });
103
+
104
+ console.log(`built ${kind.name}/${scriptCode} -> ${path.relative(rootDir, path.join(outDir, "index.cjs"))}`);
105
+ }
106
+
107
+ const onlyScript = readArg("script");
108
+ const sourceArg = readArg("source");
109
+ const selectedKind = sourceArg ? sourceKinds[sourceArg] : undefined;
110
+ if (sourceArg && !selectedKind) {
111
+ throw new Error(`unsupported source: ${sourceArg}. Expected js-code-nodes, automations, or functions`);
112
+ }
113
+
114
+ async function resolveBuildTargets() {
115
+ if (onlyScript) {
116
+ if (selectedKind) return [{ kind: selectedKind, scriptCode: onlyScript }];
117
+ for (const kind of Object.values(sourceKinds)) {
118
+ if (existsSync(path.join(kind.sourceRoot, onlyScript, "index.ts"))) {
119
+ return [{ kind, scriptCode: onlyScript }];
120
+ }
121
+ }
122
+ return [{ kind: sourceKinds["js-code-nodes"], scriptCode: onlyScript }];
123
+ }
124
+
125
+ const kinds = selectedKind ? [selectedKind] : Object.values(sourceKinds);
126
+ const targets = [];
127
+ for (const kind of kinds) {
128
+ for (const scriptCode of await listScriptCodes(kind)) {
129
+ targets.push({ kind, scriptCode });
130
+ }
131
+ }
132
+ return targets;
133
+ }
134
+
135
+ const targets = await resolveBuildTargets();
136
+
137
+ if (targets.length === 0) {
138
+ console.log("no JS_CODE scripts found under src/js-code-nodes/<scriptCode>/index.ts, src/automations/<scriptCode>/index.ts, or src/functions/<functionCode>/index.ts");
139
+ process.exit(0);
140
+ }
141
+
142
+ typecheckScripts();
143
+
144
+ for (const target of targets) {
145
+ await buildScript(target.kind, target.scriptCode);
146
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "types": ["node"],
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "@/*": ["src/*"]
12
+ }
13
+ },
14
+ "include": [
15
+ "src/js-code-nodes/**/*.ts",
16
+ "src/automations/**/*.ts",
17
+ "src/functions/**/*.ts"
18
+ ]
19
+ }