@guandata/guanwf 0.1.820 → 0.1.822

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/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## @guandata/guanwf 0.1.822 - 2026-07-23
4
+
5
+ - 优化工作流依赖与血缘读取效率,复杂编排的分析和校验等待更少。
6
+ - 完善已有工作流的原地编辑指引,避免更新时破坏原有调度、权限和下游引用。
7
+
8
+ ## @guandata/guanwf 0.1.821 - 2026-07-20
9
+
10
+ - 保持 8.2.0 专用兼容能力不变;全局安装或升级后自动刷新 AI Skill。
11
+ - 随包提供完整的工作流使用说明和参考资料。
4
12
 
5
13
  ## @guandata/guanwf 0.1.820 - 2026-07-14
6
14
 
package/README.md CHANGED
@@ -43,8 +43,20 @@ DatasetNode 刷新目标,应使用 SubWorkflowNode 调用产出工作流。参
43
43
  guanwf install-skill
44
44
  ```
45
45
 
46
+ > `npm install -g` / `npm link` 全局安装时会通过 postinstall 自动执行一次 skill 安装/刷新;上述命令用于手动重装或排查。CI 等无需 skill 的环境可设 `GUAN_SKIP_INSTALL_SKILL=1` 跳过。
47
+
46
48
  ## 版本更新
47
49
 
50
+ ### @guandata/guanwf 0.1.822
51
+
52
+ - 优化工作流依赖与血缘读取效率,复杂编排的分析和校验等待更少。
53
+ - 完善已有工作流的原地编辑指引,避免更新时破坏原有调度、权限和下游引用。
54
+
55
+ ### @guandata/guanwf 0.1.821
56
+
57
+ - 保持 8.2.0 专用兼容能力不变;全局安装或升级后自动刷新 AI Skill。
58
+ - 随包提供完整的工作流使用说明和参考资料。
59
+
48
60
  ### @guandata/guanwf 0.1.820
49
61
 
50
62
  - 8.2.0 专用兼容版本;请勿与面向 master 的 CLI 版本混用。
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Best-effort post-install hook: refresh the installed AI skill right after
5
+ * a global npm install/upgrade, so users cannot forget to run
6
+ * `guanwf install-skill` and end up with a stale SKILL.md.
7
+ *
8
+ * Constraints:
9
+ * - MUST never fail the npm install: every failure path still exits 0.
10
+ * - Only runs for global installs (`npm install -g` / `npm link`); a local
11
+ * `npm install` inside a project never touches the user's home dirs.
12
+ * - Escape hatch: GUAN_SKIP_INSTALL_SKILL=1 skips entirely (e.g. CI images
13
+ * that install the CLI but never run an AI agent are skipped by default).
14
+ */
15
+
16
+ "use strict";
17
+
18
+ const { spawnSync } = require("child_process");
19
+ const path = require("path");
20
+
21
+ const CLI_NAME = "guanwf";
22
+
23
+ function skipReason() {
24
+ if (process.env.GUAN_SKIP_INSTALL_SKILL === "1") return "GUAN_SKIP_INSTALL_SKILL=1";
25
+ if (process.env.npm_config_global !== "true") return "not a global install";
26
+ if (process.env.CI) return "CI environment";
27
+ return "";
28
+ }
29
+
30
+ function main() {
31
+ const reason = skipReason();
32
+ if (reason) {
33
+ console.log(`[${CLI_NAME}] postinstall: skipping automatic install-skill (${reason}).`);
34
+ return;
35
+ }
36
+ console.log(`[${CLI_NAME}] postinstall: refreshing AI skill (${CLI_NAME} install-skill)...`);
37
+ const result = spawnSync(
38
+ process.execPath,
39
+ [path.join(__dirname, "run.js"), "install-skill"],
40
+ { stdio: "inherit", env: process.env, timeout: 180000 }
41
+ );
42
+ if (result.error || result.status !== 0) {
43
+ console.warn(
44
+ `[${CLI_NAME}] postinstall: automatic skill install did not complete` +
45
+ (result.error ? ` (${result.error.message})` : ` (exit ${result.status})`) +
46
+ `; run \`${CLI_NAME} install-skill\` manually to refresh SKILL.md.`
47
+ );
48
+ }
49
+ }
50
+
51
+ try {
52
+ main();
53
+ } catch (err) {
54
+ console.warn(
55
+ `[${CLI_NAME}] postinstall: ${err.message}; run \`${CLI_NAME} install-skill\` manually.`
56
+ );
57
+ }
58
+ process.exit(0);
package/bin/run.js CHANGED
@@ -86,10 +86,36 @@ if (process.argv[2] === "version" && process.argv.length === 3) {
86
86
  process.exit(0);
87
87
  }
