@rex0220/formula-authoring-tools 0.1.0 → 0.2.1

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/README.md CHANGED
@@ -27,9 +27,10 @@ Projects/
27
27
  - `src/connections.ts` / `src/workspace.ts` / `src/permission.ts` / `src/commit-file.ts` … kintone の接続のファイル(kSQL の `ksql.config.json` と同じ形。profile、`tokenMap`、`userApiToken`)、`kintone/<profile>/<番号>-<アプリ名>/` のフォルダーと印 `.formula-app.json`、kintone/ の下を変える許可、ファイルの確定とロック(print-craft-authoring tools 2.0.0 の写し)
28
28
  - `src/env.ts` / `src/kintone-rest.ts` / `src/kintone-url.ts` … `.env`(kintone 公式 MCP と同じ `KINTONE_*` + `FORMULA_PLUGIN_ZIP` / `FORMULA_KINTONE_CONFIG` / `FORMULA_USER_API_TOKEN`)、GET 専用・許可 API 固定・送信先固定の REST(kintone REST API と User API)
29
29
  - `src/meta.ts` … tools の版、扱う計算式プラグイン(プラグイン ID `FORMULA_PLUGIN_ID`、封筒の `FORMULA_CONFIG_PLUGIN_ID`、版の下限 `MIN_PLUGIN_VERSION`)、API の版(`SUPPORTED_API_VERSIONS`)、API の契約(`REQUIRED_API`)
30
- - `src/core.ts` … 共通の中核の入口(npm の `@rex0220/formula-authoring-tools/core`。MCP が使う予定)。`scripts/build.mjs` が `dist/core.mjs` と `dist/types/` を作る
30
+ - `src/commands/save.ts` … 保存の約束(formula MCP の `fml_save_settings` / `fml_update_fields` の本体。CLI は使わない)。新しい設定は同じ名前があれば保存しない、既存の設定は `fml_summary` の digest を照合して項目の行(`fieldcode`)を複数まとめて差し替える。normalize は Worker で行い、エラーがあれば確定しない。確定はロック付き(`commit-file.ts`)
31
+ - `src/core.ts` … 共通の中核の入口(npm の `@rex0220/formula-authoring-tools/core`。formula MCP が使う。`CORE_API_VERSION` 2)。`scripts/build.mjs` が `dist/core.mjs` と `dist/types/` を作る。MCP が使う名前は `test/core-exports.test.mjs` で固定する
31
32
  - `scripts/vendor.mjs` … moment 2.24.0 を CDN から `vendor/` に取る(`vendor/moment.json` の SHA-256 と照合)。`scripts/build.mjs` … esbuild で `dist/cli.mjs` / `dist/eval-worker.mjs` / `dist/core.mjs`。`scripts/gen-docs.mjs` … `docs/`(設定ファイル仕様、関数一覧、functions.json、schema-manifest、既定のテストデータ)と `samples/users.json` を zip から機械生成
32
- - `pending/` … print-craft から写してまだ計算式用にしていないもの(`save.ts` = MCP の保存の約束)。npm には入らない
33
+ - `pending/` … print-craft から写してまだ計算式用にしていないもの(`diff.ts` / `normalize.ts` は計算式用の `src/commands/` が別にあるので使っていない)。npm には入らない
33
34
  - `test/` … `node --test`(src の .ts を直接読む。`test/helpers.mjs` が fixture の zip を読む。`test/fixtures/formula/` はテスト用アプリ 2305 の項目定義と設定)
34
35
 
35
36
  ## 使い方(開発)
@@ -58,4 +59,6 @@ npm pack --dry-run # 公開前に中身を確かめる(dist、vendor、LICENS
58
59
 
59
60
  ## 履歴
60
61
 
