@guandata/guanetl 0.1.26 → 0.1.28

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,11 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## @guandata/guanetl 0.1.28 - 2026-08-20
4
+
5
+ - 改进 npm 安装后的自动 Skill 安装反馈,便于判断安装结果和后续处理。
6
+ - 修复 Skill 安装/删除确认流程的兼容性问题,降低交互式安装失败风险。
7
+ - 优化 Windows 环境下的 CLI 启动兼容性。
8
+ - 同步更新安装与使用说明。
9
+
10
+ ## @guandata/guanetl 0.1.27
11
+
12
+ - 优化 ETL 首次运行后的输出校验,临时输出数据集完成生成后再进行字段检查,首次运行更稳定。
13
+ - 增强输出字段注册与查询结果校验,减少首次运行时的误报和阻断。
14
+
3
15
  ## @guandata/guanetl 0.1.26 - 2026-08-07
4
16
 
5
17
  - 新增输出数据集落位闭环校验,并同步目录意图,提升 ETL 保存、运行后的结果一致性。
6
18
  - 修复 ETL 编辑往返时配置丢失问题,并拦截 JOIN 未知类型,增强配置保真与校验可靠性。
7
19
  - `save` 影响报告新增字段级变更明细,便于使用者了解保存操作的具体影响。
8
20
  - 修复工作流输出绑定中的字段错位问题,提升输出配置的准确性。
21
+
9
22
  ## @guandata/guanetl 0.1.25 - 2026-08-04
10
23
 
11
24
  - ETL 已在运行时可直接跟踪当前任务,无需管理员级全局任务权限。
package/README.md CHANGED
@@ -47,10 +47,20 @@ guanetl save --dir <work_dir>
47
47
  guanetl install-skill
48
48
  ```
49
49
 
50
- > `npm install -g` / `npm link` 全局安装时会通过 postinstall 自动执行一次 skill 安装/刷新;上述命令用于手动重装或排查。CI 等无需 skill 的环境可设 `GUAN_SKIP_INSTALL_SKILL=1` 跳过。
50
+ > `npm install -g --foreground-scripts @guandata/guanetl` / `npm link --foreground-scripts` 会通过 postinstall 自动刷新 skill,并显示明确的成功、失败或跳过结果;上述 `guanetl install-skill` 仅用于失败后的手动修复或排查。CI 等无需 skill 的环境可设 `GUAN_SKIP_INSTALL_SKILL=1` 跳过。
51
51
 
52
52
  ## 版本更新
53
53
 
54
+ ### @guandata/guanetl 0.1.28
55
+
56
+ - 改进 Skill 自动安装反馈和删除确认流程。
57
+ - 优化 Windows 环境下的 CLI 启动兼容性。
58
+
59
+ ### @guandata/guanetl 0.1.27
60
+
61
+ - 优化 ETL 首次运行后的输出校验,临时输出数据集完成生成后再进行字段检查,首次运行更稳定。
62
+ - 增强输出字段注册与查询结果校验,减少首次运行时的误报和阻断。
63
+
54
64
  ### @guandata/guanetl 0.1.26
55
65
 
56
66
  - 新增输出数据集落位校验,提升 ETL 保存和运行后的结果一致性。
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+
3
+ const { spawnSync } = require("child_process");
4
+
5
+ const SANITIZED_MARKER = "GUAN_INSTALL_ENV_SANITIZED";
6
+ const SAFE_DELETE_TARGET = String.raw`(?:"(?:[^"]*[\\/])?genie-safe-delete\.cjs"|'(?:[^']*[\\/])?genie-safe-delete\.cjs'|(?:[^\s"']*[\\/])?genie-safe-delete\.cjs)`;
7
+
8
+ function envKey(env, expected) {
9
+ return Object.keys(env).find((key) => key.toLowerCase() === expected.toLowerCase());
10
+ }
11
+
12
+ function stripWorkBuddySafeDelete(nodeOptions) {
13
+ const pattern = new RegExp(
14
+ String.raw`(^|\s)(?:--require|-r)(?:=|\s+)${SAFE_DELETE_TARGET}(?=\s|$)(?:\s+|$)`,
15
+ "gi"
16
+ );
17
+ let removed = false;
18
+ const value = String(nodeOptions || "").replace(pattern, (_, prefix) => {
19
+ removed = true;
20
+ return prefix ? " " : "";
21
+ }).trim();
22
+ return { value, removed };
23
+ }
24
+
25
+ function hasWorkBuddySafeDelete(env = process.env) {
26
+ const key = envKey(env, "NODE_OPTIONS");
27
+ return Boolean(key && stripWorkBuddySafeDelete(env[key]).removed);
28
+ }
29
+
30
+ function createInstallChildEnv(env = process.env) {
31
+ const childEnv = { ...env };
32
+ for (const key of Object.keys(childEnv)) {
33
+ if (key.toLowerCase() === "npm_config_global") {
34
+ delete childEnv[key];
35
+ }
36
+ }
37
+
38
+ const nodeOptionsKey = envKey(childEnv, "NODE_OPTIONS");
39
+ if (nodeOptionsKey) {
40
+ const cleaned = stripWorkBuddySafeDelete(childEnv[nodeOptionsKey]);
41
+ if (cleaned.removed) {
42
+ if (cleaned.value) childEnv[nodeOptionsKey] = cleaned.value;
43
+ else delete childEnv[nodeOptionsKey];
44
+ childEnv[SANITIZED_MARKER] = "1";
45
+ }
46
+ }
47
+ return childEnv;
48
+ }
49
+
50
+ function reexecInstallCommand(scriptPath, args, options = {}) {
51
+ const env = options.env || process.env;
52
+ if (env[SANITIZED_MARKER] === "1" || !hasWorkBuddySafeDelete(env)) {
53
+ return false;
54
+ }
55
+
56
+ const spawn = options.spawn || spawnSync;
57
+ const exit = options.exit || process.exit;
58
+ const nodePath = options.nodePath || process.execPath;
59
+ const result = spawn(nodePath, [scriptPath, ...args], {
60
+ stdio: "inherit",
61
+ env: createInstallChildEnv(env),
62
+ shell: false,
63
+ });
64
+ if (result.error) throw result.error;
65
+ exit(result.status == null ? 1 : result.status);
66
+ return true;
67
+ }
68
+
69
+ module.exports = {
70
+ createInstallChildEnv,
71
+ hasWorkBuddySafeDelete,
72
+ reexecInstallCommand,
73
+ };
@@ -41,18 +41,21 @@ function main() {
41
41
  );