88
88
 
89
+
90
+ // 定位 npm 自带的 npx-cli.js 并用 node 直接执行(与 guanskill 保持一致),
91
+ // 避免 Windows 上 spawn npx.cmd 的两类问题:shell:true 不做参数 quoting
92
+ // (全局安装路径含空格时会被拆参),以及新版 Node 禁止无 shell 的 .cmd
93
+ // spawn(CVE-2024-27980)。postinstall 场景下 npm_execpath 必然可用。
94
+ function resolveNpxInvocation() {
95
+ const executableDir = path.dirname(process.execPath);
96
+ const candidates = [];
97
+ if (process.env.npm_execpath) {
98
+ candidates.push(path.join(path.dirname(process.env.npm_execpath), "npx-cli.js"));
99
+ }
100
+ candidates.push(path.join(executableDir, "node_modules", "npm", "bin", "npx-cli.js"));
101
+ candidates.push(path.resolve(executableDir, "..", "node_modules", "npm", "bin", "npx-cli.js"));
102
+ candidates.push(path.resolve(executableDir, "..", "lib", "node_modules", "npm", "bin", "npx-cli.js"));
103
+ const npxCliPath = candidates.find((candidate) => fs.existsSync(candidate));
104
+ if (npxCliPath) {
105
+ return { command: process.execPath, argsPrefix: [npxCliPath] };
106
+ }
107
+ if (process.platform === "win32") {
108
+ throw new Error("Cannot locate npm/bin/npx-cli.js for safe Windows execution");
109
+ }
110
+ return { command: "npx", argsPrefix: [] };
111
+ }
112
+
89
113
  if (process.argv[2] === "install-skill") {
90
114
  const pkgRoot = path.join(__dirname, "..");
91
115
  const extraArgs = process.argv.slice(3);
92
116
  const args = [
117
+ // --yes: postinstall 等非交互环境下 npx 需要免确认下载 skills CLI
118
+ "--yes",
93
119
  "skills",
94
120
  "add",
95
121
  pkgRoot,
@@ -100,7 +126,12 @@ if (process.argv[2] === "install-skill") {
100
126
  ...extraArgs,
101
127
  ];
102
128
  console.log("Installing guanwf to AI coding assistants...");
103
- const result = spawnSync("npx", args, { stdio: "inherit", env: process.env, shell: true });
129
+ const npxInvocation = resolveNpxInvocation();
130
+ const result = spawnSync(npxInvocation.command, [...npxInvocation.argsPrefix, ...args], {
131
+ stdio: "inherit",
132
+ env: process.env,
133
+ shell: false,
134
+ });
104
135
  if (result.error) throw result.error;
105
136
  const status = result.status || 0;
106
137
  if (status === 0) installBuddySkills(pkgRoot, "guanwf");
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@guandata/guanwf",
3
- "version": "0.1.820",
3
+ "version": "0.1.822",
4
4
  "description": "观远工作流数据流编辑工具 - 创建、编辑、导出、预览、保存数据流",
5
5
  "bin": {
6
6
  "guanwf": "bin/run.js"
7
7
  },
8
8
  "scripts": {
9
+ "postinstall": "node bin/postinstall.js",
9
10
  "build": "node scripts/build.js && node scripts/sync-skill.js",
10
11
  "changelog": "node ../../scripts/generate-release-changelog.js .",
11
12
  "check-changelog": "node ../../scripts/check-release-changelog.js .",
@@ -37,6 +37,13 @@ compatibility: "Requires Node.js 14+. Install via npm link (local) or npm instal
37
37
  **保存工作流时,必须携带完整的工作流上下文(所有 tasks + 所有 dataflowJson)。**
38
38
  `guanwf save` 内部会以服务端最新版本为基底做字段级合并,AI 不需要也不应该手拼保存 payload。
39
39
 
40
+ **编辑 ≠ 删除重建。** 编辑已有工作流永远走 `edit → export → save` 原地保存(保留 workflowId),
41
+ 改名直接修改 `workflow.go` 中 `Workflow.Name` 后正常 save。guanwf 没有提供工作流删除命令;
42
+ 用户要求删除时直接说明 CLI 暂不支持。**禁止**用"新建一个工作流替代旧工作流"来模拟编辑或
43
+ 改名——workflowId 变化会丢失定时/事件调度配置、权限与协作者设置、运行实例历史,且所有通过
44
+ `SubWorkflowNode` 引用它的编排工作流会断链。只有用户明确指示"新建替代、再废弃旧的"时才允许,
45
+ 且执行前必须列出上述影响并获得确认。
46
+
40
47
  ## Harness 工作法:源文件驱动,不手写最终 JSON
41
48
 
42
49
  先改可读、可复现的源文件,再用明确命令重新生成派生产物。不要为了让保存请求看起来正确而直接改最终 JSON。
@@ -58,7 +58,7 @@ orders := BasicInputDataset("id_1001", "订单", "ds_orders", []Field{
58
58
  }, Position{X: 100, Y: 100})
59
59
  ```
60
60
 
61
- - `go run ./cmd/guanetl export` 会把这里的字段带到最终 JSON。
61
+ - `guanetl export` 会把这里的字段带到最终 JSON。
62
62
  - `meta.json` 主要保留 ETL 名称等元信息。
63
63
  - 字段相关报错时,优先检查 `[]Field`,不要先怀疑 `meta.json`。
64
64
 
@@ -104,6 +104,9 @@ calc := BasicCalculator("id_1002", "解析展示格式度量", inputA, []Formula
104
104
  - 新版 ETL 默认使用输入字段别名:如果数据集字段有 `alias`,进入 ETL 算子后的列名通常是 `alias`;没有 `alias` 时才是原始 `name`。
105
105
  - 输入字段别名的事实源是数据集字段元数据;可用 `guands dataset fields <dsId>` 回读,用 `guands dataset alias <dsId> --fd-id <fdId> --alias "展示名"` 更新。`BasicInputDataset(..., []Field{...})` 中的字段 schema 主要用于导入/导出和本地 lint,不会把一个未设置过数据集 alias 的字段强制变成展示名列。
106
106
  - 本地校验按 `alias -> displayName/showName/title -> name` 解析有效字段名;同一来源下两个字段解析成相同有效名会被视为歧义,建模前应先改名或明确上游输出。
107
+ - 不要把 JOIN 键类型不一致直接断言为零匹配。STRING 与 LONG/DOUBLE 等数值类型 JOIN 时,Spark 可能做隐式数值 coercion:纯数字字符串可能匹配,`"001"` 可能折叠后匹配数值 `1`,非数字值可能无法匹配,超长 ID 可能丢失精度。业务标识键应在 JOIN 前显式统一为 STRING,并用 preview 验证真实匹配率。
108
+ - `preview` / `save` 会从数据集元数据补齐 `fdId -> alias` 的运行时映射,并把 JOIN predicate 中的输入 alias 归一化为 raw name;本地 `BasicInputDataset` 声明与服务端不一致时,以服务端字段身份为准,且不会修改工作区中的原始 `_exported.json`。当前服务端仍不支持把“设置过 alias 的数据集计算字段”直接选作 JOIN 输出列,应输出其原始依赖列或先在 ETL 中生成普通列。
109
+ - aggregation/window 计算字段不是行级字段,不能直接作为 JOIN 键;请先在 ETL 中生成普通计算列。`preview` / `save` 会在提交远端请求前拦截该用法。
107
110
  - SQL 节点使用上游表 `input1`、`input2` 的当前列名。字段有中文展示名时,SQL 里应写展示名并用反引号,例如 ``SELECT `门店ID` FROM input1``。
108
111
  - `SELECT_COLUMNS`、`FILTER_ROWS`、`REMOVE_DUPLICATES`、`GROUP_BY` 等直接列名算子,也以当前上游列名为准。
109
112
  - 底层数据集读取和数据集元数据仍保留原始 `name`,所以 `guancli` / `guands` 输出字段时要同时关注 `name` 和 `alias`。
@@ -338,25 +341,30 @@ func DefineETL() []Node {
338
341
 
339
342
  始终按这个顺序:
340
343
 
341
- 1. `go run ./cmd/guanetl export --dir <work_dir>`
342
- 2. 需要看结果时:`go run ./cmd/guanetl preview <node_id> --dir <work_dir>`
343
- 3. 保存前先看影响:`go run ./cmd/guanetl save --dir <work_dir> --dry-run`
344
- 4. 确认无误后:`go run ./cmd/guanetl save --dir <work_dir>`
344
+ 1. `guanetl export --dir <work_dir>`
345
+ 2. 需要看结果时:`guanetl preview <node_id> --dir <work_dir>`;保存前要求非空时追加 `--require-nonempty`,零行会返回错误并阻止命令链继续执行
346
+ 3. 保存前先看影响:`guanetl save --dir <work_dir> --dry-run`
347
+ 4. 确认无误后:`guanetl save --dir <work_dir>`
345
348
 
346
349
  如果 `export` 没过,不要直接 `save`。
347
350
 
348
351
  ## save 输出数据集冲突
349
352
 
350
- `save` 会先拉取服务端当前 ETL,再把本地 `_exported.json` 合并进去。修改已保存 ETL 时,输出节点必须保留服务端已有输出数据集绑定,否则 direct-save 可能把它当成“创建新输出数据集”,触发“输出数据集目录中存在同名文件”。
353
+ `save` 会先拉取服务端当前 ETL,再把本地 `_exported.json` 合并进去。修改已保存 ETL 时,输出节点必须复用服务端已有输出数据集绑定,否则 direct-save 可能把它当成“创建新输出数据集”,触发“输出数据集目录中存在同名文件”。
354
+
355
+ 新建 ETL 首次保存时,服务端 edit API 可能返回 ETL 不存在。若当前本地 base 与工作区 ETL ID 一致且尚无 actions,CLI 会将其识别为正常首次保存回退并输出 `save.first_save_fallback`;其他 edit 故障使用 `save.edit_fallback` warning,不应静默当作首次保存。
356
+
357
+ 当本地输出节点 id 被重新生成,但 `outputDsName + parentDirId` 与一个服务端已绑定输出唯一匹配时,`save` 会在请求副本中自动恢复原节点 id 和 dsId,并在影响报告中输出 `save.output_binding_reconciled`。源 `_exported.json` 不会被改写。跨目录、双方重名歧义、删除旧输出或无法唯一匹配时不会自动协调。
351
358
 
352
359
  - 再次修改已保存 ETL,优先重新执行 `guanetl edit <etl_id> --dir <新目录>`,不要长期复用旧工作目录。
353
360
  - 只追加输出列时,可以原地 `save`:新增列,不改名、不删已有列、不改已有列类型,并保持原 `OUTPUT_DATASET` 节点 id 和 `outputDsName`。
354
361
  - 修改已有输出 schema 时,保持原 `OUTPUT_DATASET` 节点 id;改列名、删列、改已有列类型、换输入数据集或重接输出链路都可能影响下游绑定,保存前必须重新 `preview` 并评估下游。
355
362
  - 如果用 `guands dataset rename` 改过 ETL 输出数据集名称,必须同步 `etl.go` 中 `BasicOutputDataset(..., outputDsName, ...)` 或 `BasicOutputDatasetInDir(..., outputDsName, ...)` 的名称。
356
363
  - 不要为了绕过同名错误手动删除旧输出数据集;旧输出通常仍被 ETL 依赖。
357
- - 如果确实要创建新输出数据集,必须同时更换 `OUTPUT_DATASET` 节点 id,并设置新的 `outputDsName` 或 `parentDirId`;只改名称会被视为已有输出绑定风险。
364
+ - 如果只是在保留旧输出的同时增加新输出,使用新的节点 id 和不同的 `outputDsName` 或 `parentDirId` 即可。
365
+ - 如果确实要移除已绑定输出并创建新输出数据集,必须使用新的节点 id、`outputDsName` 或 `parentDirId`,并显式加 `--allow-output-replacement`。该选项不会迁移下游 dsId 引用,调用方必须自行完成依赖迁移。
358
366
  - `save` 如果在 direct-save 前提示输出绑定风险,先修本地 `etl.go` / `meta.json`,再重新 `export -> preview -> save`。
359
- - `save --dry-run` 只生成保存影响报告,不调用 direct-save;需要机器可读结果时加 `--format json`。报告里出现阻断级输出绑定风险时,必须先修本地定义,不能继续真实 `save`。
367
+ - `save --dry-run` 只生成保存影响报告,不调用 direct-save;需要机器可读结果时加 `--format json`。报告里出现阻断级输出绑定风险时,必须先修本地定义,或在确认主动替换且已规划下游迁移后显式使用 `--allow-output-replacement`。
360
368
 
361
369
  ## Appendix: Framework Surface
362
370