62
+ - 0.2.1(2026-10): formula MCP の Codex レビューへの対応。`updateFields` の `common` を共通の設定のキー(`COMMON_KEYS`)だけに。Worker の中の誤りの種類(`EvalError.kind`)と名前を中核が instanceof で決める(zip のコードの例外の name / constructor.name / 文を応答や標準エラーに出さない。`classifyEvalError` / `builtinErrorName`)。保存の結果の想定外の誤りも種類だけ(`zipCode`)。CLI の想定外の誤りは種類だけ(`FORMULA_DEBUG=1` で文とスタック)。接続の誤りの文の print-craft の名残を直した
63
+ - 0.2.0(2026-10): formula MCP のための中核の入口(`CORE_API_VERSION` 2。CLI の動きは変えない)。`core` に workspace / connections / permission / commit-file / take / users の参照コード / JSON の誤りの文 / 行のキーを追加。`src/commands/save.ts`(`saveNewSettings` / `updateFields`)。`runEval` の `workerFile`(Worker の入口の絶対パス)と `captureOutput`(Worker の標準出力・標準エラーを親に流さず受け取る)、`loadEngine` の `vendorDir`(moment の置き場所)。評価の結果に `findings` と calc のレコードごとの行
61
64
  - 0.1.0(2026-10): 最初の公開。version / fields / record / records / pull / summary / diff / users / normalize / calc / take / edit / files。扱う計算式プラグイン Ver.101 以降、authoring API 1
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! @rex0220/formula-authoring-tools 0.1.0 (c) rex0220. MIT License. 計算式エンジンと計算式プラグインのコードは利用者の計算式プラグインプラグインの zip から読む(このファイルには含まれない)。commit 69de71e. */
2
+ /*! @rex0220/formula-authoring-tools 0.2.1 (c) rex0220. MIT License. 計算式エンジンと計算式プラグインのコードは利用者の計算式プラグインプラグインの zip から読む(このファイルには含まれない)。commit f2e46ef. */
3
3
 
4
4
  // src/cli.ts
5
5
  import { chmodSync, existsSync as existsSync9, mkdirSync as mkdirSync2, realpathSync as realpathSync5, writeFileSync as writeFileSync3 } from "node:fs";
@@ -355,7 +355,7 @@ function devMeta() {
355
355
  return { toolsVersion: pkg.version, pluginId: FORMULA_PLUGIN_ID, minPluginVersion: MIN_PLUGIN_VERSION, supportedApiVersions: [...SUPPORTED_API_VERSIONS], commit: gitCommit(AUTHORING_ROOT), builtAt: "", mode: "dev" };
356
356
  }
357
357
  function toolsMeta() {
358
- if (true) return JSON.parse('{"toolsVersion":"0.1.0","pluginId":"dhgihghfgfmibkdldkeibboabfnmdpke","minPluginVersion":101,"supportedApiVersions":[1],"commit":"69de71e","builtAt":"2026-10-18T00:25:51.113Z","mode":"build"}');
358
+ if (true) return JSON.parse('{"toolsVersion":"0.2.1","pluginId":"dhgihghfgfmibkdldkeibboabfnmdpke","minPluginVersion":101,"supportedApiVersions":[1],"commit":"f2e46ef","builtAt":"2026-10-18T03:55:45.474Z","mode":"build"}');
359
359
  return devMeta();
360
360
  }
361
361
 
@@ -659,6 +659,9 @@ function summarizeUsers(src, origin, user) {
659
659
  }
660
660
 
661
661
  // src/engine.ts
662
+ function momentFileOf(opt) {
663
+ return opt.vendorDir ? path6.join(opt.vendorDir, path6.basename(MOMENT_FILE)) : MOMENT_FILE;
664
+ }
662
665
  var DEFAULT_CONTEXT_BASE_URL = "https://example.cybozu.com";
663
666
  var SENTINEL_USER = { id: "0", code: "__sentinel__", name: "", email: "", timezone: "Asia/Tokyo", language: "ja", isGuest: false };
664
667
  var loaded = null;
@@ -758,15 +761,16 @@ async function loadOnce(opt) {
758
761
  }
759
762
  if (!src.api) throw new PluginZipError(`計算式プラグインの zip に ${API_ENTRY} が無い(Ver.${MIN_PLUGIN_VERSION} 以降の zip が要る): ${src.from}`, "no-api", known);