42
42
  if (result.error || result.status !== 0) {
43
43
  console.warn(
44
- `[${CLI_NAME}] postinstall: automatic skill install did not complete` +
44
+ `[${CLI_NAME}] postinstall: AI skill update FAILED` +
45
45
  (result.error ? ` (${result.error.message})` : ` (exit ${result.status})`) +
46
46
  `; run \`${CLI_NAME} install-skill\` manually to refresh SKILL.md.`
47
47
  );
48
+ return;
48
49
  }
50
+ console.log(`[${CLI_NAME}] postinstall: AI skill updated successfully.`);
49
51
  }
50
52
 
51
53
  try {
52
54
  main();
53
55
  } catch (err) {
54
56
  console.warn(
55
- `[${CLI_NAME}] postinstall: ${err.message}; run \`${CLI_NAME} install-skill\` manually.`
57
+ `[${CLI_NAME}] postinstall: AI skill update FAILED (${err.message}); ` +
58
+ `run \`${CLI_NAME} install-skill\` manually.`
56
59
  );
57
60
  }
58
61
  process.exit(0);
package/bin/run.js CHANGED
@@ -6,6 +6,7 @@ const { execFileSync, execSync, spawnSync } = require("child_process");
6
6
  const path = require("path");
7
7
  const fs = require("fs");
8
8
  const os = require("os");
9
+ const { createInstallChildEnv, reexecInstallCommand } = require("./install-env");
9
10
 
