@rex0220/print-craft-authoring-tools 1.0.0 → 1.2.0
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 +14 -10
- package/SECURITY.md +7 -3
- package/dist/cli.mjs +579 -317
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/*! @rex0220/print-craft-authoring-tools 1.
|
|
2
|
+
/*! @rex0220/print-craft-authoring-tools 1.2.0 (c) rex0220. MIT License. 計算式エンジンと印刷屋のコードは利用者の印刷屋プラグインの zip から読む(このファイルには含まれない)。commit 390cbac. */
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import
|
|
5
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6
|
+
import path13 from "node:path";
|
|
7
7
|
|
|
8
8
|
// src/env.ts
|
|
9
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -49,9 +49,13 @@ var NAMES = {
|
|
|
49
49
|
token: ["KINTONE_API_TOKEN", "KSQL_TOKEN"],
|
|
50
50
|
username: ["KINTONE_USERNAME", "KSQL_USERNAME"],
|
|
51
51
|
password: ["KINTONE_PASSWORD", "KSQL_PASSWORD"],
|
|
52
|
-
pluginZip: ["PCRAFT_PLUGIN_ZIP"]
|
|
53
|
-
allowUnknown: ["PCRAFT_ALLOW_UNKNOWN_PLUGIN"]
|
|
52
|
+
pluginZip: ["PCRAFT_PLUGIN_ZIP"]
|
|
54
53
|
};
|
|
54
|
+
function unquote(raw) {
|
|
55
|
+
const value = raw.trim();
|
|
56
|
+
if (value.length >= 2 && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) return value.slice(1, -1);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
55
59
|
function parseDotEnv(text) {
|
|
56
60
|
const out = {};
|
|
57
61
|
for (const raw of text.split(/\r?\n/)) {
|
|
@@ -59,33 +63,36 @@ function parseDotEnv(text) {
|
|
|
59
63
|
if (!line || line.startsWith("#")) continue;
|
|
60
64
|
const m = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
61
65
|
if (!m) continue;
|
|
62
|
-
|
|
63
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
64
|
-
out[m[1]] = value;
|
|
66
|
+
out[m[1]] = unquote(m[2]);
|
|
65
67
|
}
|
|
66
68
|
return out;
|
|
67
69
|
}
|
|
68
70
|
var AuthError = class extends Error {
|
|
69
71
|
};
|
|
70
|
-
function
|
|
71
|
-
const osEnv = opt.env
|
|
72
|
+
function pickerWithSource(opt, unquoteProcess = false) {
|
|
73
|
+
const osEnv = opt.env;
|
|
72
74
|
const file = envFileOf(opt);
|
|
73
75
|
const fromFile = existsSync(file) ? parseDotEnv(readFileSync(file, "utf8")) : {};
|
|
74
76
|
return (names) => {
|
|
75
|
-
for (const src of [osEnv, fromFile]) {
|
|
77
|
+
for (const [src, source] of [[osEnv, "process"], [fromFile, "file"]]) {
|
|
76
78
|
for (const n of names) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
+
const raw = src[n] ?? "";
|
|
80
|
+
const v = source === "process" && unquoteProcess ? unquote(raw) : raw.trim();
|
|
81
|
+
if (v) return { value: v, source, ...source === "file" ? { file } : {} };
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
return void 0;
|
|
82
85
|
};
|
|
83
86
|
}
|
|
87
|
+
function picker(opt, unquoteProcess = false) {
|
|
88
|
+
const pick = pickerWithSource(opt, unquoteProcess);
|
|
89
|
+
return (names) => pick(names)?.value;
|
|
90
|
+
}
|
|
84
91
|
function envFileOf(opt) {
|
|
85
|
-
return opt.envFile ?? path.join(opt.cwd
|
|
92
|
+
return opt.envFile ?? path.join(opt.cwd, ".env");
|
|
86
93
|
}
|
|
87
|
-
function baseUrlFromEnv(opt
|
|
88
|
-
const raw = picker(opt)(NAMES.baseUrl);
|
|
94
|
+
function baseUrlFromEnv(opt) {
|
|
95
|
+
const raw = picker(opt, true)(NAMES.baseUrl);
|
|
89
96
|
if (!raw) return void 0;
|
|
90
97
|
try {
|
|
91
98
|
return normalizeKintoneBaseUrl(raw);
|
|
@@ -93,7 +100,7 @@ function baseUrlFromEnv(opt = {}) {
|
|
|
93
100
|
throw new AuthError(`KINTONE_BASE_URL が不正: ${e instanceof KintoneUrlError ? e.message : String(e)}(https://<サブドメイン>.cybozu.com の形で書く)`);
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
|
-
function loadAuth(opt
|
|
103
|
+
function loadAuth(opt) {
|
|
97
104
|
const pick = picker(opt);
|
|
98
105
|
const file = envFileOf(opt);
|
|
99
106
|
const baseUrl = baseUrlFromEnv(opt);
|
|
@@ -104,7 +111,7 @@ function loadAuth(opt = {}) {
|
|
|
104
111
|
if (!token && !(username && password)) throw new AuthError("KINTONE_API_TOKEN か、KINTONE_USERNAME と KINTONE_PASSWORD の両方が要る(OS の環境変数か .env)");
|
|
105
112
|
return { baseUrl, token, username: token ? void 0 : username, password: token ? void 0 : password };
|
|
106
113
|
}
|
|
107
|
-
function loadAuthForEnv(env, cwd
|
|
114
|
+
function loadAuthForEnv(env, cwd) {
|
|
108
115
|
const file = path.join(cwd, env.envFile);
|
|
109
116
|
if (!existsSync(file)) throw new AuthError(`環境「${env.name}」の認証のファイルが無い: ${env.envFile}(${env.envFile === ".env" ? ".env.example を写して作る" : "env/ に作る。.env と同じ書き方"})`);
|
|
110
117
|
const vars = parseDotEnv(readFileSync(file, "utf8"));
|
|
@@ -128,13 +135,12 @@ function loadAuthForEnv(env, cwd = process.cwd()) {
|
|
|
128
135
|
if (!token && !(username && password)) throw new AuthError(`${env.envFile} に KINTONE_API_TOKEN か、KINTONE_USERNAME と KINTONE_PASSWORD の両方が要る(環境「${env.name}」。OS の環境変数は読まない)`);
|
|
129
136
|
return { baseUrl: env.baseUrl, token, username: token ? void 0 : username, password: token ? void 0 : password };
|
|
130
137
|
}
|
|
131
|
-
function pluginZipPath(opt
|
|
132
|
-
const
|
|
133
|
-
if (!
|
|
134
|
-
return path.resolve(path.dirname(envFileOf(opt)),
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return picker(opt)(NAMES.allowUnknown) === "1";
|
|
138
|
+
function pluginZipPath(opt) {
|
|
139
|
+
const picked = pickerWithSource(opt, true)(NAMES.pluginZip);
|
|
140
|
+
if (!picked) return void 0;
|
|
141
|
+
if (picked.source === "file") return path.resolve(path.dirname(picked.file ?? envFileOf(opt)), picked.value);
|
|
142
|
+
if (!path.isAbsolute(picked.value)) throw new AuthError(`PCRAFT_PLUGIN_ZIP(OS の環境変数)は絶対パスで書く: ${picked.value}(.env に書くなら .env のフォルダーからの相対パスでもよい)`);
|
|
143
|
+
return path.normalize(picked.value);
|
|
138
144
|
}
|
|
139
145
|
function describeAuth(auth) {
|
|
140
146
|
return auth.token ? "API トークン" : "ログインユーザー";
|
|
@@ -161,7 +167,7 @@ function norm(p) {
|
|
|
161
167
|
const r = path2.resolve(p);
|
|
162
168
|
return process.platform === "win32" ? r.toLowerCase() : r;
|
|
163
169
|
}
|
|
164
|
-
function realResolve(target, cwd
|
|
170
|
+
function realResolve(target, cwd) {
|
|
165
171
|
const abs = path2.resolve(cwd, target);
|
|
166
172
|
let dir = abs;
|
|
167
173
|
const rest = [];
|
|
@@ -194,7 +200,7 @@ function isInside(child, parent) {
|
|
|
194
200
|
const p = norm(parent);
|
|
195
201
|
return c === p || c.startsWith(p.endsWith(path2.sep) ? p : p + path2.sep);
|
|
196
202
|
}
|
|
197
|
-
function resolveRead(target, cwd
|
|
203
|
+
function resolveRead(target, cwd) {
|
|
198
204
|
if (!target || typeof target !== "string") throw new PathError("ファイルのパスが要る");
|
|
199
205
|
const real = realResolve(target, cwd);
|
|
200
206
|
const cwdReal = realResolve(".", cwd);
|
|
@@ -203,7 +209,7 @@ function resolveRead(target, cwd = process.cwd()) {
|
|
|
203
209
|
if (/^\.env(\.|$)/i.test(path2.basename(real)) || /^(node_modules|\.git)(\/|$)/i.test(rel)) throw new PathError(`このファイルは tools では読まない: ${target}`);
|
|
204
210
|
return real;
|
|
205
211
|
}
|
|
206
|
-
function resolveWrite(target, roots, cwd
|
|
212
|
+
function resolveWrite(target, roots, cwd) {
|
|
207
213
|
if (!target || typeof target !== "string") throw new PathError("書き込み先のパスが要る");
|
|
208
214
|
const real = realResolve(target, cwd);
|
|
209
215
|
const cwdReal = realResolve(".", cwd);
|
|
@@ -217,7 +223,7 @@ function resolveWrite(target, roots, cwd = process.cwd()) {
|
|
|
217
223
|
if (WINDOWS_RESERVED.test(base) || /[. ]$/.test(base)) throw new PathError(`使えないファイル名: ${base}`);
|
|
218
224
|
return real;
|
|
219
225
|
}
|
|
220
|
-
function resolveWriteDir(target, roots, cwd
|
|
226
|
+
function resolveWriteDir(target, roots, cwd) {
|
|
221
227
|
if (!target || typeof target !== "string") throw new PathError("フォルダーのパスが要る");
|
|
222
228
|
const real = realResolve(target, cwd);
|
|
223
229
|
const cwdReal = realResolve(".", cwd);
|
|
@@ -243,6 +249,7 @@ var ENV_FILE = /^(\.env|env\/[A-Za-z0-9._-]{1,64}\.env)$/;
|
|
|
243
249
|
var MAX_BYTES = 256 * 1024;
|
|
244
250
|
var WorkspaceError = class extends Error {
|
|
245
251
|
};
|
|
252
|
+
var ENV_ROLES = ["development", "production"];
|
|
246
253
|
var isEnvName = (s) => ENV_NAME.test(s);
|
|
247
254
|
function parseWorkspace(text, where = WORKSPACE_FILE) {
|
|
248
255
|
let raw;
|
|
@@ -262,7 +269,9 @@ function parseWorkspace(text, where = WORKSPACE_FILE) {
|
|
|
262
269
|
for (const [name, v] of Object.entries(envsRaw)) {
|
|
263
270
|
if (!isEnvName(name)) throw new WorkspaceError(`${where}: 環境の名前は英数字と _ - の 32 文字まで: ${name}`);
|
|
264
271
|
const e = obj(v, `environments.${name}`);
|
|
265
|
-
for (const k of Object.keys(e)) if (!["baseUrl", "envFile"].includes(k)) throw new WorkspaceError(`${where}: environments.${name} に使えるキーは baseUrl / envFile: ${k}`);
|
|
272
|
+
for (const k of Object.keys(e)) if (!["baseUrl", "envFile", "role"].includes(k)) throw new WorkspaceError(`${where}: environments.${name} に使えるキーは baseUrl / envFile / role: ${k}`);
|
|
273
|
+
if (e.role !== void 0 && !ENV_ROLES.includes(e.role)) throw new WorkspaceError(`${where}: environments.${name}.role は development(開発)か production(本番): ${String(e.role)}`);
|
|
274
|
+
const role = e.role === void 0 ? "unclassified" : e.role;
|
|
266
275
|
let baseUrl;
|
|
267
276
|
try {
|
|
268
277
|
baseUrl = normalizeKintoneBaseUrl(String(e.baseUrl ?? ""));
|
|
@@ -271,7 +280,7 @@ function parseWorkspace(text, where = WORKSPACE_FILE) {
|
|
|
271
280
|
}
|
|
272
281
|
const envFile = e.envFile === void 0 ? ".env" : String(e.envFile).replace(/\\/g, "/");
|
|
273
282
|
if (!ENV_FILE.test(envFile)) throw new WorkspaceError(`${where}: environments.${name}.envFile は .env か env/<名前>.env: ${envFile}`);
|
|
274
|
-
environments[name] = { name, baseUrl, host: new URL(baseUrl).hostname, envFile };
|
|
283
|
+
environments[name] = { name, role, baseUrl, host: new URL(baseUrl).hostname, envFile };
|
|
275
284
|
}
|
|
276
285
|
if (Object.keys(environments).length === 0) throw new WorkspaceError(`${where}: environments に環境が 1 つも無い`);
|
|
277
286
|
const defaultEnv = root.default === void 0 ? Object.keys(environments)[0] : String(root.default);
|
|
@@ -298,7 +307,7 @@ function parseWorkspace(text, where = WORKSPACE_FILE) {
|
|
|
298
307
|
}
|
|
299
308
|
return { defaultEnv, environments, apps };
|
|
300
309
|
}
|
|
301
|
-
function loadWorkspace(cwd
|
|
310
|
+
function loadWorkspace(cwd) {
|
|
302
311
|
const file = path3.join(cwd, WORKSPACE_FILE);
|
|
303
312
|
if (!existsSync2(file)) return null;
|
|
304
313
|
const size = statSync(file).size;
|
|
@@ -381,14 +390,20 @@ function listAppFolder(dir) {
|
|
|
381
390
|
function envsOfHost(ws, host) {
|
|
382
391
|
return Object.values(ws.environments).filter((e) => e.host === host);
|
|
383
392
|
}
|
|
393
|
+
function envOfAppFolder(ws, host, appId) {
|
|
394
|
+
const hits = envsOfHost(ws, host);
|
|
395
|
+
if (hits.length === 1) return hits[0];
|
|
396
|
+
const byApp = hits.filter((e) => ws.apps.some((a) => a.ids[e.name] === appId));
|
|
397
|
+
return byApp.length === 1 ? byApp[0] : void 0;
|
|
398
|
+
}
|
|
384
399
|
|
|
385
400
|
// src/commands/take.ts
|
|
386
|
-
import { existsSync as
|
|
387
|
-
import
|
|
401
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync7, unlinkSync, mkdirSync } from "node:fs";
|
|
402
|
+
import path10 from "node:path";
|
|
388
403
|
|
|
389
404
|
// src/commands/normalize.ts
|
|
390
|
-
import { readFileSync as
|
|
391
|
-
import
|
|
405
|
+
import { readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
|
|
406
|
+
import path7 from "node:path";
|
|
392
407
|
|
|
393
408
|
// src/normalize/findings.ts
|
|
394
409
|
var Findings = class {
|
|
@@ -422,6 +437,96 @@ var Findings = class {
|
|
|
422
437
|
}
|
|
423
438
|
};
|
|
424
439
|
|
|
440
|
+
// src/meta.ts
|
|
441
|
+
import { execFileSync } from "node:child_process";
|
|
442
|
+
import { createHash } from "node:crypto";
|
|
443
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
444
|
+
import path5 from "node:path";
|
|
445
|
+
|
|
446
|
+
// src/paths.ts
|
|
447
|
+
import path4 from "node:path";
|
|
448
|
+
import { fileURLToPath } from "node:url";
|
|
449
|
+
var here = path4.dirname(fileURLToPath(import.meta.url));
|
|
450
|
+
var AUTHORING_ROOT = path4.resolve(here, "..");
|
|
451
|
+
var REPO_ROOT = path4.resolve(AUTHORING_ROOT, "..");
|
|
452
|
+
var VENDOR_DIR = path4.join(AUTHORING_ROOT, "vendor");
|
|
453
|
+
var MOMENT_FILE = path4.join(VENDOR_DIR, "moment-with-locales.min.js");
|
|
454
|
+
|
|
455
|
+
// src/meta.ts
|
|
456
|
+
var PRINT_CRAFT_PLUGIN_ID = "lcapkanpjdabgphknkabojmcfhonhkhp";
|
|
457
|
+
var MIN_PLUGIN_VERSION = 6;
|
|
458
|
+
var SUPPORTED_API_VERSIONS = [1, 2];
|
|
459
|
+
function isSupportedPluginVersion(version2) {
|
|
460
|
+
return /^[1-9]\d{0,5}$/.test(version2) && Number(version2) >= MIN_PLUGIN_VERSION;
|
|
461
|
+
}
|
|
462
|
+
var REQUIRED_API = {
|
|
463
|
+
apiVersion: "number",
|
|
464
|
+
pluginVersion: "string",
|
|
465
|
+
pluginId: "string",
|
|
466
|
+
CONFIG_SCHEMA: "object",
|
|
467
|
+
CONFIG_LIMITS: "object",
|
|
468
|
+
PAGE_SIZES: "object",
|
|
469
|
+
DPI_OPTIONS: "object",
|
|
470
|
+
PAPER_NAMES: "object",
|
|
471
|
+
PRINT_MODES: "object",
|
|
472
|
+
buildMenuRows: "function",
|
|
473
|
+
normalizeTagsInfo: "function",
|
|
474
|
+
normalizeCssRows: "function",
|
|
475
|
+
toSavedRows: "function",
|
|
476
|
+
computeUsage: "function",
|
|
477
|
+
computePluginUOG: "function",
|
|
478
|
+
createCheckRecord: "function",
|
|
479
|
+
createFieldsInfo: "function",
|
|
480
|
+
isTargetType: "function",
|
|
481
|
+
stripComments: "function",
|
|
482
|
+
defaultCssRows: "function",
|
|
483
|
+
defaultMenuInfo: "function",
|
|
484
|
+
expandFields: "function",
|
|
485
|
+
buildReportCss: "function",
|
|
486
|
+
fileNameOf: "function",
|
|
487
|
+
formulaOf: "function",
|
|
488
|
+
mountReportHtml: "function",
|
|
489
|
+
replaceTags: "function",
|
|
490
|
+
getPaperSize: "function",
|
|
491
|
+
webFontOf: "function",
|
|
492
|
+
DUMMY_IMAGE: "string",
|
|
493
|
+
validate: "function",
|
|
494
|
+
parseJsonSafely: "function",
|
|
495
|
+
ValidationError: "function",
|
|
496
|
+
writeConfig: "function",
|
|
497
|
+
DEFAULT_LIMITS: "object",
|
|
498
|
+
ConfigStoreError: "function",
|
|
499
|
+
// pull(アプリのプラグインの設定を読んで封筒形式にする。2026-10-05)
|
|
500
|
+
readConfig: "function",
|
|
501
|
+
buildExportData: "function"
|
|
502
|
+
};
|
|
503
|
+
function stableJson(v) {
|
|
504
|
+
return JSON.stringify(v, (_k, x) => x instanceof RegExp ? `/${x.source}/${x.flags}` : x);
|
|
505
|
+
}
|
|
506
|
+
function sha256Hex(data) {
|
|
507
|
+
return createHash("sha256").update(data).digest("hex");
|
|
508
|
+
}
|
|
509
|
+
function schemaRevisionOf(schema) {
|
|
510
|
+
return sha256Hex(stableJson(schema)).slice(0, 12);
|
|
511
|
+
}
|
|
512
|
+
function gitCommit(dir) {
|
|
513
|
+
try {
|
|
514
|
+
const head = execFileSync("git", ["-C", dir, "rev-parse", "--short", "HEAD"], { encoding: "utf8" }).trim();
|
|
515
|
+
const dirty = execFileSync("git", ["-C", dir, "status", "--porcelain", "--untracked-files=no"], { encoding: "utf8" }).trim() !== "";
|
|
516
|
+
return dirty ? `${head}+dirty` : head;
|
|
517
|
+
} catch {
|
|
518
|
+
return "unknown";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function devMeta() {
|
|
522
|
+
const pkg = JSON.parse(readFileSync3(path5.join(AUTHORING_ROOT, "package.json"), "utf8"));
|
|
523
|
+
return { toolsVersion: pkg.version, pluginId: PRINT_CRAFT_PLUGIN_ID, minPluginVersion: MIN_PLUGIN_VERSION, supportedApiVersions: [...SUPPORTED_API_VERSIONS], commit: gitCommit(AUTHORING_ROOT), builtAt: "", mode: "dev" };
|
|
524
|
+
}
|
|
525
|
+
function toolsMeta() {
|
|
526
|
+
if (true) return JSON.parse('{"toolsVersion":"1.2.0","pluginId":"lcapkanpjdabgphknkabojmcfhonhkhp","minPluginVersion":6,"supportedApiVersions":[1,2],"commit":"390cbac","builtAt":"2026-10-09T12:21:22.862Z","mode":"build"}');
|
|
527
|
+
return devMeta();
|
|
528
|
+
}
|
|
529
|
+
|
|
425
530
|
// src/normalize/model.ts
|
|
426
531
|
var PSEUDO_FIELDS = /* @__PURE__ */ new Set(["$id", "$revision", "$out", "$html", "$rseq"]);
|
|
427
532
|
var UOG_MARK = "$UGO$";
|
|
@@ -937,9 +1042,9 @@ async function checkHtml(html, opt = {}) {
|
|
|
937
1042
|
const doc = new w.DOMParser().parseFromString(`<!doctype html><html><body>${html}</body></html>`, "text/html");
|
|
938
1043
|
const root = doc.body ?? doc.documentElement;
|
|
939
1044
|
if (!root) return { errors, warnings, externals };
|
|
940
|
-
const walk = (el,
|
|
1045
|
+
const walk = (el, path14) => {
|
|
941
1046
|
const tag = el.tagName.toLowerCase();
|
|
942
|
-
const here2 = `${
|
|
1047
|
+
const here2 = `${path14}>${tag}`;
|
|
943
1048
|
const denied = tagError(tag, here2);
|
|
944
1049
|
if (denied) {
|
|
945
1050
|
errors.push(denied);
|
|
@@ -1002,11 +1107,11 @@ function expressionsOf(html) {
|
|
|
1002
1107
|
}
|
|
1003
1108
|
|
|
1004
1109
|
// src/normalize/policy.ts
|
|
1005
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1006
|
-
import
|
|
1110
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync2 } from "node:fs";
|
|
1111
|
+
import path6 from "node:path";
|
|
1007
1112
|
var PolicyError = class extends Error {
|
|
1008
1113
|
};
|
|
1009
|
-
var POLICY_FILE =
|
|
1114
|
+
var POLICY_FILE = path6.join("policy", "authoring-policy.json");
|
|
1010
1115
|
var DEFAULT_ALLOWED_ORIGINS = ["https://fonts.googleapis.com", "https://fonts.gstatic.com"];
|
|
1011
1116
|
var MAX_POLICY_BYTES = 1024 * 1024;
|
|
1012
1117
|
var MAX_RULES = 200;
|
|
@@ -1103,11 +1208,11 @@ function isExternalRefsAllowed(policy, settingsFile) {
|
|
|
1103
1208
|
return false;
|
|
1104
1209
|
}
|
|
1105
1210
|
}
|
|
1106
|
-
function loadPolicy(opt
|
|
1107
|
-
const file =
|
|
1211
|
+
function loadPolicy(opt) {
|
|
1212
|
+
const file = path6.join(opt.cwd, POLICY_FILE);
|
|
1108
1213
|
if (!existsSync3(file)) return { allowExternal: [] };
|
|
1109
1214
|
if (statSync2(file).size > MAX_POLICY_BYTES) throw new PolicyError(`${file} が大きすぎる(${MAX_POLICY_BYTES} バイトまで)`);
|
|
1110
|
-
return parsePolicy(
|
|
1215
|
+
return parsePolicy(readFileSync4(file, "utf8"), file);
|
|
1111
1216
|
}
|
|
1112
1217
|
function originOf(url) {
|
|
1113
1218
|
try {
|
|
@@ -1510,7 +1615,10 @@ async function normalizeSettings(input) {
|
|
|
1510
1615
|
return { findings: f, summary: "封筒形式でない" };
|
|
1511
1616
|
}
|
|
1512
1617
|
if (envelope.pluginID !== api.pluginId) f.error("envelope.pluginID", where, `pluginID は "${api.pluginId}": ${JSON.stringify(envelope.pluginID)}(封筒なしの素の設定は作らない)`);
|
|
1513
|
-
|
|
1618
|
+
const envVersion = String(envelope.PluginVersion ?? "");
|
|
1619
|
+
if (!isSupportedPluginVersion(envVersion)) f.error("envelope.version", where, `PluginVersion は印刷屋の版(Ver.${MIN_PLUGIN_VERSION} 以降、zip は ${pluginVersion}): ${JSON.stringify(envelope.PluginVersion)}`);
|
|
1620
|
+
else if (Number(envVersion) > Number(pluginVersion)) f.error("envelope.version", where, `PluginVersion ${envVersion} の設定を、それより古い印刷屋の zip(版 ${pluginVersion})で検査しようとしている。アプリに入れた版の zip を .env の PCRAFT_PLUGIN_ZIP に書く`);
|
|
1621
|
+
else if (envVersion !== pluginVersion) f.info("envelope.version.upgrade", where, `PluginVersion ${envVersion} の設定を印刷屋の版 ${pluginVersion}(zip)で検査し、書き出す封筒は ${pluginVersion} にする`);
|
|
1514
1622
|
if (envelope.appId !== void 0) {
|
|
1515
1623
|
if (typeof envelope.appId !== "number" || !Number.isInteger(envelope.appId) || envelope.appId <= 0) f.error("envelope.appId", where, `appId は正の整数(数値): ${JSON.stringify(envelope.appId)}`);
|
|
1516
1624
|
else if (envelope.appId !== input.fields.appId) f.warning("envelope.appId", where, `appId ${envelope.appId} が fields のアプリ ${input.fields.appId} と違う`);
|
|
@@ -1571,7 +1679,7 @@ async function normalizeSettings(input) {
|
|
|
1571
1679
|
function readTextLimited(file, maxBytes = MAX_INPUT_BYTES) {
|
|
1572
1680
|
const size = statSync3(file).size;
|
|
1573
1681
|
if (size > maxBytes) throw new InputError(`${file} が大きすぎる(${size.toLocaleString()} バイト。上限 ${maxBytes.toLocaleString()})`);
|
|
1574
|
-
return
|
|
1682
|
+
return readFileSync5(file, "utf8");
|
|
1575
1683
|
}
|
|
1576
1684
|
function readJsonLimited(file, maxBytes = MAX_INPUT_BYTES) {
|
|
1577
1685
|
let v;
|
|
@@ -1590,13 +1698,157 @@ async function readFieldsFile(file) {
|
|
|
1590
1698
|
if (typeof fields2.appId !== "number" || !Number.isInteger(fields2.appId) || fields2.appId <= 0) throw new InputError(`${file} の appId が正の整数でない`);
|
|
1591
1699
|
return fields2;
|
|
1592
1700
|
}
|
|
1593
|
-
function relativeSettingsPath(file) {
|
|
1594
|
-
const rel =
|
|
1701
|
+
function relativeSettingsPath(file, cwd) {
|
|
1702
|
+
const rel = path7.relative(cwd, path7.resolve(cwd, file));
|
|
1595
1703
|
return rel.startsWith("..") ? file : rel.replace(/\\/g, "/");
|
|
1596
1704
|
}
|
|
1597
1705
|
|
|
1706
|
+
// src/permission.ts
|
|
1707
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1708
|
+
import path8 from "node:path";
|
|
1709
|
+
var PermissionError = class extends Error {
|
|
1710
|
+
};
|
|
1711
|
+
var ALLOWED_CLASSES = {
|
|
1712
|
+
settings: ["edit", "report"],
|
|
1713
|
+
snapshot: ["snapshot"],
|
|
1714
|
+
fields: ["fields"],
|
|
1715
|
+
record: ["record"],
|
|
1716
|
+
preview: ["out"]
|
|
1717
|
+
};
|
|
1718
|
+
var OP_LABEL = {
|
|
1719
|
+
settings: "設定(-edit.json と新しい帳票)",
|
|
1720
|
+
snapshot: "新しい名前のダウンロード / pull",
|
|
1721
|
+
fields: "fields.json",
|
|
1722
|
+
record: "records/ の下",
|
|
1723
|
+
preview: "out/ の下"
|
|
1724
|
+
};
|
|
1725
|
+
function classOfAppPath(relInFolder) {
|
|
1726
|
+
const rel = relInFolder.replace(/\\/g, "/");
|
|
1727
|
+
if (rel.startsWith("records/")) return rel.toLowerCase().endsWith(".json") ? "record" : "other";
|
|
1728
|
+
if (rel.startsWith("out/")) return "out";
|
|
1729
|
+
if (rel.includes("/")) return "other";
|
|
1730
|
+
if (rel === "fields.json") return "fields";
|
|
1731
|
+
if (SNAPSHOT_RE.test(rel)) return "snapshot";
|
|
1732
|
+
if (EDIT_RE.test(rel)) return "edit";
|
|
1733
|
+
if (rel.toLowerCase().endsWith(".json")) return "report";
|
|
1734
|
+
return "other";
|
|
1735
|
+
}
|
|
1736
|
+
function roleOfTarget(root, file) {
|
|
1737
|
+
const abs = path8.resolve(root, file);
|
|
1738
|
+
if (!isInside(abs, path8.join(root, KINTONE_ROOT))) return null;
|
|
1739
|
+
const ws = loadWorkspace(root);
|
|
1740
|
+
const folder = appFolderOfFile(root, abs);
|
|
1741
|
+
const cls = folder ? classOfAppPath(path8.relative(folder.dir, abs)) : void 0;
|
|
1742
|
+
if (!ws) return { role: "unclassified", why: `${WORKSPACE_FILE} が無い`, cls };
|
|
1743
|
+
if (!folder) return { role: "unclassified", why: "アプリのフォルダー(kintone/<ホスト名>/<番号>-…/)の中でない" };
|
|
1744
|
+
const env = envOfAppFolder(ws, folder.host, folder.appId);
|
|
1745
|
+
if (!env) return { role: "unclassified", why: `フォルダー(${folder.host} のアプリ ${folder.appId})がどの環境のものか ${WORKSPACE_FILE} から決まらない(同じホストの環境が 2 つ以上あるときは、apps にアプリの番号を足す)`, cls };
|
|
1746
|
+
return env.role === "unclassified" ? { role: "unclassified", envName: env.name, why: `環境「${env.name}」に role が無い`, cls } : { role: env.role, envName: env.name, cls };
|
|
1747
|
+
}
|
|
1748
|
+
function assertChangeAllowed(root, file, op) {
|
|
1749
|
+
const target = roleOfTarget(root, file);
|
|
1750
|
+
if (!target) return;
|
|
1751
|
+
const abs = path8.resolve(root, file);
|
|
1752
|
+
if (target.role === "unclassified") {
|
|
1753
|
+
throw new PermissionError(`${target.why}ので kintone/ の下を変更できない。${WORKSPACE_FILE} の環境に role(development か production)を書く(利用者が書く)`);
|
|
1754
|
+
}
|
|
1755
|
+
if (target.role === "production" && op !== "snapshot") {
|
|
1756
|
+
throw new PermissionError(`環境「${target.envName}」は本番(role: production)なので変更できない。開発の環境で作る・直す(本番のフォルダーは pull / ダウンロードで今の設定を見るだけ)`);
|
|
1757
|
+
}
|
|
1758
|
+
const cls = target.cls ?? "other";
|
|
1759
|
+
if (cls === "snapshot" && existsSync4(abs)) {
|
|
1760
|
+
throw new PermissionError(`ダウンロード / pull のファイルは書き換えない: ${path8.relative(root, abs)}。edit で -edit.json に写して直す`);
|
|
1761
|
+
}
|
|
1762
|
+
if (!ALLOWED_CLASSES[op].includes(cls)) {
|
|
1763
|
+
throw new PermissionError(`この操作でアプリのフォルダーに書けるのは ${OP_LABEL[op]} だけ: ${path8.relative(root, abs)}`);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// src/commit-file.ts
|
|
1768
|
+
import { randomBytes } from "node:crypto";
|
|
1769
|
+
import { constants, copyFileSync, linkSync, lstatSync as lstatSync2, readFileSync as readFileSync6, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1770
|
+
import path9 from "node:path";
|
|
1771
|
+
var FileExistsError = class extends Error {
|
|
1772
|
+
};
|
|
1773
|
+
var errCode = (e) => e?.code;
|
|
1774
|
+
var rand = () => randomBytes(6).toString("hex");
|
|
1775
|
+
function sleepSync(ms) {
|
|
1776
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
1777
|
+
}
|
|
1778
|
+
function withRetry(fn, tries = 3) {
|
|
1779
|
+
for (let i = 1; ; i++) {
|
|
1780
|
+
try {
|
|
1781
|
+
return fn();
|
|
1782
|
+
} catch (e) {
|
|
1783
|
+
const code = errCode(e);
|
|
1784
|
+
if (i < tries && (code === "EPERM" || code === "EBUSY")) {
|
|
1785
|
+
sleepSync(20 * i);
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
throw e;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function cleanupAll(steps, tries = 3) {
|
|
1793
|
+
const errors = [];
|
|
1794
|
+
for (const step of steps) {
|
|
1795
|
+
try {
|
|
1796
|
+
withRetry(step, tries);
|
|
1797
|
+
} catch (e) {
|
|
1798
|
+
errors.push(e instanceof Error ? e.message : String(e));
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return errors;
|
|
1802
|
+
}
|
|
1803
|
+
function tempPathFor(target) {
|
|
1804
|
+
return path9.join(path9.dirname(target), `.${path9.basename(target)}.${rand()}.tmp`);
|
|
1805
|
+
}
|
|
1806
|
+
function placeNew(tmp, target, link = linkSync) {
|
|
1807
|
+
try {
|
|
1808
|
+
link(tmp, target);
|
|
1809
|
+
return;
|
|
1810
|
+
} catch (e) {
|
|
1811
|
+
const code = errCode(e);
|
|
1812
|
+
if (code === "EEXIST") throw new FileExistsError(`確定の直前に同じ名前のファイルができた: ${path9.basename(target)}`);
|
|
1813
|
+
if (code !== "EPERM" && code !== "ENOTSUP" && code !== "EXDEV" && code !== "ENOSYS") throw e;
|
|
1814
|
+
}
|
|
1815
|
+
try {
|
|
1816
|
+
copyFileSync(tmp, target, constants.COPYFILE_EXCL);
|
|
1817
|
+
} catch (e) {
|
|
1818
|
+
if (errCode(e) === "EEXIST") throw new FileExistsError(`確定の直前に同じ名前のファイルができた: ${path9.basename(target)}`);
|
|
1819
|
+
throw e;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
function writeNewFile(target, text, recheck = () => {
|
|
1823
|
+
}) {
|
|
1824
|
+
const tmp = tempPathFor(target);
|
|
1825
|
+
let placed = false;
|
|
1826
|
+
let failure;
|
|
1827
|
+
try {
|
|
1828
|
+
recheck();
|
|
1829
|
+
writeFileSync(tmp, text, { encoding: "utf8", flag: "wx" });
|
|
1830
|
+
recheck();
|
|
1831
|
+
placeNew(tmp, target);
|
|
1832
|
+
placed = true;
|
|
1833
|
+
} catch (e) {
|
|
1834
|
+
failure = e;
|
|
1835
|
+
}
|
|
1836
|
+
const cleanup = cleanupAll([() => rmSync(tmp, { force: true })]);
|
|
1837
|
+
if (!placed) throw failure;
|
|
1838
|
+
return { cleanup };
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1598
1841
|
// src/commands/take.ts
|
|
1599
|
-
var
|
|
1842
|
+
var PRINT_CRAFT_PLUGIN_ID2 = "rex0220 Print craft plugin";
|
|
1843
|
+
function removeSource(src, warnings, rel) {
|
|
1844
|
+
try {
|
|
1845
|
+
withRetry(() => unlinkSync(src));
|
|
1846
|
+
return true;
|
|
1847
|
+
} catch (e) {
|
|
1848
|
+
warnings.push(`${rel} は行き先に置いたが、inbox から消せなかった(${e.code ?? e.message})。消してよい`);
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1600
1852
|
function envFor(ws, appId, envName) {
|
|
1601
1853
|
if (envName) return ws.environments[envName] ?? `環境「${envName}」は environments.json に無い`;
|
|
1602
1854
|
const hits = Object.values(ws.environments).filter((e) => ws.apps.some((a) => a.ids[e.name] === appId));
|
|
@@ -1606,10 +1858,11 @@ function envFor(ws, appId, envName) {
|
|
|
1606
1858
|
if (all.length === 1) return all[0];
|
|
1607
1859
|
return `アプリ ${appId} は environments.json の apps に無く、環境が 2 つ以上ある。apps に足すか --env で選ぶ`;
|
|
1608
1860
|
}
|
|
1609
|
-
function takeInbox(
|
|
1610
|
-
const
|
|
1611
|
-
const
|
|
1612
|
-
|
|
1861
|
+
function takeInbox(cwdIn, ws, envName) {
|
|
1862
|
+
const cwd = realResolve(".", cwdIn);
|
|
1863
|
+
const result = { moved: [], skipped: [], warnings: [] };
|
|
1864
|
+
const inbox = path10.join(cwd, INBOX);
|
|
1865
|
+
if (!existsSync5(inbox)) return result;
|
|
1613
1866
|
for (const name of readdirSync2(inbox).filter((n) => n.toLowerCase().endsWith(".json")).sort()) {
|
|
1614
1867
|
const rel = `${INBOX}/${name}`;
|
|
1615
1868
|
const src = resolveRead(rel, cwd);
|
|
@@ -1620,7 +1873,7 @@ function takeInbox(cwd, ws, envName) {
|
|
|
1620
1873
|
result.skipped.push({ file: rel, reason: e.message });
|
|
1621
1874
|
continue;
|
|
1622
1875
|
}
|
|
1623
|
-
if (data.pluginID !==
|
|
1876
|
+
if (data.pluginID !== PRINT_CRAFT_PLUGIN_ID2) {
|
|
1624
1877
|
result.skipped.push({ file: rel, reason: "印刷屋の設定のファイルではない(pluginID が違う)" });
|
|
1625
1878
|
continue;
|
|
1626
1879
|
}
|
|
@@ -1629,6 +1882,10 @@ function takeInbox(cwd, ws, envName) {
|
|
|
1629
1882
|
result.skipped.push({ file: rel, reason: "封筒の appId が無い" });
|
|
1630
1883
|
continue;
|
|
1631
1884
|
}
|
|
1885
|
+
if (!SNAPSHOT_RE.test(name)) {
|
|
1886
|
+
result.skipped.push({ file: rel, reason: "ダウンロードの名前(rex0220-print-craft-app<番号>-<日時>.json)ではない。設定画面のダウンロードの名前のまま inbox/ に置く" });
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1632
1889
|
const m = name.match(/-app(\d+)-/);
|
|
1633
1890
|
if (m && Number(m[1]) !== appId) {
|
|
1634
1891
|
result.skipped.push({ file: rel, reason: `ファイル名のアプリ ${m[1]} と封筒の appId ${appId} が違う` });
|
|
@@ -1640,27 +1897,55 @@ function takeInbox(cwd, ws, envName) {
|
|
|
1640
1897
|
continue;
|
|
1641
1898
|
}
|
|
1642
1899
|
const dir = appDirFor(cwd, env, appId, typeof data.appName === "string" ? data.appName : "");
|
|
1643
|
-
const dest = resolveWrite(
|
|
1644
|
-
const text =
|
|
1645
|
-
if (
|
|
1646
|
-
if (
|
|
1647
|
-
result.skipped.push({ file: rel, reason: `行き先に同じ名前の別の中身がある: ${
|
|
1900
|
+
const dest = resolveWrite(path10.join(dir, name), WRITE_ROOTS.kintone, cwd);
|
|
1901
|
+
const text = readFileSync7(src, "utf8");
|
|
1902
|
+
if (existsSync5(dest)) {
|
|
1903
|
+
if (readFileSync7(dest, "utf8") !== text) {
|
|
1904
|
+
result.skipped.push({ file: rel, reason: `行き先に同じ名前の別の中身がある: ${path10.relative(cwd, dest)}` });
|
|
1648
1905
|
continue;
|
|
1649
1906
|
}
|
|
1650
|
-
|
|
1651
|
-
result.moved.push({ file: rel, to:
|
|
1907
|
+
const removed2 = removeSource(src, result.warnings, rel);
|
|
1908
|
+
result.moved.push({ file: rel, to: path10.relative(cwd, dest), same: true, ...removed2 ? {} : { leftInInbox: true } });
|
|
1652
1909
|
continue;
|
|
1653
1910
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1911
|
+
try {
|
|
1912
|
+
assertChangeAllowed(cwd, dest, "snapshot");
|
|
1913
|
+
mkdirSync(path10.dirname(dest), { recursive: true });
|
|
1914
|
+
const { cleanup } = writeNewFile(dest, text, () => {
|
|
1915
|
+
if (resolveWrite(path10.join(dir, name), WRITE_ROOTS.kintone, cwd) !== dest) throw new PermissionError(`書く先のフォルダーが途中で変わった(symlink など): ${path10.relative(cwd, dest)}`);
|
|
1916
|
+
assertChangeAllowed(cwd, dest, "snapshot");
|
|
1917
|
+
});
|
|
1918
|
+
for (const c of cleanup) result.warnings.push(`${rel} は行き先に置いたが、一時ファイルを消せなかった(${c})。${path10.relative(cwd, path10.dirname(dest))} の .${name}.*.tmp を消してよい`);
|
|
1919
|
+
} catch (e) {
|
|
1920
|
+
if (e instanceof PermissionError) {
|
|
1921
|
+
result.skipped.push({ file: rel, reason: e.message });
|
|
1922
|
+
continue;
|
|
1923
|
+
}
|
|
1924
|
+
if (e instanceof FileExistsError) {
|
|
1925
|
+
result.skipped.push({ file: rel, reason: `行き先に同じ名前のファイルができた(移す途中で): ${path10.relative(cwd, dest)}。もう一度 take する` });
|
|
1926
|
+
continue;
|
|
1927
|
+
}
|
|
1928
|
+
throw e;
|
|
1929
|
+
}
|
|
1930
|
+
const removed = removeSource(src, result.warnings, rel);
|
|
1931
|
+
result.moved.push({ file: rel, to: path10.relative(cwd, dest), same: false, ...removed ? {} : { leftInInbox: true } });
|
|
1658
1932
|
}
|
|
1659
1933
|
return result;
|
|
1660
1934
|
}
|
|
1661
1935
|
|
|
1662
1936
|
// src/kintone-rest.ts
|
|
1663
|
-
var ALLOWED_APIS = ["app", "app/form/fields", "app/form/layout", "record", "preview/app/form/fields", "preview/app/form/layout", "app/plugin/config", "preview/app/plugin/config"];
|
|
1937
|
+
var ALLOWED_APIS = ["app", "app/form/fields", "app/form/layout", "record", "records", "preview/app/form/fields", "preview/app/form/layout", "app/plugin/config", "preview/app/plugin/config"];
|
|
1938
|
+
var RECEIVE_LIMITS = {
|
|
1939
|
+
app: 64 * 1024,
|
|
1940
|
+
"app/form/fields": 8 * 1024 * 1024,
|
|
1941
|
+
"app/form/layout": 8 * 1024 * 1024,
|
|
1942
|
+
"preview/app/form/fields": 8 * 1024 * 1024,
|
|
1943
|
+
"preview/app/form/layout": 8 * 1024 * 1024,
|
|
1944
|
+
record: 8 * 1024 * 1024,
|
|
1945
|
+
records: 8 * 1024 * 1024,
|
|
1946
|
+
"app/plugin/config": 2 * 1024 * 1024,
|
|
1947
|
+
"preview/app/plugin/config": 2 * 1024 * 1024
|
|
1948
|
+
};
|
|
1664
1949
|
var RestError = class extends Error {
|
|
1665
1950
|
status;
|
|
1666
1951
|
code;
|
|
@@ -1675,6 +1960,37 @@ var RestError = class extends Error {
|
|
|
1675
1960
|
};
|
|
1676
1961
|
var NotAllowedError = class extends Error {
|
|
1677
1962
|
};
|
|
1963
|
+
async function readCapped(res, limit, apiPath) {
|
|
1964
|
+
const tooLarge = () => new RestError(`kintone ${apiPath} の応答が大きすぎる(上限 ${limit} バイト)`, res.status, "LimitError", apiPath);
|
|
1965
|
+
const quietCancel = async (reader2) => {
|
|
1966
|
+
try {
|
|
1967
|
+
await reader2.cancel();
|
|
1968
|
+
} catch {
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
if (res.body === null) return "";
|
|
1972
|
+
if (!res.body || typeof res.body.getReader !== "function") throw new RestError(`kintone ${apiPath} の応答に本文のストリームが無い`, res.status, "ResponseError", apiPath);
|
|
1973
|
+
const reader = res.body.getReader();
|
|
1974
|
+
const declared = Number(res.headers?.get("content-length") ?? Number.NaN);
|
|
1975
|
+
if (Number.isFinite(declared) && declared > limit) {
|
|
1976
|
+
await quietCancel(reader);
|
|
1977
|
+
throw tooLarge();
|
|
1978
|
+
}
|
|
1979
|
+
const chunks = [];
|
|
1980
|
+
let total = 0;
|
|
1981
|
+
for (; ; ) {
|
|
1982
|
+
const { done, value } = await reader.read();
|
|
1983
|
+
if (done) break;
|
|
1984
|
+
if (!value) continue;
|
|
1985
|
+
total += value.length;
|
|
1986
|
+
if (total > limit) {
|
|
1987
|
+
await quietCancel(reader);
|
|
1988
|
+
throw tooLarge();
|
|
1989
|
+
}
|
|
1990
|
+
chunks.push(value);
|
|
1991
|
+
}
|
|
1992
|
+
return Buffer.concat(chunks.map((c) => Buffer.from(c))).toString("utf8");
|
|
1993
|
+
}
|
|
1678
1994
|
function apiPathOf(api, guestSpaceId) {
|
|
1679
1995
|
if (!ALLOWED_APIS.includes(api)) throw new NotAllowedError(`この API は authoring tools では呼ばない: ${api}(許可: ${ALLOWED_APIS.join(", ")})`);
|
|
1680
1996
|
if (guestSpaceId !== void 0 && (!Number.isInteger(guestSpaceId) || guestSpaceId <= 0)) throw new NotAllowedError(`ゲストスペースの ID が不正: ${String(guestSpaceId)}`);
|
|
@@ -1702,7 +2018,7 @@ function createRestClient(auth, fetchImpl = fetch) {
|
|
|
1702
2018
|
for (const [k, v] of Object.entries(params)) if (v !== void 0) url.searchParams.set(k, String(v));
|
|
1703
2019
|
if (url.origin !== baseUrl || url.username || url.password) throw new NotAllowedError(`送信先が kintone の接続先と違うので送らない: ${url.origin}`);
|
|
1704
2020
|
const res = await fetchImpl(url.href, { method: "GET", headers });
|
|
1705
|
-
const text = await res
|
|
2021
|
+
const text = await readCapped(res, RECEIVE_LIMITS[api], apiPath);
|
|
1706
2022
|
if (!res.ok) {
|
|
1707
2023
|
let code;
|
|
1708
2024
|
try {
|
|
@@ -1713,7 +2029,11 @@ function createRestClient(auth, fetchImpl = fetch) {
|
|
|
1713
2029
|
const hint = res.status === 401 ? "(認証情報かアクセス権を確かめる。API トークンならそのアプリで有効か)" : res.status === 403 ? "(アクセス権が無い)" : res.status === 404 ? "(アプリかレコードが無い)" : "";
|
|
1714
2030
|
throw new RestError(`kintone ${apiPath} が HTTP ${res.status}${code ? ` ${code}` : ""}${hint}`, res.status, code, apiPath);
|
|
1715
2031
|
}
|
|
1716
|
-
|
|
2032
|
+
try {
|
|
2033
|
+
return JSON.parse(text);
|
|
2034
|
+
} catch {
|
|
2035
|
+
throw new RestError(`kintone ${apiPath} の応答が JSON でない${text ? "" : "(本文が空)"}`, res.status, "ResponseError", apiPath);
|
|
2036
|
+
}
|
|
1717
2037
|
}
|
|
1718
2038
|
};
|
|
1719
2039
|
}
|
|
@@ -1953,8 +2273,12 @@ function columnShape(type, values) {
|
|
|
1953
2273
|
return `最大 ${longest}${multiLine ? `(複数行の値 ${multiLine} 行)` : ""}${filled.length < shapes.length ? `、空 ${shapes.length - filled.length} 行` : ""}`;
|
|
1954
2274
|
}
|
|
1955
2275
|
function describeRecord(file) {
|
|
2276
|
+
const header = `アプリ ${file.appId} レコード ${file.id}(取得 ${file.fetchedAt}${file.keptFields ? `、--fields-from で ${file.keptFields.length} 項目に絞った` : ""})。値は出さない(形だけ)`;
|
|
2277
|
+
return [header, ...shapeLines(file.record)].join("\n");
|
|
2278
|
+
}
|
|
2279
|
+
function shapeLines(record2) {
|
|
1956
2280
|
const lines = [];
|
|
1957
|
-
for (const [code, fv] of Object.entries(
|
|
2281
|
+
for (const [code, fv] of Object.entries(record2)) {
|
|
1958
2282
|
if (code === "$id" || code === "$revision" || !fv) continue;
|
|
1959
2283
|
if (fv.type === "SUBTABLE" && Array.isArray(fv.value)) {
|
|
1960
2284
|
const rows = fv.value;
|
|
@@ -1972,8 +2296,7 @@ function describeRecord(file) {
|
|
|
1972
2296
|
}
|
|
1973
2297
|
lines.push(`${code} ${fv.type} ${shapeOf(fv)}`);
|
|
1974
2298
|
}
|
|
1975
|
-
|
|
1976
|
-
return [header, ...lines].join("\n");
|
|
2299
|
+
return lines;
|
|
1977
2300
|
}
|
|
1978
2301
|
function summarizeRecord(file) {
|
|
1979
2302
|
const codes = Object.keys(file.record);
|
|
@@ -1982,6 +2305,30 @@ function summarizeRecord(file) {
|
|
|
1982
2305
|
return `アプリ ${file.appId} レコード ${file.id}: 項目 ${codes.length}${tables.length ? `(テーブル ${rows.join(", ")})` : ""}${file.keptFields ? `。--fields-from で ${file.keptFields.length} 項目に絞った` : ""}。値は表示しない`;
|
|
1983
2306
|
}
|
|
1984
2307
|
|
|
2308
|
+
// src/dev-paths.ts
|
|
2309
|
+
import { existsSync as existsSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
2310
|
+
import path11 from "node:path";
|
|
2311
|
+
function linkedRoot(env, name, envVar, fallback) {
|
|
2312
|
+
const v = env[envVar];
|
|
2313
|
+
if (v) return v;
|
|
2314
|
+
const linked = path11.join(AUTHORING_ROOT, "node_modules", name);
|
|
2315
|
+
if (existsSync6(path11.join(linked, "package.json"))) {
|
|
2316
|
+
try {
|
|
2317
|
+
return realpathSync2(linked);
|
|
2318
|
+
} catch {
|
|
2319
|
+
return linked;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
return fallback;
|
|
2323
|
+
}
|
|
2324
|
+
function printCraftRootOf(env) {
|
|
2325
|
+
return linkedRoot(env, "print-craft", "PCRAFT_PRINT_CRAFT_ROOT", path11.resolve(REPO_ROOT, "..", "print-craft"));
|
|
2326
|
+
}
|
|
2327
|
+
function printCraftProdDir(env) {
|
|
2328
|
+
return path11.join(printCraftRootOf(env), "prod");
|
|
2329
|
+
}
|
|
2330
|
+
var PRINT_CRAFT_ROOT = printCraftRootOf(process.env);
|
|
2331
|
+
|
|
1985
2332
|
// src/normalize/line-diff.ts
|
|
1986
2333
|
var MAX_CELLS = 4e6;
|
|
1987
2334
|
function lineDiff(a, b) {
|
|
@@ -2036,7 +2383,7 @@ function formatDiff(lines, context2 = 2) {
|
|
|
2036
2383
|
}
|
|
2037
2384
|
|
|
2038
2385
|
// src/commands/buttons.ts
|
|
2039
|
-
import { createHash } from "node:crypto";
|
|
2386
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2040
2387
|
var ButtonNotFoundError = class extends Error {
|
|
2041
2388
|
};
|
|
2042
2389
|
var kb = (v) => `${(Buffer.byteLength(JSON.stringify(v) ?? "", "utf8") / 1024).toFixed(1)} KB`;
|
|
@@ -2046,7 +2393,7 @@ var oneLine = (s, max = 160) => {
|
|
|
2046
2393
|
};
|
|
2047
2394
|
var chars = (s) => [...s ?? ""].length;
|
|
2048
2395
|
function shortenDataUrls(s, opt = {}) {
|
|
2049
|
-
return s.replace(/data:[^"'\s)<>]{120,}/g, (m) => `${m.slice(0, 40)}…(data URL ${m.length.toLocaleString()} 文字${opt.hash ? `、sha256 ${
|
|
2396
|
+
return s.replace(/data:[^"'\s)<>]{120,}/g, (m) => `${m.slice(0, 40)}…(data URL ${m.length.toLocaleString()} 文字${opt.hash ? `、sha256 ${createHash2("sha256").update(m).digest("hex").slice(0, 8)}` : ""})`);
|
|
2050
2397
|
}
|
|
2051
2398
|
function screensOf(b) {
|
|
2052
2399
|
const ids = (b.viewsCsv ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -2261,41 +2608,13 @@ function defaultPullName(appName, appId) {
|
|
|
2261
2608
|
}
|
|
2262
2609
|
|
|
2263
2610
|
// src/engine.ts
|
|
2264
|
-
import { existsSync as
|
|
2265
|
-
import
|
|
2611
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9 } from "node:fs";
|
|
2612
|
+
import path12 from "node:path";
|
|
2266
2613
|
import vm from "node:vm";
|
|
2267
2614
|
|
|
2268
|
-
// src/paths.ts
|
|
2269
|
-
import { existsSync as existsSync5, realpathSync as realpathSync2 } from "node:fs";
|
|
2270
|
-
import path7 from "node:path";
|
|
2271
|
-
import { fileURLToPath } from "node:url";
|
|
2272
|
-
var here = path7.dirname(fileURLToPath(import.meta.url));
|
|
2273
|
-
var AUTHORING_ROOT = path7.resolve(here, "..");
|
|
2274
|
-
var REPO_ROOT = path7.resolve(AUTHORING_ROOT, "..");
|
|
2275
|
-
var VENDOR_DIR = path7.join(AUTHORING_ROOT, "vendor");
|
|
2276
|
-
var MOMENT_FILE = path7.join(VENDOR_DIR, "moment-with-locales.min.js");
|
|
2277
|
-
function linkedRoot(name, envVar, fallback) {
|
|
2278
|
-
const env = process.env[envVar];
|
|
2279
|
-
if (env) return env;
|
|
2280
|
-
const linked = path7.join(AUTHORING_ROOT, "node_modules", name);
|
|
2281
|
-
if (existsSync5(path7.join(linked, "package.json"))) {
|
|
2282
|
-
try {
|
|
2283
|
-
return realpathSync2(linked);
|
|
2284
|
-
} catch {
|
|
2285
|
-
return linked;
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
return fallback;
|
|
2289
|
-
}
|
|
2290
|
-
var PRINT_CRAFT_ROOT = linkedRoot("print-craft", "PCRAFT_PRINT_CRAFT_ROOT", path7.resolve(REPO_ROOT, "..", "print-craft"));
|
|
2291
|
-
function devPluginDir() {
|
|
2292
|
-
const dir = path7.join(PRINT_CRAFT_ROOT, "prod");
|
|
2293
|
-
return existsSync5(path7.join(dir, "desktop_js", "KintoneFormulaPCraft.min.js")) ? dir : null;
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
2615
|
// src/plugin-zip.ts
|
|
2297
|
-
import { createHash as
|
|
2298
|
-
import { readFileSync as
|
|
2616
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2617
|
+
import { readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
|
|
2299
2618
|
import { inflateRawSync } from "node:zlib";
|
|
2300
2619
|
var SIG_EOCD = 101010256;
|
|
2301
2620
|
var SIG_CENTRAL = 33639248;
|
|
@@ -2396,18 +2715,18 @@ var BIGNUMBER_ENTRY = "desktop_js/bignumber.min.js";
|
|
|
2396
2715
|
var MOMENT_TZ_ENTRY = "desktop_js/moment-timezone-with-data.min.js";
|
|
2397
2716
|
var API_ENTRY = "config_js/print-craft-authoring-api.js";
|
|
2398
2717
|
var MANIFEST_ENTRY = "manifest.json";
|
|
2399
|
-
function
|
|
2400
|
-
return
|
|
2718
|
+
function sha256Hex2(data) {
|
|
2719
|
+
return createHash3("sha256").update(data).digest("hex");
|
|
2401
2720
|
}
|
|
2402
2721
|
function pluginIdOf(publicKey) {
|
|
2403
|
-
return
|
|
2722
|
+
return sha256Hex2(publicKey).slice(0, 32).replace(/[0-9a-f]/g, (c) => "abcdefghijklmnop"["0123456789abcdef".indexOf(c)]);
|
|
2404
2723
|
}
|
|
2405
2724
|
function readPluginZip(file) {
|
|
2406
2725
|
let outer;
|
|
2407
2726
|
try {
|
|
2408
2727
|
const size = statSync4(file).size;
|
|
2409
2728
|
if (size > ZIP_LIMITS.maxOuterBytes) throw new PluginZipError(`大きすぎる(${size} バイト)`);
|
|
2410
|
-
outer = unzip(
|
|
2729
|
+
outer = unzip(readFileSync8(file));
|
|
2411
2730
|
} catch (e) {
|
|
2412
2731
|
throw new PluginZipError(`印刷屋の zip を読めない: ${file}(${e instanceof Error ? e.message : String(e)})`);
|
|
2413
2732
|
}
|
|
@@ -2446,131 +2765,37 @@ function readPluginZip(file) {
|
|
|
2446
2765
|
momentTimezone: momentTimezone.toString("utf8"),
|
|
2447
2766
|
api: api?.toString("utf8"),
|
|
2448
2767
|
sha256: {
|
|
2449
|
-
engine:
|
|
2450
|
-
...api ? { api:
|
|
2451
|
-
bignumber:
|
|
2452
|
-
momentTimezone:
|
|
2453
|
-
contents:
|
|
2768
|
+
engine: sha256Hex2(engine),
|
|
2769
|
+
...api ? { api: sha256Hex2(api) } : {},
|
|
2770
|
+
bignumber: sha256Hex2(bignumber),
|
|
2771
|
+
momentTimezone: sha256Hex2(momentTimezone),
|
|
2772
|
+
contents: sha256Hex2(contents)
|
|
2454
2773
|
}
|
|
2455
2774
|
};
|
|
2456
2775
|
}
|
|
2457
2776
|
|
|
2458
|
-
// src/meta.ts
|
|
2459
|
-
import { execFileSync } from "node:child_process";
|
|
2460
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
2461
|
-
import { readFileSync as readFileSync7 } from "node:fs";
|
|
2462
|
-
import path8 from "node:path";
|
|
2463
|
-
var SUPPORTED_PLUGIN_VERSIONS = ["6"];
|
|
2464
|
-
var SUPPORTED_API_VERSION = 1;
|
|
2465
|
-
var KNOWN_PLUGIN_RELEASES = {
|
|
2466
|
-
"6": [
|
|
2467
|
-
{
|
|
2468
|
-
engine: "8a5f78f78f51e04b5daaa435b0e8a8c0ea4d08d388d84d279f0cd1b50a857140",
|
|
2469
|
-
api: "59c27e480f419b9d3480c24426ae93dadf0ce7c46c940bcf7457d8252b26dea3",
|
|
2470
|
-
bignumber: "eca7c1c71fee589d7b5c58bd3df3f31d56fbd234821ed8c60ec4e2050ec50129",
|
|
2471
|
-
momentTimezone: "31b9bea01ffef2e8f311eafdbbcdd944a12194fa216d8f54489e15a7188d47dc",
|
|
2472
|
-
note: "Ver.6(2026-10-04 の PR #2 マージ時点。print-craft 73097d2。描画前の掃除が入る前)"
|
|
2473
|
-
},
|
|
2474
|
-
{
|
|
2475
|
-
engine: "8a5f78f78f51e04b5daaa435b0e8a8c0ea4d08d388d84d279f0cd1b50a857140",
|
|
2476
|
-
api: "3c029ba0bb2b2c869de9dc074e588cf3e5cae8caac91a83e3da196ef2aeee673",
|
|
2477
|
-
bignumber: "eca7c1c71fee589d7b5c58bd3df3f31d56fbd234821ed8c60ec4e2050ec50129",
|
|
2478
|
-
momentTimezone: "31b9bea01ffef2e8f311eafdbbcdd944a12194fa216d8f54489e15a7188d47dc",
|
|
2479
|
-
note: "Ver.6(2026-10-04〜05 描画前の掃除 sanitize.ts と共通の設定「外部参照」externalRefs、load.ts の derivedOf(無効な行と空の計算式は formula / usedFields を持たない)を入れた後。API に sanitizeReportCss / sanitizeReportNodes / isAllowedReportUrl / EXTERNAL_REFS / externalRefsOf を追加。途中の API 9cc7daf8… / b22564da… / 6c2e447d… は配っていないので載せない)"
|
|
2480
|
-
}
|
|
2481
|
-
]
|
|
2482
|
-
};
|
|
2483
|
-
var KNOWN_ENGINE_SHA256 = Object.fromEntries(Object.entries(KNOWN_PLUGIN_RELEASES).map(([v, rs]) => [v, rs.map((r) => r.engine)]));
|
|
2484
|
-
var REQUIRED_API = {
|
|
2485
|
-
apiVersion: "number",
|
|
2486
|
-
pluginVersion: "string",
|
|
2487
|
-
pluginId: "string",
|
|
2488
|
-
CONFIG_SCHEMA: "object",
|
|
2489
|
-
CONFIG_LIMITS: "object",
|
|
2490
|
-
PAGE_SIZES: "object",
|
|
2491
|
-
DPI_OPTIONS: "object",
|
|
2492
|
-
PAPER_NAMES: "object",
|
|
2493
|
-
PRINT_MODES: "object",
|
|
2494
|
-
buildMenuRows: "function",
|
|
2495
|
-
normalizeTagsInfo: "function",
|
|
2496
|
-
normalizeCssRows: "function",
|
|
2497
|
-
toSavedRows: "function",
|
|
2498
|
-
computeUsage: "function",
|
|
2499
|
-
computePluginUOG: "function",
|
|
2500
|
-
createCheckRecord: "function",
|
|
2501
|
-
createFieldsInfo: "function",
|
|
2502
|
-
isTargetType: "function",
|
|
2503
|
-
stripComments: "function",
|
|
2504
|
-
defaultCssRows: "function",
|
|
2505
|
-
defaultMenuInfo: "function",
|
|
2506
|
-
expandFields: "function",
|
|
2507
|
-
buildReportCss: "function",
|
|
2508
|
-
fileNameOf: "function",
|
|
2509
|
-
formulaOf: "function",
|
|
2510
|
-
mountReportHtml: "function",
|
|
2511
|
-
replaceTags: "function",
|
|
2512
|
-
getPaperSize: "function",
|
|
2513
|
-
webFontOf: "function",
|
|
2514
|
-
DUMMY_IMAGE: "string",
|
|
2515
|
-
validate: "function",
|
|
2516
|
-
parseJsonSafely: "function",
|
|
2517
|
-
ValidationError: "function",
|
|
2518
|
-
writeConfig: "function",
|
|
2519
|
-
DEFAULT_LIMITS: "object",
|
|
2520
|
-
ConfigStoreError: "function",
|
|
2521
|
-
// pull(アプリのプラグインの設定を読んで封筒形式にする。2026-10-05)
|
|
2522
|
-
readConfig: "function",
|
|
2523
|
-
buildExportData: "function"
|
|
2524
|
-
};
|
|
2525
|
-
function stableJson(v) {
|
|
2526
|
-
return JSON.stringify(v, (_k, x) => x instanceof RegExp ? `/${x.source}/${x.flags}` : x);
|
|
2527
|
-
}
|
|
2528
|
-
function sha256Hex2(data) {
|
|
2529
|
-
return createHash3("sha256").update(data).digest("hex");
|
|
2530
|
-
}
|
|
2531
|
-
function schemaRevisionOf(schema) {
|
|
2532
|
-
return sha256Hex2(stableJson(schema)).slice(0, 12);
|
|
2533
|
-
}
|
|
2534
|
-
function gitCommit(dir) {
|
|
2535
|
-
try {
|
|
2536
|
-
const head = execFileSync("git", ["-C", dir, "rev-parse", "--short", "HEAD"], { encoding: "utf8" }).trim();
|
|
2537
|
-
const dirty = execFileSync("git", ["-C", dir, "status", "--porcelain", "--untracked-files=no"], { encoding: "utf8" }).trim() !== "";
|
|
2538
|
-
return dirty ? `${head}+dirty` : head;
|
|
2539
|
-
} catch {
|
|
2540
|
-
return "unknown";
|
|
2541
|
-
}
|
|
2542
|
-
}
|
|
2543
|
-
function devMeta() {
|
|
2544
|
-
const pkg = JSON.parse(readFileSync7(path8.join(AUTHORING_ROOT, "package.json"), "utf8"));
|
|
2545
|
-
return { toolsVersion: pkg.version, supportedPluginVersions: SUPPORTED_PLUGIN_VERSIONS, supportedApiVersion: SUPPORTED_API_VERSION, commit: gitCommit(AUTHORING_ROOT), builtAt: "", mode: "dev" };
|
|
2546
|
-
}
|
|
2547
|
-
function toolsMeta() {
|
|
2548
|
-
if (true) return JSON.parse('{"toolsVersion":"1.0.0","supportedPluginVersions":["6"],"supportedApiVersion":1,"commit":"d662e0b","builtAt":"2026-10-06T05:47:45.315Z","mode":"build"}');
|
|
2549
|
-
return devMeta();
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
2777
|
// src/engine.ts
|
|
2553
2778
|
var DEFAULT_CONTEXT_BASE_URL = "https://example.cybozu.com";
|
|
2554
2779
|
var DEFAULT_USER = { id: "1", code: "authoring", name: "authoring", email: "", language: "ja" };
|
|
2555
2780
|
var loaded = null;
|
|
2556
2781
|
function resolvePluginSources(opt = {}, mode = opt.mode ?? toolsMeta().mode) {
|
|
2557
|
-
const zip = opt.pluginZip
|
|
2782
|
+
const zip = opt.pluginZip;
|
|
2558
2783
|
if (zip) {
|
|
2559
|
-
if (!
|
|
2784
|
+
if (!existsSync7(zip)) throw new PluginZipError(`印刷屋の zip が無い: ${zip}(.env の PCRAFT_PLUGIN_ZIP)`);
|
|
2560
2785
|
return { ...readPluginZip(zip), kind: "zip" };
|
|
2561
2786
|
}
|
|
2562
|
-
const
|
|
2787
|
+
const dev = opt.devPluginDir;
|
|
2788
|
+
const devAllowed = mode === "dev" && !!dev;
|
|
2563
2789
|
if (!devAllowed) {
|
|
2564
2790
|
throw new PluginZipError(`印刷屋の zip の場所が分からない。.env に PCRAFT_PLUGIN_ZIP=<印刷屋プラグインの zip のパス> を書く${mode === "dev" ? "(開発中に隣の print-craft の prod/ を読むなら環境変数 PCRAFT_ALLOW_DEV_PLUGIN=1)" : ""}`);
|
|
2565
2791
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
const
|
|
2569
|
-
const apiFile = path9.join(dev, API_ENTRY);
|
|
2792
|
+
if (!dev || !existsSync7(path12.join(dev, ENGINE_ENTRY))) throw new PluginZipError(`印刷屋の zip の場所が分からない(PCRAFT_ALLOW_DEV_PLUGIN=1 だが print-craft の prod/ が無い: ${dev})。.env に PCRAFT_PLUGIN_ZIP を書く`);
|
|
2793
|
+
const read = (rel) => readFileSync9(path12.join(dev, rel), "utf8");
|
|
2794
|
+
const apiFile = path12.join(dev, API_ENTRY);
|
|
2570
2795
|
const engine = read(ENGINE_ENTRY);
|
|
2571
2796
|
const bignumber = read(BIGNUMBER_ENTRY);
|
|
2572
2797
|
const momentTimezone = read(MOMENT_TZ_ENTRY);
|
|
2573
|
-
const api =
|
|
2798
|
+
const api = existsSync7(apiFile) ? readFileSync9(apiFile, "utf8") : void 0;
|
|
2574
2799
|
const manifest = JSON.parse(read(MANIFEST_ENTRY));
|
|
2575
2800
|
return {
|
|
2576
2801
|
kind: "dev",
|
|
@@ -2581,47 +2806,22 @@ function resolvePluginSources(opt = {}, mode = opt.mode ?? toolsMeta().mode) {
|
|
|
2581
2806
|
bignumber,
|
|
2582
2807
|
momentTimezone,
|
|
2583
2808
|
api,
|
|
2584
|
-
sha256: { engine:
|
|
2809
|
+
sha256: { engine: sha256Hex2(engine), ...api ? { api: sha256Hex2(api) } : {}, bignumber: sha256Hex2(bignumber), momentTimezone: sha256Hex2(momentTimezone) }
|
|
2585
2810
|
};
|
|
2586
2811
|
}
|
|
2587
|
-
var PARTS = [
|
|
2588
|
-
["計算式エンジン", "engine"],
|
|
2589
|
-
["authoring API", "api"],
|
|
2590
|
-
["bignumber", "bignumber"],
|
|
2591
|
-
["moment-timezone", "momentTimezone"]
|
|
2592
|
-
];
|
|
2593
|
-
function unknownParts(src, releases = KNOWN_PLUGIN_RELEASES) {
|
|
2594
|
-
const list = releases[src.pluginVersion];
|
|
2595
|
-
if (!list?.length) return ["版の一覧が無い"];
|
|
2596
|
-
const matches = (r) => PARTS.filter(([, k]) => src.sha256[k] && r[k] === src.sha256[k]).length;
|
|
2597
|
-
if (list.some((r) => matches(r) === PARTS.length)) return [];
|
|
2598
|
-
const show2 = ([label, k]) => `${label} ${src.sha256[k] ? src.sha256[k].slice(0, 12) + "…" : "無し"}`;
|
|
2599
|
-
const bestScore = Math.max(...list.map(matches));
|
|
2600
|
-
if (bestScore === 0) return PARTS.map(show2);
|
|
2601
|
-
const candidates = list.filter((r) => matches(r) === bestScore);
|
|
2602
|
-
const diffs = candidates.map((r) => PARTS.filter(([, k]) => !src.sha256[k] || r[k] !== src.sha256[k]).map(([label]) => label).join("|"));
|
|
2603
|
-
if (new Set(diffs).size !== 1) return PARTS.map(show2);
|
|
2604
|
-
const best = candidates[0];
|
|
2605
|
-
const diff2 = PARTS.filter(([, k]) => !src.sha256[k] || best[k] !== src.sha256[k]).map(show2);
|
|
2606
|
-
return diff2.length ? diff2 : PARTS.map(show2);
|
|
2607
|
-
}
|
|
2608
2812
|
async function loadEngine(opt = {}) {
|
|
2609
2813
|
if (loaded) return loaded;
|
|
2610
2814
|
const meta = toolsMeta();
|
|
2611
2815
|
const src = resolvePluginSources(opt, opt.mode ?? meta.mode);
|
|
2612
2816
|
const warnings = [];
|
|
2613
|
-
if (
|
|
2614
|
-
throw new PluginZipError(
|
|
2615
|
-
}
|
|
2616
|
-
if (!src.api) throw new PluginZipError(`印刷屋の zip に ${API_ENTRY} が無い(Ver.6 以降の zip が要る): ${src.from}`);
|
|
2617
|
-
const unknown = unknownParts(src);
|
|
2618
|
-
const engineKnown = unknown.length === 0;
|
|
2619
|
-
if (!engineKnown) {
|
|
2620
|
-
const head = `印刷屋の zip の中身が tools の既知のリリースと違う: ${unknown.join("、")}(${src.from})`;
|
|
2621
|
-
if (src.kind === "dev") warnings.push(`${head}。開発中の print-craft(PCRAFT_ALLOW_DEV_PLUGIN=1)なので続ける`);
|
|
2622
|
-
else if (opt.allowUnknown) warnings.push(`${head}。PCRAFT_ALLOW_UNKNOWN_PLUGIN=1 なので続ける(結果が設定画面と違うことがある)`);
|
|
2623
|
-
else throw new PluginZipError(`${head}。配布元から取り直した zip を使うか、tools を新しい版にする。新しい修正版の zip だと分かっていて続けるなら、利用者が .env に PCRAFT_ALLOW_UNKNOWN_PLUGIN=1 を書く(zip の中のコードはこの PC の権限で動く。AI は .env を書けない)`);
|
|
2817
|
+
if (src.kind === "zip" && src.pluginId !== PRINT_CRAFT_PLUGIN_ID) {
|
|
2818
|
+
throw new PluginZipError(`印刷屋プラグインの zip ではない(プラグイン ID ${src.pluginId ?? "不明(PUBKEY が無い)"}。印刷屋は ${PRINT_CRAFT_PLUGIN_ID}): ${src.from}。配布元から入手した印刷屋の zip を PCRAFT_PLUGIN_ZIP に書く`);
|
|
2624
2819
|
}
|
|
2820
|
+
if (src.kind === "dev") warnings.push(`開発中の print-craft(${src.from}。PCRAFT_ALLOW_DEV_PLUGIN=1)から読む。プラグイン ID は確かめない`);
|
|
2821
|
+
if (!isSupportedPluginVersion(src.pluginVersion)) {
|
|
2822
|
+
throw new PluginZipError(`印刷屋プラグインの版 ${src.pluginVersion || "不明"}(${src.from})には対応していない。tools が扱うのは Ver.${MIN_PLUGIN_VERSION} 以降`);
|
|
2823
|
+
}
|
|
2824
|
+
if (!src.api) throw new PluginZipError(`印刷屋の zip に ${API_ENTRY} が無い(Ver.${MIN_PLUGIN_VERSION} 以降の zip が要る): ${src.from}`);
|
|
2625
2825
|
const g = globalThis;
|
|
2626
2826
|
const { Window } = await import("happy-dom");
|
|
2627
2827
|
const ctx = { baseUrl: DEFAULT_CONTEXT_BASE_URL, appId: 1, loginUser: { ...DEFAULT_USER } };
|
|
@@ -2659,7 +2859,7 @@ async function loadEngine(opt = {}) {
|
|
|
2659
2859
|
window.kintone = g.kintone;
|
|
2660
2860
|
window.cybozu = g.cybozu;
|
|
2661
2861
|
window.rex0220_users_info3 = g.rex0220_users_info3;
|
|
2662
|
-
if (!
|
|
2862
|
+
if (!existsSync7(MOMENT_FILE)) throw new Error(`moment が無い: ${MOMENT_FILE}(tools の npm install 時に取る。手で取るなら npm run vendor)`);
|
|
2663
2863
|
const runScript = (code, filename) => {
|
|
2664
2864
|
vm.runInThisContext(code, { filename });
|
|
2665
2865
|
for (const k of ["moment", "rex0220_BigNumber", "rex0220_Decimal", "rex0220p", "rex0220PrintCraftAuthoring"]) {
|
|
@@ -2667,7 +2867,7 @@ async function loadEngine(opt = {}) {
|
|
|
2667
2867
|
if (window[k] === void 0 && g[k] !== void 0) window[k] = g[k];
|
|
2668
2868
|
}
|
|
2669
2869
|
};
|
|
2670
|
-
runScript(
|
|
2870
|
+
runScript(readFileSync9(MOMENT_FILE, "utf8"), MOMENT_FILE);
|
|
2671
2871
|
runScript(src.momentTimezone, `${src.from}!${MOMENT_TZ_ENTRY}`);
|
|
2672
2872
|
runScript(src.bignumber, `${src.from}!${BIGNUMBER_ENTRY}`);
|
|
2673
2873
|
runScript(src.engine, `${src.from}!${ENGINE_ENTRY}`);
|
|
@@ -2677,7 +2877,7 @@ async function loadEngine(opt = {}) {
|
|
|
2677
2877
|
if (typeof Ctor !== "function") throw new Error("KintoneFormulaPCraft is not loaded");
|
|
2678
2878
|
const api = window.rex0220PrintCraftAuthoring;
|
|
2679
2879
|
if (!api || typeof api !== "object") throw new PluginZipError("印刷屋の authoring API(rex0220PrintCraftAuthoring)が読めない");
|
|
2680
|
-
if (api.apiVersion
|
|
2880
|
+
if (!SUPPORTED_API_VERSIONS.includes(api.apiVersion)) throw new PluginZipError(`印刷屋の authoring API の版 ${String(api.apiVersion)} には対応していない(tools は ${SUPPORTED_API_VERSIONS.join(", ")})。tools を新しい版にする`);
|
|
2681
2881
|
if (String(api.pluginVersion) !== src.pluginVersion) throw new PluginZipError(`authoring API の印刷屋の版 ${String(api.pluginVersion)} が zip の manifest の版 ${src.pluginVersion} と違う(組み替えられた zip)`);
|
|
2682
2882
|
const bad = Object.entries(REQUIRED_API).filter(([k, t]) => typeof api[k] !== t).map(([k]) => k);
|
|
2683
2883
|
if (bad.length) throw new PluginZipError(`印刷屋の authoring API に tools が使うものが無い、または型が違う: ${bad.join(", ")}(tools と印刷屋の版を合わせる)`);
|
|
@@ -2685,7 +2885,7 @@ async function loadEngine(opt = {}) {
|
|
|
2685
2885
|
window,
|
|
2686
2886
|
Ctor,
|
|
2687
2887
|
api,
|
|
2688
|
-
source: { kind: src.kind, from: src.from, pluginVersion: src.pluginVersion, engineSha256: src.sha256.engine, apiSha256: src.sha256.api,
|
|
2888
|
+
source: { kind: src.kind, from: src.from, pluginVersion: src.pluginVersion, engineSha256: src.sha256.engine, apiSha256: src.sha256.api, ...src.pluginId ? { pluginId: src.pluginId } : {} },
|
|
2689
2889
|
warnings,
|
|
2690
2890
|
setContext(next) {
|
|
2691
2891
|
if (next.baseUrl) ctx.baseUrl = next.baseUrl.replace(/\/+$/, "");
|
|
@@ -2892,6 +3092,18 @@ async function runPreview(input) {
|
|
|
2892
3092
|
return { findings, results, skipped, summary };
|
|
2893
3093
|
}
|
|
2894
3094
|
|
|
3095
|
+
// src/context.ts
|
|
3096
|
+
function createContext(opt) {
|
|
3097
|
+
return Object.freeze({ root: realResolve(".", opt.cwd), env: Object.freeze({ ...opt.env }) });
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// src/commands/save.ts
|
|
3101
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3102
|
+
var MAX_SAVE_INPUT_BYTES = 256 * 1024;
|
|
3103
|
+
function digestOf(text) {
|
|
3104
|
+
return createHash4("sha256").update(text).digest("hex");
|
|
3105
|
+
}
|
|
3106
|
+
|
|
2895
3107
|
// src/cli.ts
|
|
2896
3108
|
var USAGE = `使い方: npx @rex0220/print-craft-authoring-tools <command> [options]
|
|
2897
3109
|
(短い npx pcraft-authoring は使わない。npm ci の前だと npm の公開レジストリの同じ名前のパッケージを取りに行く)
|
|
@@ -2905,14 +3117,16 @@ var USAGE = `使い方: npx @rex0220/print-craft-authoring-tools <command> [opti
|
|
|
2905
3117
|
取得済みの fields/<N>.json を 1 項目 1 行で(レイアウトの順。型・ラベル・書式・単位・選択肢・ルックアップ)。通信しない。
|
|
2906
3118
|
pull --app N [--preview] [--guest <spaceId>] [--out settings/<file>] [--force] [--plugin-id <ID>]
|
|
2907
3119
|
アプリに入っている印刷屋の今の設定を取って、設定画面の「設定をダウンロード」と同じ封筒形式で settings/APP<N>-<アプリ名>.json に保存(GET だけ)。
|
|
2908
|
-
kintone の API
|
|
2909
|
-
--preview(動作テスト環境 = 保存して未反映の設定)がアプリ管理。プラグイン ID は印刷屋の zip から。既にあるファイルは --force
|
|
3120
|
+
kintone の API ラボ「アプリに追加されているプラグインの設定情報を取得する」を有効にした環境だけ。権限は運用中の設定がレコード閲覧(API トークンでも可)、
|
|
3121
|
+
--preview(動作テスト環境 = 保存して未反映の設定)がアプリ管理。プラグイン ID は印刷屋の zip から。既にあるファイルは --force で上書き
|
|
3122
|
+
(--force は settings/ に置くときだけ。environments.json があるときはダウンロードと同じ日時の名前で新しく置き、上書きしない)。
|
|
2910
3123
|
record --app N --id R [--fields-from <settings.json>] [--guest <spaceId>] [--out records/<file>]
|
|
2911
3124
|
レコードを records/<N>-<R>.json に保存。--fields-from で設定が使う項目だけ残す。
|
|
2912
3125
|
record --app N --id R --summary
|
|
2913
3126
|
取得済みの records/<N>-<R>.json を 1 項目 1 行で。値は出さず形だけ(文字数・行数・数値の桁・テーブルの行数・添付の件数と種類)。通信しない。
|
|
2914
|
-
buttons <settings.json> [--button <名前>]
|
|
3127
|
+
buttons <settings.json> [--button <名前>] [--json]
|
|
2915
3128
|
設定のボタン一覧(画面・保存先・用紙・表示条件・ファイル名・帳票の行・更新項目)。--button でそのボタンの HTML / CSS / 計算式(data: の URL は長さだけ)。
|
|
3129
|
+
--json はファイルの digest(sha256)とボタンの名前の一覧も(直すときの照合に使う)。
|
|
2916
3130
|
normalize <settings.json> --fields <fields.json> [--out settings/<file>] [--dry-run] [--check] [--json]
|
|
2917
3131
|
設定画面と同じ手順で派生値を作り直し、検査して、エラーが無ければ書き戻す(既定は同じファイルに上書き。--dry-run は書かない)。
|
|
2918
3132
|
--check は入力の派生値と生成した値の差を出す。外部 URL の承認は policy/authoring-policy.json(場所は固定。利用者が書く)。
|
|
@@ -2945,6 +3159,12 @@ kintone には GET しか送らない。
|
|
|
2945
3159
|
var FLAGS = /* @__PURE__ */ new Set(["preview", "check", "json", "dry-run", "derived", "summary", "force"]);
|
|
2946
3160
|
var UsageError = class extends Error {
|
|
2947
3161
|
};
|
|
3162
|
+
var W;
|
|
3163
|
+
var resolveRead2 = (target) => resolveRead(target, W.root);
|
|
3164
|
+
var resolveWrite2 = (target, roots) => resolveWrite(target, roots, W.root);
|
|
3165
|
+
var resolveWriteDir2 = (target, roots) => resolveWriteDir(target, roots, W.root);
|
|
3166
|
+
var authOpt = () => ({ cwd: W.root, env: W.env });
|
|
3167
|
+
var allow = (file, op) => assertChangeAllowed(W.root, file, op);
|
|
2948
3168
|
function option(args, name) {
|
|
2949
3169
|
const i = args.indexOf(`--${name}`);
|
|
2950
3170
|
return i >= 0 ? args[i + 1] : void 0;
|
|
@@ -2977,20 +3197,35 @@ function rejectRemovedOptions(args) {
|
|
|
2977
3197
|
if (args.includes(`--${name}`)) throw new UsageError(`--${name} は使えない。.env と policy/authoring-policy.json と PCRAFT_PLUGIN_ZIP は作業フォルダーのものに固定(AI が書けるファイルを読ませないため)`);
|
|
2978
3198
|
}
|
|
2979
3199
|
}
|
|
2980
|
-
function
|
|
2981
|
-
|
|
2982
|
-
|
|
3200
|
+
function writeText(file, text, roots, op, opt = {}) {
|
|
3201
|
+
const real = resolveWrite2(file, roots);
|
|
3202
|
+
allow(real, op);
|
|
3203
|
+
mkdirSync2(path13.dirname(real), { recursive: true });
|
|
3204
|
+
if (opt.exclusive ?? op === "snapshot") {
|
|
3205
|
+
try {
|
|
3206
|
+
const { cleanup } = writeNewFile(real, text, () => {
|
|
3207
|
+
if (resolveWrite2(file, roots) !== real) throw new InputError(`書く先のフォルダーが途中で変わった(symlink など): ${shown(real)}`);
|
|
3208
|
+
allow(real, op);
|
|
3209
|
+
});
|
|
3210
|
+
for (const c of cleanup) console.error(`注意: 書いたが一時ファイルを消せなかった(${c})`);
|
|
3211
|
+
} catch (e) {
|
|
3212
|
+
if (e instanceof FileExistsError) throw new InputError(`${shown(real)} は既にある(確かめた後に作られた)。上書きしない`);
|
|
3213
|
+
throw e;
|
|
3214
|
+
}
|
|
3215
|
+
return real;
|
|
3216
|
+
}
|
|
3217
|
+
writeFileSync2(real, text, "utf8");
|
|
3218
|
+
return real;
|
|
2983
3219
|
}
|
|
2984
|
-
function
|
|
2985
|
-
|
|
2986
|
-
writeFileSync2(file, text, "utf8");
|
|
3220
|
+
function writeJson(file, data, roots, op, opt = {}) {
|
|
3221
|
+
return writeText(file, JSON.stringify(data, null, 2) + "\n", roots, op, opt);
|
|
2987
3222
|
}
|
|
2988
3223
|
function shown(file) {
|
|
2989
|
-
const rel =
|
|
3224
|
+
const rel = path13.relative(W.root, file);
|
|
2990
3225
|
return rel && !rel.startsWith("..") ? rel : file;
|
|
2991
3226
|
}
|
|
2992
3227
|
async function engineFor() {
|
|
2993
|
-
const engine = await loadEngine({ pluginZip: pluginZipPath(),
|
|
3228
|
+
const engine = await loadEngine({ pluginZip: pluginZipPath(authOpt()), devPluginDir: W.env.PCRAFT_ALLOW_DEV_PLUGIN === "1" ? printCraftProdDir(W.env) : void 0 });
|
|
2994
3229
|
for (const w of engine.warnings) console.error(`注意: ${w}`);
|
|
2995
3230
|
return engine;
|
|
2996
3231
|
}
|
|
@@ -3007,12 +3242,12 @@ async function version(args) {
|
|
|
3007
3242
|
if (expect !== void 0 && !expect) throw new UsageError("--expect には印刷屋の版(例: 6)を続ける");
|
|
3008
3243
|
let expectError;
|
|
3009
3244
|
if (expect !== void 0 && engine) {
|
|
3010
|
-
if (!
|
|
3245
|
+
if (!isSupportedPluginVersion(expect)) expectError = `--expect ${expect}: tools ${meta.toolsVersion} が扱う印刷屋の版は Ver.${meta.minPluginVersion} 以降`;
|
|
3011
3246
|
else if (engine.source.pluginVersion !== expect) expectError = `--expect ${expect}: zip の印刷屋の版は ${engine.source.pluginVersion}(${engine.source.from}。アプリに入れた版の zip を PCRAFT_PLUGIN_ZIP に書く)`;
|
|
3012
3247
|
}
|
|
3013
3248
|
const info = {
|
|
3014
3249
|
...meta,
|
|
3015
|
-
plugin: engine ? { source: engine.source.kind, from: engine.source.from, pluginVersion: engine.source.pluginVersion, apiVersion: engine.api.apiVersion, engineSha256: engine.source.engineSha256, apiSha256: engine.source.apiSha256,
|
|
3250
|
+
plugin: engine ? { source: engine.source.kind, from: engine.source.from, pluginVersion: engine.source.pluginVersion, apiVersion: engine.api.apiVersion, engineSha256: engine.source.engineSha256, apiSha256: engine.source.apiSha256, pluginId: engine.source.pluginId ?? null, schemaRevision: schemaRevisionOf(engine.api.CONFIG_SCHEMA) } : null,
|
|
3016
3251
|
expected: expect,
|
|
3017
3252
|
ok: !!engine && !expectError,
|
|
3018
3253
|
error: error ?? expectError
|
|
@@ -3020,10 +3255,11 @@ async function version(args) {
|
|
|
3020
3255
|
if (flag(args, "json")) {
|
|
3021
3256
|
console.log(JSON.stringify(info, null, 2));
|
|
3022
3257
|
} else {
|
|
3023
|
-
console.log(`@rex0220/print-craft-authoring-tools ${meta.toolsVersion}${meta.mode === "dev" ? "(開発中)" : ""}
|
|
3258
|
+
console.log(`@rex0220/print-craft-authoring-tools ${meta.toolsVersion}${meta.mode === "dev" ? "(開発中)" : ""}(扱う印刷屋 Ver.${meta.minPluginVersion} 以降、API ${meta.supportedApiVersions.join(", ")}。commit ${meta.commit}${meta.builtAt ? `、ビルド ${meta.builtAt}` : ""})`);
|
|
3024
3259
|
if (engine) {
|
|
3025
3260
|
console.log(`印刷屋プラグイン: 版 ${engine.source.pluginVersion}、authoring API ${engine.api.apiVersion}(${engine.source.kind === "zip" ? "zip" : "開発中の print-craft"}: ${engine.source.from})`);
|
|
3026
|
-
console.log(
|
|
3261
|
+
console.log(`プラグイン ID: ${engine.source.pluginId ?? "(開発中の print-craft。確かめない)"}`);
|
|
3262
|
+
console.log(`計算式エンジン: sha256 ${engine.source.engineSha256}`);
|
|
3027
3263
|
console.log(`設定スキーマの版: ${schemaRevisionOf(engine.api.CONFIG_SCHEMA)}`);
|
|
3028
3264
|
if (expectError) console.error(expectError);
|
|
3029
3265
|
else if (expect !== void 0) console.log(`--expect ${expect}: 一致`);
|
|
@@ -3034,7 +3270,7 @@ async function version(args) {
|
|
|
3034
3270
|
return info.ok ? 0 : 1;
|
|
3035
3271
|
}
|
|
3036
3272
|
function context(args) {
|
|
3037
|
-
const ws = loadWorkspace();
|
|
3273
|
+
const ws = loadWorkspace(W.root);
|
|
3038
3274
|
const name = option(args, "env");
|
|
3039
3275
|
if (args.includes("--env")) {
|
|
3040
3276
|
if (!name || !isEnvName(name)) throw new UsageError("--env には environments.json の環境の名前を続ける(ファイルの場所は指定できない)");
|
|
@@ -3043,7 +3279,7 @@ function context(args) {
|
|
|
3043
3279
|
return ws ? { ws, env: pickEnv(ws, name) } : { ws: null };
|
|
3044
3280
|
}
|
|
3045
3281
|
function client(ctx) {
|
|
3046
|
-
const auth = ctx?.ws && ctx.env ? loadAuthForEnv(ctx.env) : loadAuth();
|
|
3282
|
+
const auth = ctx?.ws && ctx.env ? loadAuthForEnv(ctx.env, W.root) : loadAuth(authOpt());
|
|
3047
3283
|
return { client: createRestClient(auth), authLabel: describeAuth(auth) };
|
|
3048
3284
|
}
|
|
3049
3285
|
var envFlag = (ctx) => ctx.ws && ctx.env && ctx.env.name !== ctx.ws.defaultEnv ? ` --env ${ctx.env.name}` : "";
|
|
@@ -3054,7 +3290,7 @@ function appOf(args, ctx) {
|
|
|
3054
3290
|
return resolveApp(ctx.ws, ctx.env, v).appId;
|
|
3055
3291
|
}
|
|
3056
3292
|
function existingAppDir(ctx, appId) {
|
|
3057
|
-
const dir = findAppDir(
|
|
3293
|
+
const dir = findAppDir(W.root, ctx.env, appId);
|
|
3058
3294
|
if (!dir) throw new InputError(`アプリ ${appId} のフォルダー(kintone/${ctx.env.host}/${appId}-…)が無い。先に npx @rex0220/print-craft-authoring-tools fields --app ${appId}${envFlag(ctx)}`);
|
|
3059
3295
|
return dir;
|
|
3060
3296
|
}
|
|
@@ -3062,38 +3298,39 @@ function noOutInWorkspace(args, ctx) {
|
|
|
3062
3298
|
if (ctx.ws && args.includes("--out")) throw new UsageError("environments.json があるときは --out を使わない(保存先はアプリのフォルダー kintone/<ホスト名>/<番号>-<アプリ名>/)");
|
|
3063
3299
|
}
|
|
3064
3300
|
function existingFile(rel, howToFetch) {
|
|
3065
|
-
const file =
|
|
3066
|
-
if (!
|
|
3301
|
+
const file = resolveRead2(rel);
|
|
3302
|
+
if (!existsSync8(file)) throw new InputError(`${shown(file)} が無い。先に npx @rex0220/print-craft-authoring-tools ${howToFetch}`);
|
|
3067
3303
|
return file;
|
|
3068
3304
|
}
|
|
3069
3305
|
function folderDefaults(settingsFile) {
|
|
3070
|
-
return loadWorkspace() ? appFolderOfFile(
|
|
3306
|
+
return loadWorkspace(W.root) ? appFolderOfFile(W.root, settingsFile) : null;
|
|
3071
3307
|
}
|
|
3072
3308
|
function fieldsFileOf(args, folder) {
|
|
3073
3309
|
const arg = option(args, "fields");
|
|
3074
|
-
if (arg) return
|
|
3310
|
+
if (arg) return resolveRead2(arg);
|
|
3075
3311
|
if (!folder) throw new UsageError("--fields <fields.json> が要る(fields コマンドの出力。kintone/<ホスト名>/<番号>-…/ の中のファイルなら同じフォルダーの fields.json を使う)");
|
|
3076
|
-
return existingFile(
|
|
3312
|
+
return existingFile(path13.join(folder.dir, "fields.json"), `fields --app ${folder.appId}`);
|
|
3077
3313
|
}
|
|
3078
3314
|
function trustedBaseUrl(folder) {
|
|
3079
|
-
const ws = loadWorkspace();
|
|
3315
|
+
const ws = loadWorkspace(W.root);
|
|
3080
3316
|
if (ws && folder) return envsOfHost(ws, folder.host)[0]?.baseUrl;
|
|
3081
|
-
return baseUrlFromEnv();
|
|
3317
|
+
return baseUrlFromEnv(authOpt());
|
|
3082
3318
|
}
|
|
3083
3319
|
async function fields(args) {
|
|
3084
3320
|
const ctx = context(args);
|
|
3085
3321
|
const app = appOf(args, ctx);
|
|
3086
3322
|
noOutInWorkspace(args, ctx);
|
|
3087
3323
|
if (flag(args, "summary")) {
|
|
3088
|
-
const rel = ctx.ws ?
|
|
3324
|
+
const rel = ctx.ws ? path13.join(existingAppDir(ctx, app), "fields.json") : path13.join("fields", `${app}.json`);
|
|
3089
3325
|
console.log(listFields(await readFieldsFile(existingFile(rel, `fields --app ${app}${envFlag(ctx)}`))));
|
|
3090
3326
|
return 0;
|
|
3091
3327
|
}
|
|
3092
3328
|
const guestSpaceId = intOption(args, "guest", false);
|
|
3093
3329
|
const { client: c, authLabel } = client(ctx);
|
|
3094
3330
|
const file = await fetchFields(c, { app, lang: option(args, "lang"), preview: flag(args, "preview"), guestSpaceId });
|
|
3095
|
-
const out = ctx.ws ?
|
|
3096
|
-
|
|
3331
|
+
const out = ctx.ws ? resolveWrite2(path13.join(appDirFor(W.root, ctx.env, app, file.appName), "fields.json"), WRITE_ROOTS.kintone) : resolveWrite2(option(args, "out") ?? path13.join("fields", `${app}.json`), WRITE_ROOTS.fields);
|
|
3332
|
+
allow(out, "fields");
|
|
3333
|
+
writeJson(out, file, ctx.ws ? WRITE_ROOTS.kintone : WRITE_ROOTS.fields, "fields");
|
|
3097
3334
|
console.log(`${summarizeFields(file)}
|
|
3098
3335
|
${authLabel}で ${c.baseUrl} から取得 → ${shown(out)}`);
|
|
3099
3336
|
return 0;
|
|
@@ -3103,7 +3340,7 @@ async function record(args) {
|
|
|
3103
3340
|
const app = appOf(args, ctx);
|
|
3104
3341
|
const id = intOption(args, "id", true);
|
|
3105
3342
|
noOutInWorkspace(args, ctx);
|
|
3106
|
-
const defaultRel = () => ctx.ws ?
|
|
3343
|
+
const defaultRel = () => ctx.ws ? path13.join(existingAppDir(ctx, app), "records", `${id}.json`) : path13.join("records", `${app}-${id}.json`);
|
|
3107
3344
|
if (flag(args, "summary")) {
|
|
3108
3345
|
const file2 = existingFile(defaultRel(), `record --app ${app} --id ${id}${envFlag(ctx)}`);
|
|
3109
3346
|
const data = readJsonLimited(file2);
|
|
@@ -3112,18 +3349,19 @@ async function record(args) {
|
|
|
3112
3349
|
return 0;
|
|
3113
3350
|
}
|
|
3114
3351
|
const guestSpaceId = intOption(args, "guest", false);
|
|
3115
|
-
const out =
|
|
3352
|
+
const out = resolveWrite2(option(args, "out") ?? defaultRel(), WRITE_ROOTS.records);
|
|
3116
3353
|
const from = option(args, "fields-from");
|
|
3117
3354
|
let keep;
|
|
3118
3355
|
if (from) {
|
|
3119
|
-
const settings = readJsonLimited(
|
|
3356
|
+
const settings = readJsonLimited(resolveRead2(from));
|
|
3120
3357
|
const codes = usedFieldCodes(settings);
|
|
3121
3358
|
if (!codes) console.log(`${from} に使う項目の情報が無いので絞らない(normalize した設定なら usedFields から絞れる)`);
|
|
3122
3359
|
else keep = codes;
|
|
3123
3360
|
}
|
|
3124
3361
|
const { client: c, authLabel } = client(ctx);
|
|
3362
|
+
allow(out, "record");
|
|
3125
3363
|
const file = await fetchRecord(c, { app, id, guestSpaceId, keep });
|
|
3126
|
-
writeJson(out, file);
|
|
3364
|
+
writeJson(out, file, WRITE_ROOTS.records, "record");
|
|
3127
3365
|
console.log(`${summarizeRecord(file)}
|
|
3128
3366
|
${authLabel}で ${c.baseUrl} から取得 → ${shown(out)}(個人情報を含む。コミットしない)`);
|
|
3129
3367
|
return 0;
|
|
@@ -3131,24 +3369,25 @@ ${authLabel}で ${c.baseUrl} から取得 → ${shown(out)}(個人情報を含
|
|
|
3131
3369
|
async function normalize(args) {
|
|
3132
3370
|
const [settingsArg] = positional(args);
|
|
3133
3371
|
if (!settingsArg) throw new UsageError("設定 JSON のパスが要る");
|
|
3134
|
-
const settingsFile =
|
|
3372
|
+
const settingsFile = resolveRead2(settingsArg);
|
|
3135
3373
|
const folder = folderDefaults(settingsFile);
|
|
3136
3374
|
const fieldsFile = fieldsFileOf(args, folder);
|
|
3137
3375
|
const write = !flag(args, "dry-run");
|
|
3138
|
-
if (write && folder && !option(args, "out") && SNAPSHOT_RE.test(
|
|
3376
|
+
if (write && folder && !option(args, "out") && SNAPSHOT_RE.test(path13.basename(settingsFile))) {
|
|
3139
3377
|
throw new UsageError(`ダウンロード / pull のファイルは書き換えない。npx @rex0220/print-craft-authoring-tools edit ${shown(settingsFile)} で -edit.json に写してから直す(検査だけなら --dry-run)`);
|
|
3140
3378
|
}
|
|
3141
|
-
const out = write ?
|
|
3379
|
+
const out = write ? resolveWrite2(option(args, "out") ?? settingsFile, WRITE_ROOTS.settings) : void 0;
|
|
3380
|
+
if (out) allow(out, "settings");
|
|
3142
3381
|
const fieldsData = await readFieldsFile(fieldsFile);
|
|
3143
3382
|
const baseUrl = trustedBaseUrl(folder);
|
|
3144
3383
|
const engine = await engineFor();
|
|
3145
3384
|
engine.setContext({ baseUrl: baseUrl ?? DEFAULT_CONTEXT_BASE_URL, appId: fieldsData.appId });
|
|
3146
3385
|
const result = await normalizeSettings({
|
|
3147
3386
|
settingsText: readTextLimited(settingsFile),
|
|
3148
|
-
settingsFile: relativeSettingsPath(settingsArg),
|
|
3387
|
+
settingsFile: relativeSettingsPath(settingsArg, W.root),
|
|
3149
3388
|
fields: fieldsData,
|
|
3150
3389
|
engine,
|
|
3151
|
-
policy: loadPolicy(),
|
|
3390
|
+
policy: loadPolicy({ cwd: W.root }),
|
|
3152
3391
|
check: flag(args, "check"),
|
|
3153
3392
|
baseUrl
|
|
3154
3393
|
});
|
|
@@ -3168,7 +3407,7 @@ async function normalize(args) {
|
|
|
3168
3407
|
return 1;
|
|
3169
3408
|
}
|
|
3170
3409
|
if (out) {
|
|
3171
|
-
writeJson(out, result.output);
|
|
3410
|
+
writeJson(out, result.output, WRITE_ROOTS.settings, "settings");
|
|
3172
3411
|
if (!flag(args, "json")) console.log(`出力: ${shown(out)}(封筒の date を更新。派生値を生成)`);
|
|
3173
3412
|
}
|
|
3174
3413
|
return 0;
|
|
@@ -3176,8 +3415,8 @@ async function normalize(args) {
|
|
|
3176
3415
|
async function diff(args) {
|
|
3177
3416
|
const [before, after] = positional(args);
|
|
3178
3417
|
if (!before || !after) throw new UsageError("diff <before.json> <after.json>");
|
|
3179
|
-
const a = readJsonLimited(
|
|
3180
|
-
const b = readJsonLimited(
|
|
3418
|
+
const a = readJsonLimited(resolveRead2(before));
|
|
3419
|
+
const b = readJsonLimited(resolveRead2(after));
|
|
3181
3420
|
console.log(diffSettings(a, b, { derived: flag(args, "derived") }));
|
|
3182
3421
|
return 0;
|
|
3183
3422
|
}
|
|
@@ -3185,6 +3424,7 @@ async function pull(args) {
|
|
|
3185
3424
|
const ctx = context(args);
|
|
3186
3425
|
const app = appOf(args, ctx);
|
|
3187
3426
|
noOutInWorkspace(args, ctx);
|
|
3427
|
+
if (ctx.ws && flag(args, "force")) throw new UsageError("environments.json があるときの pull は、ダウンロードと同じ日時の名前で新しく置く(ダウンロード / pull のファイルは上書きしない)ので --force は使えない");
|
|
3188
3428
|
const guestSpaceId = intOption(args, "guest", false);
|
|
3189
3429
|
const preview2 = flag(args, "preview");
|
|
3190
3430
|
const outArg = option(args, "out");
|
|
@@ -3198,13 +3438,15 @@ async function pull(args) {
|
|
|
3198
3438
|
result = await pullSettings(c, engine, { app, preview: preview2, guestSpaceId, pluginId });
|
|
3199
3439
|
} catch (e) {
|
|
3200
3440
|
if (e instanceof RestError && /plugin\/config/.test(e.apiPath ?? "")) {
|
|
3201
|
-
throw new RestError(`${e.message}。確かめること: kintone の API ラボ「アプリに追加されているプラグインの設定情報を取得または更新するREST API」が有効か(cybozu.com 共通管理者がアップデートオプションの「検討中の新機能」で)、権限(運用中:
|
|
3441
|
+
throw new RestError(`${e.message}。確かめること: kintone の API ラボ「アプリに追加されているプラグインの設定情報を取得または更新するREST API」が有効か(cybozu.com 共通管理者がアップデートオプションの「検討中の新機能」で)、権限(運用中: レコード閲覧。API トークンでも可 / --preview: アプリ管理)、アプリに印刷屋プラグイン(ID ${pluginId})が入っているか`, e.status, e.code, e.apiPath);
|
|
3202
3442
|
}
|
|
3203
3443
|
throw e;
|
|
3204
3444
|
}
|
|
3205
|
-
const out = ctx.ws ?
|
|
3206
|
-
if (
|
|
3207
|
-
|
|
3445
|
+
const out = ctx.ws ? resolveWrite2(path13.join(appDirFor(W.root, ctx.env, app, result.appName), snapshotNameOf(app)), WRITE_ROOTS.kintone) : resolveWrite2(outArg ?? path13.join("settings", defaultPullName(result.appName, app)), WRITE_ROOTS.settings);
|
|
3446
|
+
if (existsSync8(out) && ctx.ws) throw new InputError(`${shown(out)} は既にある(同じ日時の名前)。少し待ってからやり直す(ダウンロード / pull のファイルは上書きしない)`);
|
|
3447
|
+
if (existsSync8(out) && !flag(args, "force")) throw new InputError(`${shown(out)} は既にある。上書きするなら --force(上書きの前の内容は git の差分で確かめる)、別の名前なら --out settings/<ファイル>`);
|
|
3448
|
+
allow(out, ctx.ws ? "snapshot" : "settings");
|
|
3449
|
+
writeJson(out, result.envelope, ctx.ws ? WRITE_ROOTS.kintone : WRITE_ROOTS.settings, ctx.ws ? "snapshot" : "settings", { exclusive: !!ctx.ws || !flag(args, "force") });
|
|
3208
3450
|
console.log(`pull: アプリ ${app} ${result.appName}(${preview2 ? "動作テスト環境" : "運用中"}の設定、revision ${result.revision}、保存形式 ${result.format})を${authLabel}で ${c.baseUrl} から取得 → ${shown(out)}`);
|
|
3209
3451
|
console.log(listButtons(result.envelope, { file: shown(out) }));
|
|
3210
3452
|
return 0;
|
|
@@ -3217,20 +3459,37 @@ async function buttons(args) {
|
|
|
3217
3459
|
const app = appOf(args, ctx);
|
|
3218
3460
|
const list = listAppFolder(existingAppDir(ctx, app));
|
|
3219
3461
|
if (!list.snapshots.length) throw new InputError(`${shown(list.dir)} にダウンロード / pull のファイルが無い(設定画面でダウンロードして inbox/ に置き take、または pull --app ${app}${envFlag(ctx)})`);
|
|
3220
|
-
settingsArg = shown(
|
|
3462
|
+
settingsArg = shown(path13.join(list.dir, list.snapshots[0]));
|
|
3221
3463
|
}
|
|
3222
3464
|
if (!settingsArg) throw new UsageError("設定 JSON のパスが要る");
|
|
3223
3465
|
const button = option(args, "button");
|
|
3224
3466
|
if (args.includes("--button") && !button) throw new UsageError("--button にはボタン名を続ける");
|
|
3225
|
-
|
|
3467
|
+
const file = resolveRead2(settingsArg);
|
|
3468
|
+
const bytes = readFileSync10(file);
|
|
3469
|
+
if (bytes.length > MAX_INPUT_BYTES) throw new InputError(`${shown(file)} が大きすぎる(${bytes.length.toLocaleString()} バイト。上限 ${MAX_INPUT_BYTES.toLocaleString()})`);
|
|
3470
|
+
let settings;
|
|
3471
|
+
try {
|
|
3472
|
+
settings = JSON.parse(bytes.toString("utf8"));
|
|
3473
|
+
} catch (e) {
|
|
3474
|
+
throw new InputError(`${shown(file)} を JSON として読めない: ${e.message}`);
|
|
3475
|
+
}
|
|
3476
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new InputError(`${shown(file)} の最上位はオブジェクト`);
|
|
3477
|
+
const text = listButtons(settings, { file: settingsArg, button });
|
|
3478
|
+
if (flag(args, "json")) {
|
|
3479
|
+
const menus = (Array.isArray(settings.pluginInfos) ? settings.pluginInfos : []).map((r) => String(r.menu ?? ""));
|
|
3480
|
+
console.log(JSON.stringify({ file: shown(file), digest: digestOf(bytes), menus, text }, null, 2));
|
|
3481
|
+
} else {
|
|
3482
|
+
console.log(text);
|
|
3483
|
+
}
|
|
3226
3484
|
return 0;
|
|
3227
3485
|
}
|
|
3228
3486
|
async function take(args) {
|
|
3229
3487
|
const ctx = context(args);
|
|
3230
3488
|
if (!ctx.ws) throw new UsageError("take は environments.json があるときだけ使える(inbox/ のダウンロードをアプリのフォルダーへ移す)");
|
|
3231
|
-
const r = takeInbox(
|
|
3232
|
-
for (const m of r.moved) console.log(`${m.file} → ${m.to}${m.same ? "(同じものが既にあったので inbox から消した)" : ""}`);
|
|
3489
|
+
const r = takeInbox(W.root, ctx.ws, args.includes("--env") ? ctx.env.name : void 0);
|
|
3490
|
+
for (const m of r.moved) console.log(`${m.file} → ${m.to}${m.leftInInbox ? "(置いたが inbox に残った)" : m.same ? "(同じものが既にあったので inbox から消した)" : ""}`);
|
|
3233
3491
|
for (const s of r.skipped) console.error(`移さない: ${s.file}(${s.reason})`);
|
|
3492
|
+
for (const w of r.warnings) console.error(`注意: ${w}`);
|
|
3234
3493
|
if (!r.moved.length && !r.skipped.length) console.log(`${INBOX}/ に設定のファイルが無い`);
|
|
3235
3494
|
return r.skipped.length ? 1 : 0;
|
|
3236
3495
|
}
|
|
@@ -3242,15 +3501,16 @@ async function edit(args) {
|
|
|
3242
3501
|
const app = appOf(args, ctx);
|
|
3243
3502
|
const list = listAppFolder(existingAppDir(ctx, app));
|
|
3244
3503
|
if (!list.snapshots.length) throw new InputError(`${shown(list.dir)} にダウンロード / pull のファイルが無い`);
|
|
3245
|
-
src =
|
|
3504
|
+
src = path13.join(list.dir, list.snapshots[0]);
|
|
3246
3505
|
}
|
|
3247
|
-
const file =
|
|
3248
|
-
if (!SNAPSHOT_RE.test(
|
|
3249
|
-
const dest =
|
|
3250
|
-
if (
|
|
3506
|
+
const file = resolveRead2(src);
|
|
3507
|
+
if (!SNAPSHOT_RE.test(path13.basename(file)) || !appFolderOfFile(W.root, file)) throw new UsageError(`edit に渡すのはアプリのフォルダーのダウンロード / pull のファイル(rex0220-print-craft-app<番号>-<日時>.json): ${src}`);
|
|
3508
|
+
const dest = resolveWrite2(editNameOf(file), WRITE_ROOTS.kintone);
|
|
3509
|
+
if (existsSync8(dest)) {
|
|
3251
3510
|
console.log(`既にある(続けて直す): ${shown(dest)}`);
|
|
3252
3511
|
} else {
|
|
3253
|
-
|
|
3512
|
+
allow(dest, "settings");
|
|
3513
|
+
writeText(dest, readTextLimited(file), WRITE_ROOTS.kintone, "settings");
|
|
3254
3514
|
console.log(`${shown(file)} → ${shown(dest)}(ここを直す。ダウンロードのファイルは書き換えない)`);
|
|
3255
3515
|
}
|
|
3256
3516
|
console.log(`次: 直したら npx @rex0220/print-craft-authoring-tools normalize ${shown(dest)} → diff ${shown(file)} ${shown(dest)}`);
|
|
@@ -3278,40 +3538,41 @@ async function files(args) {
|
|
|
3278
3538
|
async function preview(args) {
|
|
3279
3539
|
const [settingsArg] = positional(args);
|
|
3280
3540
|
if (!settingsArg) throw new UsageError("設定 JSON のパスが要る");
|
|
3281
|
-
const settingsFile =
|
|
3541
|
+
const settingsFile = resolveRead2(settingsArg);
|
|
3282
3542
|
const folder = folderDefaults(settingsFile);
|
|
3283
3543
|
const fieldsFile = fieldsFileOf(args, folder);
|
|
3284
3544
|
const recordArg = option(args, "record");
|
|
3285
3545
|
if (!recordArg) throw new UsageError("--record <record.json> が要る(record コマンドの出力。アプリのフォルダーの中のファイルならレコード番号だけでよい: --record 3)");
|
|
3286
|
-
const recordFile = folder && /^\d+$/.test(recordArg) ? existingFile(
|
|
3287
|
-
const outDir =
|
|
3546
|
+
const recordFile = folder && /^\d+$/.test(recordArg) ? existingFile(path13.join(folder.dir, "records", `${recordArg}.json`), `record --app ${folder.appId} --id ${recordArg}`) : resolveRead2(recordArg);
|
|
3547
|
+
const outDir = resolveWriteDir2(option(args, "out-dir") ?? (folder ? path13.join(folder.dir, "out") : "out"), WRITE_ROOTS.out);
|
|
3288
3548
|
const fieldsData = await readFieldsFile(fieldsFile);
|
|
3289
3549
|
const engine = await engineFor();
|
|
3290
3550
|
const result = await runPreview({
|
|
3291
3551
|
settingsText: readTextLimited(settingsFile),
|
|
3292
|
-
settingsFile: relativeSettingsPath(settingsArg),
|
|
3552
|
+
settingsFile: relativeSettingsPath(settingsArg, W.root),
|
|
3293
3553
|
fields: fieldsData,
|
|
3294
3554
|
recordFile: readJsonLimited(recordFile),
|
|
3295
3555
|
engine,
|
|
3296
|
-
policy: loadPolicy(),
|
|
3556
|
+
policy: loadPolicy({ cwd: W.root }),
|
|
3297
3557
|
button: option(args, "button"),
|
|
3298
3558
|
baseUrl: trustedBaseUrl(folder)
|
|
3299
3559
|
});
|
|
3300
3560
|
const written = [];
|
|
3301
3561
|
if (!result.findings.hasErrors) {
|
|
3302
3562
|
for (const r of result.results) {
|
|
3303
|
-
const file =
|
|
3304
|
-
|
|
3563
|
+
const file = resolveWrite2(path13.join(outDir, r.file), WRITE_ROOTS.out);
|
|
3564
|
+
allow(file, "preview");
|
|
3565
|
+
writeText(file, r.html, WRITE_ROOTS.out, "preview");
|
|
3305
3566
|
written.push(shown(file));
|
|
3306
3567
|
}
|
|
3307
3568
|
}
|
|
3308
3569
|
if (flag(args, "json")) {
|
|
3309
|
-
console.log(JSON.stringify({ summary: result.summary, findings: result.findings.items, results: result.results.map((r) => ({ menu: r.menu, file: shown(
|
|
3570
|
+
console.log(JSON.stringify({ summary: result.summary, findings: result.findings.items, results: result.results.map((r) => ({ menu: r.menu, file: shown(path13.join(outDir, r.file)), fileName: r.fileName, pages: r.pages, errors: r.errors })), skipped: result.skipped, written }, null, 2));
|
|
3310
3571
|
} else {
|
|
3311
3572
|
console.log(`preview: ${settingsArg}(${result.summary})`);
|
|
3312
3573
|
console.log(result.findings.format());
|
|
3313
3574
|
for (const r of result.results) {
|
|
3314
|
-
console.log(`${r.menu}: ${r.pages} ページ、ファイル名 ${r.fileName}${r.errors.length ? `、式のエラー ${r.errors.length}` : ""} → ${shown(
|
|
3575
|
+
console.log(`${r.menu}: ${r.pages} ページ、ファイル名 ${r.fileName}${r.errors.length ? `、式のエラー ${r.errors.length}` : ""} → ${shown(path13.join(outDir, r.file))}`);
|
|
3315
3576
|
for (const e of r.errors) console.log(` ${e}`);
|
|
3316
3577
|
}
|
|
3317
3578
|
for (const s of result.skipped) console.log(`対象外: ${s}`);
|
|
@@ -3322,6 +3583,7 @@ async function preview(args) {
|
|
|
3322
3583
|
}
|
|
3323
3584
|
async function main(argv) {
|
|
3324
3585
|
const [command, ...args] = argv;
|
|
3586
|
+
W = createContext({ cwd: process.cwd(), env: process.env });
|
|
3325
3587
|
rejectRemovedOptions(args);
|
|
3326
3588
|
if (args.includes("--env")) context(args);
|
|
3327
3589
|
switch (command) {
|
|
@@ -3368,7 +3630,7 @@ main(process.argv.slice(2)).then(
|
|
|
3368
3630
|
console.error(`${e.message}
|
|
3369
3631
|
${USAGE}`);
|
|
3370
3632
|
process.exitCode = 2;
|
|
3371
|
-
} else if (e instanceof AuthError || e instanceof RestError || e instanceof NotAllowedError || e instanceof PluginZipError || e instanceof PolicyError || e instanceof InputError || e instanceof KintoneUrlError || e instanceof ButtonNotFoundError || e instanceof WorkspaceError) {
|
|
3633
|
+
} else if (e instanceof AuthError || e instanceof RestError || e instanceof NotAllowedError || e instanceof PluginZipError || e instanceof PolicyError || e instanceof InputError || e instanceof KintoneUrlError || e instanceof ButtonNotFoundError || e instanceof WorkspaceError || e instanceof PermissionError) {
|
|
3372
3634
|
console.error(e.message);
|
|
3373
3635
|
process.exitCode = 1;
|
|
3374
3636
|
} else {
|