@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 +5 -2
- package/dist/cli.mjs +74 -19
- package/dist/core.mjs +1403 -38
- package/dist/eval-worker.mjs +42 -14
- package/dist/types/commands/save.d.ts +86 -0
- package/dist/types/commands/take.d.ts +18 -0
- package/dist/types/commit-file.d.ts +38 -0
- package/dist/types/connections.d.ts +106 -0
- package/dist/types/core.d.ts +21 -13
- package/dist/types/engine.d.ts +4 -0
- package/dist/types/permission-error.d.ts +3 -0
- package/dist/types/permission.d.ts +10 -0
- package/dist/types/worker/eval-job.d.ts +24 -0
- package/dist/types/worker/eval-run.d.ts +11 -1
- package/dist/types/worker/run.d.ts +11 -3
- package/dist/types/workspace.d.ts +126 -0
- package/package.json +2 -1
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/
|
|
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 から写してまだ計算式用にしていないもの(`
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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}
|
|
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: {
|
|
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
|
-
|
|
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)
|
|
3145
|
-
|
|
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:
|
|
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 (
|
|
3820
|
+
if (e.kind !== "other") {
|
|
3774
3821
|
console.error(e.message);
|
|
3775
3822
|
return 1;
|
|
3776
3823
|
}
|
|
3777
|
-
|
|
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
|
-
|
|
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 {
|