10
11
  const PLATFORM_MAP = {
11
12
  "darwin-arm64": "guanetl-darwin-arm64",
@@ -111,6 +112,7 @@ function resolveNpxInvocation() {
111
112
  }
112
113
 
113
114
  if (process.argv[2] === "install-skill") {
115
+ reexecInstallCommand(__filename, process.argv.slice(2));
114
116
  const pkgRoot = path.join(__dirname, "..");
115
117
  const extraArgs = process.argv.slice(3);
116
118
  const args = [
@@ -128,8 +130,11 @@ if (process.argv[2] === "install-skill") {
128
130
  console.log("Installing guanetl to AI coding assistants...");
129
131
  const npxInvocation = resolveNpxInvocation();
130
132
  const result = spawnSync(npxInvocation.command, [...npxInvocation.argsPrefix, ...args], {
133
+ // Avoid leaking project-only Agent markers (for example PromptScript) from
134
+ // the caller's cwd into this global installation.
135
+ cwd: pkgRoot,
131
136
  stdio: "inherit",
132
- env: process.env,
137
+ env: createInstallChildEnv(process.env),
133
138
  shell: false,
134
139
  });
135
140
  if (result.error) throw result.error;
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guandata/guanetl",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "description": "观远 ETL 本地开发工具 - 拉取、编辑、导出、预览、保存 ETL",
5
5
  "bin": {
6
6
  "guanetl": "bin/run.js"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: guanetl
3
3
  description: 当用户要新建、修改、调试、定时调度、立即执行、取消执行、保存发布观远 BI / Guandata 的 ETL,或给出 ETL ID、dataFlowId、etl.go、meta.json、SQL 节点、dsId、节点报错时,优先使用这个 skill。即使用户只说"帮我改一下这个 ETL""这个 ETL 导出失败""新建一个观远 ETL""把这个 ETL 配成每天 2 点跑""取消正在跑的 ETL""保存后立即执行并等完成",也要主动使用。它把线上 ETL 拉到本地工作目录,约束 AI 只编辑 etl/ 下文件,并完成 create/edit → export → preview → save → run/schedule 闭环。查 ETL/数据集元信息走 guancli;数据源/数据集 CRUD 走 guands。
4
- compatibility: "Requires Node.js 14+. Install via npm link (local) or npm install -g @guandata/guanetl (from internal Nexus registry). CLI command: guanetl."
4
+ compatibility: "Requires Node.js 14+. Install via npm link --foreground-scripts (local) or npm install -g --foreground-scripts @guandata/guanetl (from internal Nexus registry) so the AI skill refresh result is visible. CLI command: guanetl."
5
5
  ---
6
6
 
7
7
  # guanetl
@@ -155,6 +155,8 @@ guanetl run <etl_id> --wait # 可选:保存后触发执行并等待
155
155
 
156
156
  **输出数据集落位语义(重要,勿用 dsId 判断输出是否创建)**:服务端保存时总会给每个输出补一个 `dataSource{dsId, created:false}`,但在该 ETL **首次成功运行**之前数据集实体并不存在,且每次 save/run 都会重新生成一个全新的临时 dsId。因此"保存后 dsId 非空"不代表输出已创建;判断依据是 `dataSource.created == true`。`save` 成功后 CLI 会回读服务端并输出两类诊断:`save.output_dir_rectified` 表示 `parentDirId` 无效、已被服务端静默纠正(通常纠正到数据集根目录,页面"保存至"可能显示"请选择")——必须修正 etl.go 中的目录 id 后重新保存;`save.output_pending_materialization` 表示输出尚未物化,dsId 是临时值,勿用于下游引用。`run --wait` 成功后 CLI 会校验输出 `created == true`,存在未创建的输出会直接报错,按错误提示排查目录与节点连线,不要循环重试 save/run。例外:仅以顶层 dsId 绑定已有数据集且无 `dataSource` 的增量输出无法自动核验,会以 `run.output_placement_unverified` 警告提示,需用 `guancli etl get <etl_id> --raw` 人工确认(默认渲染不显示 `dataSource.created` 字段)。
157
157
 
158
+ **维护约束(首次运行字段校验)**:新增或修改 CLI 的运行前校验时,只有 `dataSource.created == true` 的输出 dsId 才能当作可查询的既有数据集并读取 baseline。显式 `created:false` 表示首跑临时 dsId,运行前不得查询它、更不得因此阻断 `/api/etl/execute`;应保留输出节点 ID 与预期字段,在任务完成、输出落位校验通过后 fresh edit 回读稳定 dsId,再校验 metadata 和定向 preview。缺失 `created` 标记不等于首跑,必须保留既有的严格校验/失败语义,避免把旧 DATAFLOW 的字段注册漂移静默放行。
159
+
158
160
  如果 `save` 在 direct-save 前提示输出数据集绑定风险,按提示处理,不要手动删除或重命名旧输出数据集。跨目录、重名歧义、删除旧输出或无法唯一匹配时,CLI 不会猜测并默认停止保存。修复优先级:重新 `edit` 到新目录、同步 `outputDsName`、确认输出目录;确实要替换已绑定输出时,设置新的节点 id、`outputDsName` 或 `parentDirId`,并显式加 `--allow-output-replacement`。该选项只放行保存,不会迁移任何下游 dsId 引用。
159
161
 
160
162
  只追加输出列只是在输出绑定层面的安全例外:新增列、不改名、不删已有列、不改已有列类型,并保持输出名称和目录时,可以原地 `save`,保存前仍需 `preview` 目标输出确认结果;这**不代表**旧 DATAFLOW 数据集已注册该字段。`run --wait` 对本轮新增且透传到输出的 `BasicCalculator` 字段,会逐个输出等待其自身的 `lastExecution.endTime` 与 `datasourceModifyTime` 推进,并同时检查数据集 metadata 和实际 preview 返回字段。任一目标输出仍在产出,或字段没有注册/不可查询,命令会以错误结束;可用 `--timeout` 延长这段校验时间。改列名、删列、改类型、换输入数据集或重接输出链路仍按下游 schema 风险处理;输出节点 id 变化仅在唯一匹配时自动协调。