760
763
  if (!src.decimal) warnings.push(`zip に ${DECIMAL_ENTRY} が無い(べき乗の小数の指数がエラーになる)`);
761
- if (!existsSync3(MOMENT_FILE)) throw new Error(`moment が無い: ${MOMENT_FILE}(tools の npm install 時に取る。手で取るなら npm run vendor)`);
764
+ const momentFile = momentFileOf(opt);
765
+ if (!existsSync3(momentFile)) throw new Error(`moment が無い: ${momentFile}(tools の npm install 時に取る。手で取るなら npm run vendor)`);
762
766
  try {
763
- return await startEngine({ ...src, api: src.api }, known, warnings, stamp);
767
+ return await startEngine({ ...src, api: src.api }, known, warnings, stamp, momentFile);
764
768
  } catch (e) {
765
769
  poisoned = { code: e instanceof PluginZipError ? e.code : "unexpected", info: e instanceof PluginZipError ? e.info : known };
766
770
  throw e;
767
771
  }
768
772
  }
769
- async function startEngine(src, known, warnings, stamp) {
773
+ async function startEngine(src, known, warnings, stamp, momentFile) {
770
774
  const g = globalThis;
771
775
  const { Window } = await import("happy-dom");
772
776
  const ctx = {
@@ -842,7 +846,7 @@ async function startEngine(src, known, warnings, stamp) {
842
846
  throw new PluginZipError(`計算式プラグインの zip のコード(${entry})を実行できない: ${e instanceof Error ? e.message : String(e)}`, "engine-unreadable", known);
843
847
  }
844
848
  };
845
- runScript(readFileSync4(MOMENT_FILE, "utf8"), MOMENT_FILE);
849
+ runScript(readFileSync4(momentFile, "utf8"), momentFile);
846
850
  runScript(src.momentTimezone, `${src.from}!${MOMENT_TZ_ENTRY}`, MOMENT_TZ_ENTRY);
847
851
  runScript(src.bignumber, `${src.from}!${BIGNUMBER_ENTRY}`, BIGNUMBER_ENTRY);
848
852
  if (src.decimal) runScript(src.decimal, `${src.from}!${DECIMAL_ENTRY}`, DECIMAL_ENTRY);
@@ -1397,7 +1401,7 @@ function authFor(set, profile, appId) {
1397
1401
  if (value === void 0) throw new ConnectionError(`profile「${profile.profile}」の tokenMap に APP${appId} が無い(そのアプリの API トークンを足す)`, "no-token");
1398
1402
  if (value.startsWith("env:")) {
1399
1403
  const token = nonblank(held.env[value.slice(4)]);
1400
- if (!token) throw new ConnectionError(`profile「${profile.profile}」の APP${appId} のトークンの環境変数が無い(設定した後、print-craft を再接続する。Desktop は起動し直す)`, "env-missing");
1404
+ if (!token) throw new ConnectionError(`profile「${profile.profile}」の APP${appId} のトークンの環境変数が無い(環境変数を置いてから実行し直す。MCP なら再接続する: Claude Code は /mcp、Desktop は起動し直す)`, "env-missing");
1401
1405
  return { baseUrl: profile.baseUrl, token };
1402
1406
  }
1403
1407
  return { baseUrl: profile.baseUrl, token: value };
@@ -3075,8 +3079,24 @@ function calcSettings(input) {
3075
3079
  }
3076
3080
 
3077
3081
  // src/worker/eval-run.ts
3082
+ function classifyEvalError(e) {
3083
+ const message = String(e?.message ?? e);
3084
+ if (e instanceof InputError) return { kind: "input", name: "InputError", message };
3085
+ if (e instanceof PluginZipError) return { kind: "zip", name: "PluginZipError", message, code: e.code };
3086
+ if (e instanceof UsersDataError) return { kind: "users", name: "UsersDataError", message };
3087
+ if (e instanceof PolicyError) return { kind: "policy", name: "PolicyError", message };
3088
+ return { kind: "other", name: builtinErrorName(e), message };
3089
+ }
3090
+ function builtinErrorName(e) {
3091
+ if (e instanceof TypeError) return "TypeError";
3092
+ if (e instanceof RangeError) return "RangeError";
3093
+ if (e instanceof ReferenceError) return "ReferenceError";
3094
+ if (e instanceof SyntaxError) return "SyntaxError";
3095
+ if (e instanceof URIError) return "URIError";
3096
+ return "Error";
3097
+ }
3078
3098
  async function runEvalJob(job) {
3079
- const engine = await loadEngine({ pluginZip: job.pluginZip, devPluginDir: job.devPluginDir, mode: job.mode });
3099
+ const engine = await loadEngine({ pluginZip: job.pluginZip, devPluginDir: job.devPluginDir, mode: job.mode, vendorDir: job.vendorDir });
3080
3100
  if (job.baseUrl) engine.setContext({ baseUrl: job.baseUrl });
3081
3101
  const usersInfo = buildUsersInfo(job.usersSource);
3082
3102
  const usersLabel = job.usersOrigin.kind === "file" ? job.usersOrigin.file : "既定のテストデータ(架空)";
@@ -3093,7 +3113,16 @@ async function runEvalJob(job) {
3093
3113
  usersOrigin: job.usersOrigin
3094
3114
  });
3095
3115
  const outcome = {
3096
- normalize: { summary: res.summary, hasErrors: res.findings.hasErrors, warnings: res.findings.count("warning"), output: res.output, checkDiffs: res.checkDiffs },
3116
+ normalize: {
3117
+ summary: res.summary,
3118
+ hasErrors: res.findings.hasErrors,
3119
+ warnings: res.findings.count("warning"),
3120
+ findings: [...res.findings.items],
3121
+ output: res.output,
3122
+ checkDiffs: res.checkDiffs,
3123
+ appId: res.appId,
3124
+ size: res.size
3125
+ },
3097
3126
  engineWarnings: [...engine.warnings]
3098
3127
  };
3099
3128
  if (job.kind === "calc" && job.calc && !res.findings.hasErrors) {
@@ -3111,7 +3140,7 @@ async function runEvalJob(job) {
3111
3140
  usersInfo,
3112
3141
  usersOrigin: job.usersOrigin
3113
3142
  });
3114
- outcome.calc = { text: r.text, failed: r.records.some((x) => x.errors.length > 0 || x.failed) };
3143
+ outcome.calc = { text: r.text, failed: r.records.some((x) => x.errors.length > 0 || x.failed), records: r.records, contextLine: r.contextLine, notice: r.notice, summary: r.summary };
3115
3144
  }
3116
3145
  return outcome;
3117
3146
  }
@@ -3124,10 +3153,12 @@ var EvalTimeoutError = class extends Error {
3124
3153
  var EvalError = class extends Error {
3125
3154
  innerName;
3126
3155
  code;
3127
- constructor(message, innerName, code) {
3156
+ kind;
3157
+ constructor(message, innerName, code, kind = "other") {
3128
3158
  super(message);
3129
3159
  this.innerName = innerName;
3130
3160
  this.code = code;
3161
+ this.kind = kind;
3131
3162
  }
3132
3163
  };
3133
3164
  function workerEntry() {
@@ -3141,10 +3172,26 @@ function workerEntry() {
3141
3172
  }
3142
3173
  async function runEval(job, opt = {}) {
3143
3174
  const timeoutSec = Math.min(Math.max(opt.timeoutSec ?? DEFAULT_TIMEOUT_SEC, 1), MAX_TIMEOUT_SEC);
3144
- if (opt.inline) return runEvalJob(job);
3145
- const entry = workerEntry();
3175
+ if (opt.inline) {
3176
+ try {
3177
+ return await runEvalJob(job);
3178
+ } catch (e) {
3179
+ const c = classifyEvalError(e);
3180
+ throw new EvalError(c.message, c.name, c.code, c.kind);
3181
+ }
3182
+ }
3183
+ let entry;
3184
+ if (opt.workerFile !== void 0) {
3185
+ if (!path13.isAbsolute(opt.workerFile) || !existsSync8(opt.workerFile)) throw new Error(`Worker の入口が無い(絶対パスで指定する): ${opt.workerFile}`);
3186
+ entry = opt.workerFile;
3187
+ } else entry = workerEntry();
3188
+ const capture = opt.captureOutput;
3146
3189
  return new Promise((resolve, reject) => {
3147
- const worker = new Worker(entry, { workerData: job, stdout: false, stderr: false });
3190
+ const worker = new Worker(entry, { workerData: job, stdout: capture !== void 0, stderr: capture !== void 0 });
3191
+ if (capture) {
3192
+ worker.stdout.on("data", (d) => capture(String(d), "stdout"));
3193
+ worker.stderr.on("data", (d) => capture(String(d), "stderr"));
3194
+ }
3148
3195
  let settled = false;
3149
3196
  const finish = (fn) => {
3150
3197
  if (settled) return;
@@ -3161,10 +3208,10 @@ async function runEval(job, opt = {}) {
3161
3208
  worker.on("message", (m) => {
3162
3209
  finish(() => {
3163
3210
  if (m.ok) resolve(m.outcome);
3164
- else reject(new EvalError(m.error.message, m.error.name, m.error.code));
3211
+ else reject(new EvalError(m.error.message, m.error.name, m.error.code, m.error.kind ?? "other"));
3165
3212
  });
3166
3213
  });
3167
- worker.on("error", (e) => finish(() => reject(e)));
3214
+ worker.on("error", (e) => finish(() => reject(new EvalError(e?.message ?? String(e), builtinErrorName(e), void 0, "other"))));
3168
3215
  worker.on("exit", (code) => finish(() => reject(new Error(`評価の Worker が結果を返さずに終わった(exit ${code})`))));
3169
3216
  });
3170
3217
  }
@@ -3770,13 +3817,18 @@ ${USAGE}`);
3770
3817
  return 1;
3771
3818
  }
3772
3819
  if (e instanceof EvalError) {
3773
- if (["PluginZipError", "InputError", "UsersDataError", "PolicyError", "AuthError"].includes(e.innerName)) {
3820
+ if (e.kind !== "other") {
3774
3821
  console.error(e.message);
3775
3822
  return 1;
3776
3823
  }
3777
- throw new Error(`${e.innerName}: ${e.message}`);
3824
+ console.error(`評価の途中で想定外の誤り(${e.innerName})。誤りの文は値を含みうるので表示しない(FORMULA_DEBUG=1 で表示)。normalize で設定を、version で zip を確かめる`);
3825
+ if (W.env.FORMULA_DEBUG === "1") console.error(e.message);
3826
+ return 1;
3778
3827
  }
3779
- throw e;
3828
+ const name = builtinErrorName(e);
3829
+ console.error(`想定外の誤り(${name})。誤りの文は値を含みうるので表示しない(FORMULA_DEBUG=1 で表示)`);
3830
+ if (W.env.FORMULA_DEBUG === "1") console.error(e instanceof Error ? e.stack ?? e.message : String(e));
3831
+ return 1;
3780
3832
  }
3781
3833
  }
3782
3834
  var invokedRaw = process.argv[1] ? path14.resolve(process.argv[1]) : "";
@@ -3795,6 +3847,9 @@ try {
3795
3847
  if (invoked && (invoked === self || /[\\/]dist[\\/]cli\.mjs$/.test(invoked))) {
3796
3848
  main(process.argv.slice(2)).then((code) => {
3797
3849
  process.exitCode = code;
3850
+ }, (e) => {
3851
+ console.error(`想定外の誤り(${builtinErrorName(e)})`);
3852
+ process.exitCode = 1;
3798
3853
  });
3799
3854
  }
3800
3855
  export {