@auspex-ai/react 0.0.0-stage → 1.4.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/dist/vite.mjs ADDED
@@ -0,0 +1,1843 @@
1
+ import path from "node:path";
2
+ import { createHmac, randomBytes } from "node:crypto";
3
+ import { promises } from "node:fs";
4
+ import { parse } from "@babel/parser";
5
+ import { MagicString } from "magic-string";
6
+ //#region ../build-names/src/element-ref.ts
7
+ const ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
8
+ const REF_LENGTH = 12;
9
+ /** RFC 4648 の base32 を小文字・パディング無しにしたもの */
10
+ function base32(bytes) {
11
+ let out = "";
12
+ let bits = 0;
13
+ let acc = 0;
14
+ for (const b of bytes) {
15
+ acc = acc << 8 | b;
16
+ bits += 8;
17
+ while (bits >= 5) {
18
+ out += ALPHABET[acc >>> bits - 5 & 31];
19
+ bits -= 5;
20
+ }
21
+ acc &= (1 << bits) - 1;
22
+ }
23
+ if (bits > 0) out += ALPHABET[acc << 5 - bits & 31];
24
+ return out;
25
+ }
26
+ /** 名前の参照(`^[a-z2-7]{12}$`)。ビルド鍵は UTF-8 のバイト列として HMAC の鍵にする */
27
+ function nameRef(key, name, kind) {
28
+ return base32(createHmac("sha256", Buffer.from(key, "utf8")).update(`${kind}:${name}`).digest()).slice(0, REF_LENGTH);
29
+ }
30
+ //#endregion
31
+ //#region ../build-names/src/host.ts
32
+ function isValidHost(host) {
33
+ let u;
34
+ try {
35
+ u = new URL(host);
36
+ } catch {
37
+ return false;
38
+ }
39
+ if (u.username || u.password) return false;
40
+ if (u.protocol === "https:") return true;
41
+ return u.protocol === "http:" && (u.hostname === "localhost" || u.hostname === "127.0.0.1");
42
+ }
43
+ /** セルフホスト版で /api/names を受ける internal の面の URL。あれば通常の host より優先する(ビルド時だけ読む) */
44
+ const INTERNAL_HOST_ENV = "AUSPEX_INTERNAL_HOST";
45
+ /**
46
+ * 名前の登録の送り先。環境変数 AUSPEX_INTERNAL_HOST が空でなければそれ(不正な値でも通常の host に
47
+ * 落とさない。落とすと 404 になる面へビルド鍵を送るため、呼び出し側の isValidHost で弾く)。無ければ通常の host
48
+ */
49
+ function registrationHost(host, internal = process.env[INTERNAL_HOST_ENV]) {
50
+ return internal?.trim() ? internal.trim() : host;
51
+ }
52
+ //#endregion
53
+ //#region ../build-names/src/iap-token.ts
54
+ const IAP_SERVICE_ACCOUNT_ENV = "AUSPEX_IAP_SERVICE_ACCOUNT";
55
+ const TOKEN_LIFETIME_SECONDS = 3600;
56
+ /** IAP が検証する aud。host の origin 全体(`<origin>/*`) */
57
+ function iapAudience(host) {
58
+ return `${new URL(host).origin}/*`;
59
+ }
60
+ function iapPayload(serviceAccount, host, nowMs = Date.now()) {
61
+ const iat = Math.floor(nowMs / 1e3);
62
+ return JSON.stringify({
63
+ iss: serviceAccount,
64
+ sub: serviceAccount,
65
+ aud: iapAudience(host),
66
+ iat,
67
+ exp: iat + TOKEN_LIFETIME_SECONDS
68
+ });
69
+ }
70
+ /** 既定の署名: google-auth-library(任意の依存)で ADC を読み、IAM Credentials の signJwt を呼ぶ */
71
+ const signJwtWithAdc = async (serviceAccount, payload) => {
72
+ let lib;
73
+ try {
74
+ lib = await import("google-auth-library");
75
+ } catch {
76
+ throw new Error(`${IAP_SERVICE_ACCOUNT_ENV} が設定されているが google-auth-library が入っていない。ビルドの環境に google-auth-library を入れる(例: pnpm add -D google-auth-library)`);
77
+ }
78
+ const res = await new lib.GoogleAuth({ scopes: ["https://www.googleapis.com/auth/cloud-platform"] }).request({
79
+ url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(serviceAccount)}:signJwt`,
80
+ method: "POST",
81
+ data: { payload },
82
+ timeout: 2e4
83
+ });
84
+ if (!res.data.signedJwt) throw new Error("signJwt の応答に signedJwt が無い");
85
+ return res.data.signedJwt;
86
+ };
87
+ /** `Proxy-Authorization` の値(`Bearer <JWT>`)。失敗は投げる(呼び出し側が警告にする) */
88
+ async function iapProxyAuthorization(host, serviceAccount, sign = signJwtWithAdc) {
89
+ return `Bearer ${await sign(serviceAccount, iapPayload(serviceAccount, host))}`;
90
+ }
91
+ //#endregion
92
+ //#region ../build-names/src/log.ts
93
+ const WARNED = Symbol.for("auspex.warned");
94
+ const PREFIX = "[auspex]";
95
+ const consoleLogger = {
96
+ info: (m) => console.log(`${PREFIX} ${m}`),
97
+ warn: (m) => console.warn(`${PREFIX} ${m}`)
98
+ };
99
+ /** `id` の警告を、このプロセスでまだ出していなければ出す(出したら true) */
100
+ function warnOnce(id, message, logger = consoleLogger) {
101
+ const g = globalThis;
102
+ const seen = g[WARNED] ?? /* @__PURE__ */ new Set();
103
+ g[WARNED] = seen;
104
+ if (seen.has(id)) return false;
105
+ seen.add(id);
106
+ logger.warn(message);
107
+ return true;
108
+ }
109
+ //#endregion
110
+ //#region ../build-names/src/icon-name/transform.ts
111
+ /** 書く属性。@auspex-ai/core の ICON_REF_ATTR(読む側)と同じ値(test/ で一致を確かめる) */
112
+ const ICON_REF_ATTR = "data-auspex-icon-ref";
113
+ /** #12 の旧属性(平文の名前)。今は書かず、手書きのものを消すだけ */
114
+ const LEGACY_ICON_ATTR = "data-auspex-icon";
115
+ const ATTR = ICON_REF_ATTR;
116
+ /** 自分の名前を使わない汎用名(名前は呼び出し側の props か、外から渡された値で決まる) */
117
+ const GENERIC = /^(Icon|Icons|IconBase|BaseIcon|Svg|SvgIcon|IconWrapper|Glyph|StyledSvg)$/;
118
+ /** アイコンらしい名前(呼び出し側の名前 props を拾う対象、根に返すと自分の名前を渡す対象) */
119
+ const ICONISH = /Icon|Svg|Glyph/;
120
+ /** 名前を表す props。type は汎用名の要素だけ(<Button type="submit"> 等を拾わないため) */
121
+ const NAME_PROPS = [
122
+ "name",
123
+ "icon",
124
+ "iconName",
125
+ "glyph"
126
+ ];
127
+ const NAME_PROPS_GENERIC = [...NAME_PROPS, "type"];
128
+ const SKIP_KEYS$2 = /* @__PURE__ */ new Set([
129
+ "loc",
130
+ "start",
131
+ "end",
132
+ "extra",
133
+ "range",
134
+ "leadingComments",
135
+ "trailingComments",
136
+ "innerComments"
137
+ ]);
138
+ /** AST を深さ優先でたどる。stopAtFunctions なら入れ子の関数の中には入らない */
139
+ function walk(node, visit, stopAtFunctions = false, root = true) {
140
+ visit(node);
141
+ if (stopAtFunctions && !root && /Function|ClassMethod/.test(node.type)) return;
142
+ for (const key of Object.keys(node)) {
143
+ if (SKIP_KEYS$2.has(key)) continue;
144
+ const v = node[key];
145
+ if (Array.isArray(v)) {
146
+ for (const c of v) if (c && typeof c.type === "string") walk(c, visit, stopAtFunctions, false);
147
+ } else if (v && typeof v.type === "string") walk(v, visit, stopAtFunctions, false);
148
+ }
149
+ }
150
+ function calleeName(c) {
151
+ if (c.type === "Identifier") return c.name;
152
+ if (c.type === "MemberExpression" && c.property.type === "Identifier") return c.property.name;
153
+ return "";
154
+ }
155
+ /** forwardRef(...) / memo(...) / 型注釈の包みを剥がして関数を取り出す */
156
+ function unwrapFn(e) {
157
+ let n = e;
158
+ while (n && (n.type === "TSAsExpression" || n.type === "TSSatisfiesExpression" || n.type === "ParenthesizedExpression")) n = n.expression;
159
+ if (!n) return null;
160
+ if (n.type === "ArrowFunctionExpression" || n.type === "FunctionExpression") return n;
161
+ if (n.type === "CallExpression" && ["forwardRef", "memo"].includes(calleeName(n.callee))) {
162
+ const [arg] = n.arguments;
163
+ return arg && arg.type !== "SpreadElement" && arg.type !== "ArgumentPlaceholder" ? unwrapFn(arg) : null;
164
+ }
165
+ return null;
166
+ }
167
+ function tagName$1(o) {
168
+ if (o.name.type === "JSXIdentifier") return o.name.name;
169
+ if (o.name.type === "JSXMemberExpression") return o.name.property.name;
170
+ return "";
171
+ }
172
+ /** ビルド時に値が決まる文字列: リテラル / 式を含まないテンプレートリテラル / 両辺がそうである条件式 */
173
+ function isStaticString(e) {
174
+ if (!e) return false;
175
+ switch (e.type) {
176
+ case "StringLiteral": return true;
177
+ case "TemplateLiteral": return e.expressions.length === 0;
178
+ case "ConditionalExpression": return isStaticString(e.consequent) && isStaticString(e.alternate);
179
+ case "ParenthesizedExpression": return isStaticString(e.expression);
180
+ default: return false;
181
+ }
182
+ }
183
+ /** アイコンらしい要素で、名前を表す props が静的な文字列ならその式を返す */
184
+ function staticNameProp(o) {
185
+ const tag = tagName$1(o);
186
+ if (!/^[A-Z]/.test(tag) || !ICONISH.test(tag)) return null;
187
+ const props = GENERIC.test(tag) ? NAME_PROPS_GENERIC : NAME_PROPS;
188
+ for (const a of o.attributes) {
189
+ if (a.type !== "JSXAttribute" || a.name.type !== "JSXIdentifier" || !props.includes(a.name.name) || !a.value) continue;
190
+ const v = a.value.type === "JSXExpressionContainer" ? a.value.expression : a.value;
191
+ if (isStaticString(v)) return v;
192
+ }
193
+ return null;
194
+ }
195
+ /** 名前を表す props を受け取るコンポーネントは汎用とみなす */
196
+ function takesNameProp(fn) {
197
+ const [p0] = fn.params;
198
+ return p0?.type === "ObjectPattern" && p0.properties.some((p) => p.type === "ObjectProperty" && p.key.type === "Identifier" && NAME_PROPS_GENERIC.includes(p.key.name));
199
+ }
200
+ function collectComponents(program, file) {
201
+ const comps = [];
202
+ const base = path.basename(file).replace(/\.[^.]+$/, "");
203
+ walk(program, (n) => {
204
+ if (n.type === "FunctionDeclaration" && n.id) comps.push({
205
+ fn: n,
206
+ name: n.id.name,
207
+ isClass: false
208
+ });
209
+ else if (n.type === "VariableDeclarator" && n.id.type === "Identifier") {
210
+ const fn = unwrapFn(n.init);
211
+ if (fn) comps.push({
212
+ fn,
213
+ name: n.id.name,
214
+ isClass: false
215
+ });
216
+ } else if (n.type === "ObjectProperty" && !n.computed) {
217
+ const fn = unwrapFn(n.value);
218
+ const key = n.key.type === "Identifier" ? n.key.name : n.key.type === "StringLiteral" ? n.key.value : null;
219
+ if (fn && key) comps.push({
220
+ fn,
221
+ name: key,
222
+ isClass: false
223
+ });
224
+ } else if (n.type === "ExportDefaultDeclaration") {
225
+ const d = n.declaration;
226
+ const fn = d.type === "FunctionDeclaration" ? d.id ? null : d : unwrapFn(d);
227
+ if (fn) comps.push({
228
+ fn,
229
+ name: base,
230
+ isClass: false
231
+ });
232
+ } else if (n.type === "ClassDeclaration" && n.id) {
233
+ const render = n.body.body.find((m) => m.type === "ClassMethod" && m.key.type === "Identifier" && m.key.name === "render");
234
+ if (render) comps.push({
235
+ fn: render,
236
+ name: n.id.name,
237
+ isClass: true
238
+ });
239
+ }
240
+ });
241
+ return comps;
242
+ }
243
+ /** コンポーネントが返す根の要素(return / 式本体 / 条件式の両辺 / && の右辺 / フラグメントの最初の要素) */
244
+ function rootsOf(fn) {
245
+ const roots = [];
246
+ const add = (e) => {
247
+ let n = e;
248
+ while (n && (n.type === "ParenthesizedExpression" || n.type === "TSAsExpression")) n = n.expression;
249
+ if (!n) return;
250
+ if (n.type === "JSXElement") roots.push({ jsx: n.openingElement });
251
+ else if (n.type === "ConditionalExpression") {
252
+ add(n.consequent);
253
+ add(n.alternate);
254
+ } else if (n.type === "LogicalExpression") add(n.right);
255
+ else if (n.type === "JSXFragment") {
256
+ const first = n.children.find((c) => c.type === "JSXElement");
257
+ if (first) roots.push({ jsx: first.openingElement });
258
+ } else if (n.type === "CallExpression" && [
259
+ "createElement",
260
+ "jsx",
261
+ "jsxs",
262
+ "_jsx",
263
+ "_jsxs"
264
+ ].includes(calleeName(n.callee)) && n.arguments[0]?.type === "StringLiteral" && n.arguments[0].value === "svg") roots.push({ call: n });
265
+ };
266
+ if (fn.body.type !== "BlockStatement") add(fn.body);
267
+ else walk(fn.body, (n) => n.type === "ReturnStatement" && add(n.argument), true);
268
+ return roots;
269
+ }
270
+ const startOf$1 = (n) => n.start ?? 0;
271
+ const endOf$1 = (n) => n.end ?? 0;
272
+ /** JS/TS のソースを解析する(解析できなければ null。ビルドはバンドラ側のエラーに任せる) */
273
+ function parseSource(source, file) {
274
+ const ext = path.extname(file);
275
+ try {
276
+ return parse(source, {
277
+ sourceType: "module",
278
+ plugins: ext === ".ts" ? ["typescript"] : ext === ".tsx" ? ["typescript", "jsx"] : ["jsx"]
279
+ }).program;
280
+ } catch {
281
+ return null;
282
+ }
283
+ }
284
+ /** アイコンに関係しうるファイルか(解析する前の絞り込み) */
285
+ function mayHaveIcons(source) {
286
+ return /svg|Icon|Glyph/.test(source);
287
+ }
288
+ /**
289
+ * アイコン名の参照の書き込みを `s` に積む(要素名の変換と同じ MagicString に積めるよう分けている。手書きの属性の削除は
290
+ * 先に stripHandWritten で積む)。参照を書いた名前を返す
291
+ */
292
+ function applyIconNames(program, source, file, s, refOf) {
293
+ const touched = /* @__PURE__ */ new Set();
294
+ const written = /* @__PURE__ */ new Set();
295
+ const refLiteral = (name) => {
296
+ written.add(name);
297
+ return JSON.stringify(refOf(name));
298
+ };
299
+ /** 静的な文字列の式を、各リテラルを参照に置き換えた式にする(名前は written に残る) */
300
+ const refExpr = (e) => {
301
+ switch (e.type) {
302
+ case "StringLiteral": return refLiteral(e.value);
303
+ case "TemplateLiteral": return refLiteral(e.quasis[0]?.value.cooked ?? "");
304
+ case "ConditionalExpression": return `${source.slice(startOf$1(e.test), endOf$1(e.test))} ? ${refExpr(e.consequent)} : ${refExpr(e.alternate)}`;
305
+ case "ParenthesizedExpression": return `(${refExpr(e.expression)})`;
306
+ default: return "undefined";
307
+ }
308
+ };
309
+ for (const { fn, name, isClass } of collectComponents(program, file)) {
310
+ const roots = rootsOf(fn);
311
+ const svgRoots = roots.flatMap((r) => "jsx" in r && tagName$1(r.jsx) === "svg" ? [r.jsx] : []);
312
+ const callRoots = roots.flatMap((r) => "call" in r ? [r.call] : []);
313
+ const iconishRoots = roots.flatMap((r) => "jsx" in r && /^[A-Z]/.test(tagName$1(r.jsx)) && ICONISH.test(tagName$1(r.jsx)) ? [r.jsx] : []);
314
+ if (!svgRoots.length && !callRoots.length && !iconishRoots.length) continue;
315
+ const generic = GENERIC.test(name) || !isClass && takesNameProp(fn);
316
+ let passed = null;
317
+ const [p0] = fn.params;
318
+ if (isClass) passed = `this.props?.[${JSON.stringify(ATTR)}]`;
319
+ else if (p0?.type === "ObjectPattern") {
320
+ s.appendRight(startOf$1(p0) + 1, ` ${JSON.stringify(ATTR)}: __auspexIcon,`);
321
+ passed = "__auspexIcon";
322
+ } else if (p0?.type === "Identifier") passed = `${p0.name}?.[${JSON.stringify(ATTR)}]`;
323
+ let cached;
324
+ const iconValue = () => {
325
+ if (cached === void 0) {
326
+ const own = generic ? "undefined" : refLiteral(name);
327
+ cached = passed ? `${passed} ?? ${own}` : own;
328
+ }
329
+ return cached;
330
+ };
331
+ for (const o of svgRoots) {
332
+ if (touched.has(o)) continue;
333
+ s.appendLeft(endOf$1(o.name), ` ${ATTR}={${iconValue()}}`);
334
+ touched.add(o);
335
+ }
336
+ for (const call of callRoots) {
337
+ const props = call.arguments[1];
338
+ const entry = `${JSON.stringify(ATTR)}: ${iconValue()}`;
339
+ if (!props) {
340
+ const tagArg = call.arguments[0];
341
+ if (tagArg) s.appendLeft(endOf$1(tagArg), `, { ${entry} }`);
342
+ } else if (props.type === "NullLiteral") s.overwrite(startOf$1(props), endOf$1(props), `{ ${entry} }`);
343
+ else if (props.type === "ObjectExpression") s.appendRight(startOf$1(props) + 1, ` ${entry},`);
344
+ else {
345
+ s.prependLeft(startOf$1(props), "{ ...");
346
+ s.appendRight(endOf$1(props), `, ${entry} }`);
347
+ }
348
+ }
349
+ for (const o of iconishRoots) {
350
+ if (touched.has(o) || staticNameProp(o)) continue;
351
+ if (generic && !passed) continue;
352
+ s.appendLeft(endOf$1(o.name), ` ${ATTR}={${generic ? passed : iconValue()}}`);
353
+ touched.add(o);
354
+ }
355
+ }
356
+ walk(program, (n) => {
357
+ if (n.type !== "JSXOpeningElement" || touched.has(n)) return;
358
+ const v = staticNameProp(n);
359
+ if (!v) return;
360
+ s.appendLeft(endOf$1(n.name), ` ${ATTR}={${refExpr(v)}}`);
361
+ touched.add(n);
362
+ });
363
+ return [...written];
364
+ }
365
+ const MAX_BUILD_ID = 128;
366
+ const MAX_RETRIES = 3;
367
+ /** ディレクトリのプロジェクトからの相対パス(ローダーが this.rootContext に対して解決する)。POSIX の区切りで書く */
368
+ const fragmentsSubdir = (keyId) => `cache/auspex/names/${keyId}`;
369
+ const KIND_ORDER = {
370
+ element: 0,
371
+ icon: 1,
372
+ label: 2
373
+ };
374
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
375
+ /**
376
+ * 断片を読んで要素名・アイコン名の集合(それぞれ辞書順)にする。壊れた断片は警告して飛ばす。
377
+ * `icons` の無い段階 1 の断片も読める
378
+ */
379
+ async function readFragments(dir, logger) {
380
+ let files;
381
+ try {
382
+ files = (await promises.readdir(dir)).filter((f) => f.endsWith(".json"));
383
+ } catch {
384
+ return {
385
+ names: [],
386
+ icons: [],
387
+ labels: []
388
+ };
389
+ }
390
+ const names = /* @__PURE__ */ new Set();
391
+ const icons = /* @__PURE__ */ new Set();
392
+ const labels = /* @__PURE__ */ new Set();
393
+ for (const f of files) try {
394
+ const parsed = JSON.parse(await promises.readFile(path.join(dir, f), "utf8"));
395
+ if (!Array.isArray(parsed.names)) throw new Error("names が配列ではない");
396
+ if (parsed.icons !== void 0 && !Array.isArray(parsed.icons)) throw new Error("icons が配列ではない");
397
+ if (parsed.labels !== void 0 && !Array.isArray(parsed.labels)) throw new Error("labels が配列ではない");
398
+ for (const n of parsed.names) if (typeof n === "string") names.add(n);
399
+ for (const n of parsed.icons ?? []) if (typeof n === "string") icons.add(n);
400
+ for (const n of parsed.labels ?? []) if (typeof n === "string") labels.add(n);
401
+ } catch (e) {
402
+ logger.warn(`壊れた断片を飛ばした: ${path.join(dir, f)}(${e.message})`);
403
+ }
404
+ return {
405
+ names: [...names].sort(),
406
+ icons: [...icons].sort(),
407
+ labels: [...labels].sort()
408
+ };
409
+ }
410
+ /** build_id: `<distDir>/BUILD_ID`(か --build-id)。無い・128 文字超は自前の id(1 回の実行で 1 度だけ作る) */
411
+ async function resolveBuildId(distDirAbs, override, logger, readFile = true) {
412
+ let raw = override;
413
+ if (raw === void 0 && readFile) try {
414
+ raw = (await promises.readFile(path.join(distDirAbs, "BUILD_ID"), "utf8")).trim();
415
+ } catch {}
416
+ if (raw && raw.length <= MAX_BUILD_ID) return raw;
417
+ const own = `local-${Date.now()}-${randomBytes(4).toString("hex")}`;
418
+ logger.info(raw ? `build_id が長すぎる(${raw.length} 文字。上限 ${MAX_BUILD_ID})ので、自前の id を使う: ${own}` : `build_id(${path.join(distDirAbs, "BUILD_ID")})が無いので、自前の id を使う: ${own}`);
419
+ return own;
420
+ }
421
+ async function post(o, buildId, names, icons, labels) {
422
+ const url = `${o.host.replace(/\/+$/, "")}/api/names`;
423
+ let reason = "";
424
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
425
+ if (attempt > 0) await o.sleep(500 * 2 ** (attempt - 1));
426
+ try {
427
+ const res = await o.fetch(url, {
428
+ method: "POST",
429
+ headers: {
430
+ "content-type": "application/json",
431
+ authorization: `Bearer ${o.key}`,
432
+ ...o.proxyAuthorization ? { "proxy-authorization": o.proxyAuthorization } : {}
433
+ },
434
+ body: JSON.stringify({
435
+ build_id: buildId,
436
+ names,
437
+ ...icons.length > 0 ? { icons } : {},
438
+ ...labels.length > 0 ? { labels } : {}
439
+ }),
440
+ signal: AbortSignal.timeout(2e4),
441
+ redirect: "error"
442
+ });
443
+ if (res.status === 401) return { kind: "unauthorized" };
444
+ if (res.status >= 500) {
445
+ reason = `HTTP ${res.status}`;
446
+ continue;
447
+ }
448
+ if (res.status !== 200) return {
449
+ kind: "rejected",
450
+ status: res.status
451
+ };
452
+ const body = await res.json();
453
+ if (!Array.isArray(body.results)) return {
454
+ kind: "rejected",
455
+ status: res.status
456
+ };
457
+ return {
458
+ kind: "ok",
459
+ results: body.results
460
+ };
461
+ } catch (e) {
462
+ reason = e.message;
463
+ }
464
+ }
465
+ return {
466
+ kind: "failed",
467
+ reason
468
+ };
469
+ }
470
+ /**
471
+ * 断片の名前を登録する。例外は投げない(ネットワーク・5xx・401・400・413 のどれでもビルドを落とさず警告する)
472
+ */
473
+ async function registerNames(options) {
474
+ const logger = options.logger ?? consoleLogger;
475
+ const summary = {
476
+ buildId: null,
477
+ names: 0,
478
+ icons: 0,
479
+ labels: 0,
480
+ registered: 0,
481
+ already: 0,
482
+ dropped: [],
483
+ collisions: [],
484
+ unregistered: []
485
+ };
486
+ try {
487
+ let names;
488
+ let icons;
489
+ let labels;
490
+ let buildId;
491
+ if ("names" in options) {
492
+ names = [...new Set(options.names)].sort();
493
+ icons = [...new Set(options.icons)].sort();
494
+ summary.names = names.length;
495
+ labels = [...new Set(options.labels ?? [])].sort();
496
+ summary.icons = icons.length;
497
+ summary.labels = labels.length;
498
+ if (names.length + icons.length + labels.length === 0) logger.info("登録する名前が無い");
499
+ buildId = await resolveBuildId("", options.buildId, logger, false);
500
+ } else {
501
+ const distDirAbs = path.resolve(options.projectDir ?? process.cwd(), options.distDir);
502
+ const dir = path.join(distDirAbs, fragmentsSubdir(options.keyId));
503
+ ({names, icons, labels} = await readFragments(dir, logger));
504
+ summary.names = names.length;
505
+ summary.icons = icons.length;
506
+ summary.labels = labels.length;
507
+ if (names.length + icons.length + labels.length === 0) logger.info(`登録する名前が無い(断片: ${dir})`);
508
+ buildId = await resolveBuildId(distDirAbs, options.buildId, logger, options.readBuildIdFile ?? true);
509
+ }
510
+ summary.buildId = buildId;
511
+ const iapServiceAccount = options.iapServiceAccount ?? process.env["AUSPEX_IAP_SERVICE_ACCOUNT"];
512
+ const proxyAuthorization = iapServiceAccount ? await iapProxyAuthorization(options.host, iapServiceAccount, options.iapSigner).catch((e) => {
513
+ throw new Error(`IAP 用のトークンを作れなかった: ${e.message}`);
514
+ }) : void 0;
515
+ const o = {
516
+ key: options.key,
517
+ host: options.host,
518
+ proxyAuthorization,
519
+ fetch: options.fetch ?? fetch,
520
+ sleep: options.sleep ?? defaultSleep
521
+ };
522
+ const items = (ns, is, ls) => [
523
+ ...ns.map((name) => ({
524
+ kind: "element",
525
+ name
526
+ })),
527
+ ...is.map((name) => ({
528
+ kind: "icon",
529
+ name
530
+ })),
531
+ ...ls.map((name) => ({
532
+ kind: "label",
533
+ name
534
+ }))
535
+ ];
536
+ const chunks = (list) => {
537
+ const out = [];
538
+ for (let i = 0; i < list.length; i += 100) out.push(list.slice(i, i + 100));
539
+ return out;
540
+ };
541
+ const retry = [];
542
+ const unregistered = /* @__PURE__ */ new Map();
543
+ const markUnregistered = (item, reason) => {
544
+ unregistered.set(`${item.kind}\0${item.name}`, {
545
+ item,
546
+ reason
547
+ });
548
+ };
549
+ const label = (kind) => kind === "icon" ? "アイコン名" : kind === "label" ? "ラベル" : "名前";
550
+ const send = async (list, second) => {
551
+ const all = list.length === 0 ? [[]] : chunks(list);
552
+ for (const [ci, chunk] of all.entries()) {
553
+ const out = await post(o, buildId, chunk.filter((i) => i.kind === "element").map((i) => i.name), chunk.filter((i) => i.kind === "icon").map((i) => i.name), chunk.filter((i) => i.kind === "label").map((i) => i.name));
554
+ if (out.kind === "unauthorized") {
555
+ logger.warn("ビルド鍵が受け付けられなかった(401)。鍵が違うか、再発行された可能性がある。登録せずに続ける(名前なしのクリックとして入る)");
556
+ for (const n of all.slice(ci).flat()) markUnregistered(n, "401");
557
+ return "stop";
558
+ }
559
+ if (out.kind === "rejected") {
560
+ logger.warn(`登録が拒否された(HTTP ${out.status}、${chunk.length} 件のチャンク)。再送しない`);
561
+ for (const n of chunk) markUnregistered(n, `HTTP ${out.status}`);
562
+ continue;
563
+ }
564
+ if (out.kind === "failed") {
565
+ logger.warn(`登録を送れなかった(${MAX_RETRIES} 回送り直した。${out.reason})。ビルドは続ける。次のビルドで再登録される`);
566
+ for (const n of chunk) markUnregistered(n, out.reason);
567
+ continue;
568
+ }
569
+ for (const r of out.results) {
570
+ const kind = r.kind === "icon" ? "icon" : r.kind === "label" ? "label" : "element";
571
+ if (r.status === "registered") summary.registered++;
572
+ else if (r.status === "already") summary.already++;
573
+ else if (r.status === "collision") {
574
+ summary.collisions.push({
575
+ kind,
576
+ name: r.name,
577
+ winner: r.winner
578
+ });
579
+ logger.warn(`${label(kind)} "${r.name}" は登録済みの "${r.winner}" と同じ参照になった。"${r.name}" の${kind === "icon" ? "アイコン" : kind === "label" ? "ラベル" : "要素"}は "${r.winner}" の名前で記録される`);
580
+ } else if (r.status === "retry") {
581
+ if (second) markUnregistered({
582
+ kind,
583
+ name: r.name
584
+ }, "retry");
585
+ else retry.push({
586
+ kind,
587
+ name: r.name
588
+ });
589
+ } else {
590
+ summary.dropped.push({
591
+ kind,
592
+ name: r.name,
593
+ status: r.status
594
+ });
595
+ logger.warn(`${label(kind)} "${r.name}" は登録されなかった(${r.status})`);
596
+ }
597
+ }
598
+ }
599
+ return "go";
600
+ };
601
+ if (await send(items(names, icons, labels), false) === "go" && retry.length > 0) await send(retry.sort((a, b) => KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)), true);
602
+ for (const { item, reason } of unregistered.values()) {
603
+ summary.unregistered.push(item);
604
+ if (reason !== "401") logger.warn(`${label(item.kind)} "${item.name}" は未登録(${reason})。次のビルドで再登録される`);
605
+ }
606
+ logger.info(`名前の登録: 要素名 ${summary.names} 件・アイコン名 ${summary.icons} 件・ラベル ${summary.labels} 件中 新規 ${summary.registered}・登録済み ${summary.already}・捨てた ${summary.dropped.length}・衝突 ${summary.collisions.length}・未登録 ${summary.unregistered.length}(build_id ${buildId})`);
607
+ } catch (e) {
608
+ logger.warn(`名前の登録に失敗した(ビルドは続ける): ${e.message}`);
609
+ }
610
+ return summary;
611
+ }
612
+ //#endregion
613
+ //#region ../build-names/src/element-name/label.ts
614
+ /** 式の置き換え */
615
+ const HOLE = "[*]";
616
+ /** 子の中のテキストを文書順に集める(入れ子の要素の子を含む)。式は HOLE にする */
617
+ function collect(children, out) {
618
+ for (const c of children) switch (c.type) {
619
+ case "JSXText":
620
+ out.push(c.value);
621
+ break;
622
+ case "JSXElement":
623
+ case "JSXFragment":
624
+ collect(c.children ?? [], out);
625
+ break;
626
+ case "JSXExpressionContainer":
627
+ out.push(fromExpression(c.expression));
628
+ break;
629
+ case "JSXSpreadChild": out.push(HOLE);
630
+ }
631
+ }
632
+ /** 式 1 つ分のテキスト。文字列リテラルと式の無いテンプレートリテラルだけが静的な文言 */
633
+ function fromExpression(e) {
634
+ if (!e || e.type === "JSXEmptyExpression") return "";
635
+ if (e.type === "StringLiteral") return e.value;
636
+ if (e.type === "TemplateLiteral") {
637
+ let s = "";
638
+ for (const [i, q] of e.quasis.entries()) {
639
+ s += q.value.cooked ?? q.value.raw;
640
+ if (i < e.expressions.length) s += HOLE;
641
+ }
642
+ return s;
643
+ }
644
+ return HOLE;
645
+ }
646
+ function normalize(raw) {
647
+ return raw.replace(/\s+/g, " ").trim().replace(/\[\*\](?: ?\[\*\])+/g, HOLE).normalize("NFC");
648
+ }
649
+ /** [*]・空白・句読点を除いて何も残らないか */
650
+ function isEmptyText(s) {
651
+ return s.split(HOLE).join("").replace(/[\s\p{P}]/gu, "") === "";
652
+ }
653
+ /** 静的な文字列リテラルの属性値(式は取らない) */
654
+ function staticAttr(o, name) {
655
+ for (const a of o.attributes) {
656
+ if (a.type !== "JSXAttribute" || a.name.type !== "JSXIdentifier" || a.name.name !== name) continue;
657
+ return a.value?.type === "StringLiteral" ? a.value.value : null;
658
+ }
659
+ return null;
660
+ }
661
+ /** クリックされうる JSX 要素のラベル。取れない・使えない(実行時の値だけ・句読点だけ・長すぎる)ときは null */
662
+ function labelOf(el) {
663
+ const parts = [];
664
+ collect(el.children ?? [], parts);
665
+ let text = normalize(parts.join(""));
666
+ if (isEmptyText(text)) {
667
+ text = "";
668
+ for (const attr of ["aria-label", "title"]) {
669
+ const v = staticAttr(el.openingElement, attr);
670
+ if (v === null) continue;
671
+ const n = normalize(v);
672
+ if (!isEmptyText(n)) {
673
+ text = n;
674
+ break;
675
+ }
676
+ }
677
+ }
678
+ if (text === "" || [...text].length > 80) return null;
679
+ return text;
680
+ }
681
+ //#endregion
682
+ //#region ../build-names/src/element-name/extract.ts
683
+ const SKIP_KEYS$1 = /* @__PURE__ */ new Set([
684
+ "loc",
685
+ "start",
686
+ "end",
687
+ "extra",
688
+ "range",
689
+ "leadingComments",
690
+ "trailingComments",
691
+ "innerComments"
692
+ ]);
693
+ function children(n) {
694
+ const out = [];
695
+ for (const k of Object.keys(n)) {
696
+ if (SKIP_KEYS$1.has(k)) continue;
697
+ const v = n[k];
698
+ if (Array.isArray(v)) {
699
+ for (const c of v) if (c && typeof c.type === "string") out.push(c);
700
+ } else if (v && typeof v.type === "string") out.push(v);
701
+ }
702
+ return out;
703
+ }
704
+ const GENERIC_HANDLER = /^((on|handle)(Click|Press|Tap|Change|Select|Submit|PointerDown|MouseDown|TouchStart|KeyDown|DoubleClick|Action)?|callback|cb|fn|handler|action|formAction|onClickHandler|clickHandler)$/;
705
+ const GENERIC_COMPONENT = /^(App|Common|Shared|My|Main|Primary|Secondary|Submit)?(Button|IconButton|Link|NavLink|Anchor|Clickable|Pressable|Item|MenuItem|ListItem|Root|Trigger|Wrapper|Card|Tab|Option|Chip|App|Component|Default|Page|Layout|Home)$|^(Base|Styled|Custom|Ui|UI|Primitive)[A-Z]\w*$|(Page|Layout|Template)$/;
706
+ const GENERIC_MEMBER_PROP = /* @__PURE__ */ new Set([
707
+ "mutate",
708
+ "mutateAsync",
709
+ "trigger",
710
+ "execute",
711
+ "run",
712
+ "call",
713
+ "open",
714
+ "close",
715
+ "toggle",
716
+ "show",
717
+ "hide",
718
+ "reset",
719
+ "refetch",
720
+ "submit",
721
+ "onOpen",
722
+ "onClose",
723
+ "onToggle"
724
+ ]);
725
+ /** 呼んでも要素の意味にならない呼び出し */
726
+ const NOISE_CALLS = /* @__PURE__ */ new Set([
727
+ "preventDefault",
728
+ "stopPropagation",
729
+ "stopImmediatePropagation",
730
+ "log",
731
+ "warn",
732
+ "error",
733
+ "info",
734
+ "debug",
735
+ "alert",
736
+ "confirm",
737
+ "prompt",
738
+ "setTimeout",
739
+ "clearTimeout",
740
+ "requestAnimationFrame",
741
+ "startTransition",
742
+ "then",
743
+ "catch",
744
+ "finally",
745
+ "resolve",
746
+ "all",
747
+ "focus",
748
+ "blur",
749
+ "scrollIntoView",
750
+ "success",
751
+ "loading",
752
+ "dismiss",
753
+ "toast",
754
+ "Number",
755
+ "String",
756
+ "Boolean",
757
+ "parseInt",
758
+ "structuredClone"
759
+ ]);
760
+ /** メンバー呼び出しのときだけ雑音(配列・文字列・ストレージのメソッド) */
761
+ const NOISE_MEMBER = /* @__PURE__ */ new Set([
762
+ "stringify",
763
+ "parse",
764
+ "push",
765
+ "filter",
766
+ "map",
767
+ "includes",
768
+ "find",
769
+ "slice",
770
+ "trim",
771
+ "join",
772
+ "replace",
773
+ "getItem",
774
+ "setItem",
775
+ "removeItem",
776
+ "getTime",
777
+ "now",
778
+ "max",
779
+ "min",
780
+ "floor",
781
+ "round",
782
+ "random",
783
+ "toString",
784
+ "toISOString",
785
+ "current",
786
+ "forEach",
787
+ "concat",
788
+ "split",
789
+ "set",
790
+ "get",
791
+ "delete",
792
+ "has",
793
+ "add"
794
+ ]);
795
+ const NOISE_OBJECTS = /* @__PURE__ */ new Set([
796
+ "e",
797
+ "ev",
798
+ "evt",
799
+ "event",
800
+ "console",
801
+ "toast",
802
+ "Math",
803
+ "Date",
804
+ "JSON",
805
+ "Object",
806
+ "Array",
807
+ "localStorage",
808
+ "sessionStorage"
809
+ ]);
810
+ /** 既存の計測呼び出し(第 1 引数などのイベント名を名前にする) */
811
+ const TRACK_CALLEES = /* @__PURE__ */ new Set([
812
+ "track",
813
+ "trackEvent",
814
+ "sendGAEvent",
815
+ "sendGTMEvent",
816
+ "logEvent",
817
+ "gtag",
818
+ "capture",
819
+ "logAnalyticsEvent",
820
+ "analyticsTrack"
821
+ ]);
822
+ const TRACK_OBJECTS = /* @__PURE__ */ new Set([
823
+ "posthog",
824
+ "mixpanel",
825
+ "amplitude",
826
+ "analytics",
827
+ "ga"
828
+ ]);
829
+ const CLICKISH_COMPONENT = /Button|Link|Tab|MenuItem|Trigger|Checkbox|Radio|Switch|Toggle|SelectItem|Option|Chip|Action$/;
830
+ const HANDLER_PROPS = [
831
+ "onClick",
832
+ "onPress",
833
+ "onPointerDown",
834
+ "onMouseDown",
835
+ "onTouchStart",
836
+ "onDoubleClick",
837
+ "onKeyDown",
838
+ "onChange",
839
+ "onCheckedChange",
840
+ "onValueChange",
841
+ "onSelect",
842
+ "onToggle"
843
+ ];
844
+ const GROUP_HANDLER_PROPS = [
845
+ "onValueChange",
846
+ "onChange",
847
+ "onCheckedChange"
848
+ ];
849
+ const VALUE_ELEMENTS = /^(input|option|SelectItem|TabsTrigger|RadioGroupItem|ToggleGroupItem|DropdownMenuRadioItem|Radio|Tab)$/;
850
+ const NATIVE_CLICKABLE = /* @__PURE__ */ new Set([
851
+ "button",
852
+ "a",
853
+ "input",
854
+ "select",
855
+ "label",
856
+ "summary",
857
+ "area",
858
+ "option"
859
+ ]);
860
+ const CLICK_ROLES = /* @__PURE__ */ new Set([
861
+ "button",
862
+ "link",
863
+ "tab",
864
+ "menuitem",
865
+ "option",
866
+ "switch",
867
+ "checkbox",
868
+ "radio",
869
+ "menuitemradio",
870
+ "menuitemcheckbox"
871
+ ]);
872
+ /** 配列の名前として意味を持たない変数名 */
873
+ const GENERIC_ARRAY = /^(items|list|data|rows|options|entries|children|props)$/;
874
+ /**
875
+ * 候補の名前として使える形か(受信口の形の規則 `isElementName` と同じ)。ここでは候補を選ぶためだけに使い、
876
+ * 書き込んだ値の検査は受信口に一本化する(Design Doc #43 の 3 節)
877
+ */
878
+ const ID = /^[A-Za-z0-9_.:/[\]*#|-]{1,64}$/;
879
+ function usable(n) {
880
+ return !!n && ID.test(n) && !/\d{3,}/.test(n.replace(/\[\*\]/g, ""));
881
+ }
882
+ const WRAPPERS = /* @__PURE__ */ new Set([
883
+ "TSAsExpression",
884
+ "TSSatisfiesExpression",
885
+ "TSNonNullExpression",
886
+ "ParenthesizedExpression",
887
+ "JSXExpressionContainer",
888
+ "AwaitExpression",
889
+ "TSTypeAssertion"
890
+ ]);
891
+ function unwrap(e) {
892
+ let n = e;
893
+ while (n && WRAPPERS.has(n.type)) n = n.type === "AwaitExpression" ? n.argument : n.expression;
894
+ return n;
895
+ }
896
+ function staticString(e) {
897
+ const n = unwrap(e);
898
+ if (!n) return null;
899
+ if (n.type === "StringLiteral") return n.value;
900
+ if (n.type === "TemplateLiteral" && n.expressions.length === 0) return n.quasis[0].value.cooked ?? null;
901
+ return null;
902
+ }
903
+ /** 引数の静的な値: 文字列・数値・真偽・null と、x ± 数値 の ±N */
904
+ function literalArg(e) {
905
+ const str = staticString(e);
906
+ if (str !== null) return str;
907
+ const n = unwrap(e);
908
+ if (!n) return null;
909
+ if (n.type === "NumericLiteral") return String(n.value);
910
+ if (n.type === "UnaryExpression" && n.operator === "-" && n.argument.type === "NumericLiteral") return `-${n.argument.value}`;
911
+ if (n.type === "BooleanLiteral") return String(n.value);
912
+ if (n.type === "NullLiteral") return "null";
913
+ if (n.type === "BinaryExpression" && (n.operator === "+" || n.operator === "-") && n.right.type === "NumericLiteral") return `${n.operator}${n.right.value}`;
914
+ return null;
915
+ }
916
+ function keyName(p) {
917
+ if (p.computed) return null;
918
+ if (p.key.type === "Identifier") return p.key.name;
919
+ if (p.key.type === "StringLiteral") return p.key.value;
920
+ return null;
921
+ }
922
+ /** パスとして: 式の部分は [*]。静的な部分が無ければ null */
923
+ function pathTemplate(e) {
924
+ const n = unwrap(e);
925
+ if (!n) return null;
926
+ const s = staticString(n);
927
+ if (s !== null) return s;
928
+ if (n.type === "TemplateLiteral") {
929
+ let out = "";
930
+ n.quasis.forEach((q, i) => {
931
+ out += q.value.cooked;
932
+ if (i < n.expressions.length) out += "[*]";
933
+ });
934
+ return out;
935
+ }
936
+ if (n.type === "BinaryExpression" && n.operator === "+") {
937
+ const l = pathTemplate(n.left);
938
+ if (l === null) return null;
939
+ return l + (pathTemplate(n.right) ?? "[*]");
940
+ }
941
+ if (n.type === "ObjectExpression") {
942
+ const p = n.properties.find((p) => p.type === "ObjectProperty" && keyName(p) === "pathname");
943
+ return p ? pathTemplate(p.value) : null;
944
+ }
945
+ return null;
946
+ }
947
+ /** パスを名前にする: クエリ・ハッシュを落とし、外部 URL はホストだけ、mailto・tel は種類だけ */
948
+ function normalizePath(p) {
949
+ if (p === null) return null;
950
+ const s = p.trim();
951
+ if (/^mailto:/i.test(s)) return "mailto";
952
+ if (/^tel:/i.test(s)) return "tel";
953
+ if (/^https?:\/\//i.test(s)) {
954
+ const host = s.replace(/^https?:\/\//i, "").split(/[/?#]/)[0] ?? "";
955
+ return host.includes("[*]") || !host ? null : `ext:${host}`;
956
+ }
957
+ if (s.startsWith("#")) return s.length > 1 ? s : null;
958
+ if (!s.startsWith("/")) return null;
959
+ return (s.split(/[?#]/)[0] ?? "").replace(/\[\*\][^/]*/g, "[*]").replace(/\/+$/, "") || "/";
960
+ }
961
+ function calleeParts(c) {
962
+ const n = unwrap(c);
963
+ if (n.type === "Identifier") return {
964
+ obj: null,
965
+ prop: n.name
966
+ };
967
+ if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
968
+ const prop = !n.computed && n.property.type === "Identifier" ? n.property.name : null;
969
+ const o = unwrap(n.object);
970
+ return {
971
+ obj: o.type === "Identifier" ? o.name : o.type === "MemberExpression" && !o.computed && o.property.type === "Identifier" ? o.property.name : o.type === "ThisExpression" ? "this" : null,
972
+ prop
973
+ };
974
+ }
975
+ return {
976
+ obj: null,
977
+ prop: null
978
+ };
979
+ }
980
+ const PRIORITY = [
981
+ "track",
982
+ "call",
983
+ "setterArg",
984
+ "api",
985
+ "nav",
986
+ "mcall",
987
+ "setter"
988
+ ];
989
+ /** 後始末(pause・stopSpeaking)が先に来て本体が後に来る書き方が多いので、同じ種類の中では最後のものを採る */
990
+ const LAST = /* @__PURE__ */ new Set([
991
+ "call",
992
+ "setterArg",
993
+ "mcall"
994
+ ]);
995
+ var Ctx = class {
996
+ parent = /* @__PURE__ */ new WeakMap();
997
+ constructor(program) {
998
+ const visit = (n) => {
999
+ for (const c of children(n)) {
1000
+ this.parent.set(c, n);
1001
+ visit(c);
1002
+ }
1003
+ };
1004
+ visit(program);
1005
+ }
1006
+ ancestors(n) {
1007
+ const out = [];
1008
+ for (let p = this.parent.get(n); p; p = this.parent.get(p)) out.push(p);
1009
+ return out;
1010
+ }
1011
+ /** 識別子の束縛(同じファイル内: 変数宣言・関数宣言・分割代入・引数)を内側のスコープから探す */
1012
+ binding(name, from) {
1013
+ for (const a of [from, ...this.ancestors(from)]) {
1014
+ if (/Function/.test(a.type)) for (const p of a.params) {
1015
+ if (p.type === "Identifier" && p.name === name) return {
1016
+ init: null,
1017
+ kind: "param"
1018
+ };
1019
+ if (p.type === "ObjectPattern") {
1020
+ for (const pr of p.properties) if (pr.type === "ObjectProperty" && unwrap(pr.value)?.type === "Identifier" && pr.value.name === name) return {
1021
+ init: null,
1022
+ kind: "param"
1023
+ };
1024
+ }
1025
+ }
1026
+ const body = a.type === "BlockStatement" || a.type === "Program" ? a.body : null;
1027
+ if (!body) continue;
1028
+ for (const st of body) {
1029
+ const decl = st.type === "ExportNamedDeclaration" || st.type === "ExportDefaultDeclaration" ? st.declaration : st;
1030
+ if (decl?.type === "VariableDeclaration") for (const d of decl.declarations) {
1031
+ if (d.id.type === "Identifier" && d.id.name === name) return {
1032
+ init: d.init,
1033
+ kind: "var"
1034
+ };
1035
+ if (d.id.type === "ObjectPattern") {
1036
+ for (const pr of d.id.properties) if (pr.type === "ObjectProperty" && pr.value.type === "Identifier" && pr.value.name === name) return {
1037
+ init: d.init,
1038
+ kind: "destructure"
1039
+ };
1040
+ }
1041
+ if (d.id.type === "ArrayPattern" && d.id.elements.some((el) => el?.type === "Identifier" && el.name === name)) return {
1042
+ init: d.init,
1043
+ kind: "destructure",
1044
+ pattern: d.id
1045
+ };
1046
+ }
1047
+ if (decl?.type === "FunctionDeclaration" && decl.id?.name === name) return {
1048
+ init: decl,
1049
+ kind: "fn"
1050
+ };
1051
+ }
1052
+ }
1053
+ return null;
1054
+ }
1055
+ };
1056
+ function trackName(call) {
1057
+ const { obj, prop } = calleeParts(call.callee);
1058
+ const args = call.arguments;
1059
+ const fromObj = (a) => {
1060
+ const o = unwrap(a);
1061
+ if (o?.type !== "ObjectExpression") return null;
1062
+ const p = o.properties.find((p) => p.type === "ObjectProperty" && [
1063
+ "event",
1064
+ "name",
1065
+ "action"
1066
+ ].includes(keyName(p) ?? ""));
1067
+ return p ? staticString(p.value) : null;
1068
+ };
1069
+ if (obj === "dataLayer" && prop === "push") return fromObj(args[0]);
1070
+ if (!(prop && (TRACK_CALLEES.has(prop) || obj && TRACK_OBJECTS.has(obj) && [
1071
+ "track",
1072
+ "capture",
1073
+ "logEvent"
1074
+ ].includes(prop)))) return null;
1075
+ if (prop === "gtag" || prop === "sendGAEvent" && staticString(args[0]) === "event") return staticString(args[1]);
1076
+ if (prop === "logEvent" && args[0] && staticString(args[0]) === null) return staticString(args[1]);
1077
+ return staticString(args[0]) ?? fromObj(args[0]);
1078
+ }
1079
+ /** 画面遷移: 遷移先のパス(名前にならなければ null)。遷移でなければ undefined */
1080
+ function navPath(call) {
1081
+ const { obj, prop } = calleeParts(call.callee);
1082
+ const a0 = call.arguments[0];
1083
+ if (obj && /router|navigation|nav/i.test(obj) && (prop === "push" || prop === "replace") || prop === "redirect" || prop === "navigate" || prop === "permanentRedirect" || obj === "location" && (prop === "assign" || prop === "replace") || obj === "window" && prop === "open") return normalizePath(pathTemplate(a0));
1084
+ if (obj && /router|history/i.test(obj) && prop === "back" || prop === "back") return "back";
1085
+ if (obj && /router/i.test(obj) && prop === "refresh") return "refresh";
1086
+ }
1087
+ /** API の呼び出し: API のパス(名前にならなければ null)。API でなければ undefined */
1088
+ function apiPath(call) {
1089
+ const { obj, prop } = calleeParts(call.callee);
1090
+ if (prop === "fetch" || prop === "axios" || obj && /^(axios|api|client|http|ky|\$fetch)$/.test(obj) && /^(get|post|put|patch|delete|request)$/.test(prop ?? "")) {
1091
+ const p = normalizePath(pathTemplate(call.arguments[0]));
1092
+ return p?.startsWith("/") ? p : null;
1093
+ }
1094
+ }
1095
+ function analyzeCall(call, ctx, out) {
1096
+ const tracked = trackName(call);
1097
+ if (tracked) {
1098
+ out.push({
1099
+ kind: "track",
1100
+ name: tracked
1101
+ });
1102
+ return;
1103
+ }
1104
+ const nav = navPath(call);
1105
+ if (nav !== void 0) {
1106
+ if (nav) out.push({
1107
+ kind: "nav",
1108
+ name: nav
1109
+ });
1110
+ return;
1111
+ }
1112
+ const api = apiPath(call);
1113
+ if (api !== void 0) {
1114
+ if (api) out.push({
1115
+ kind: "api",
1116
+ name: api
1117
+ });
1118
+ return;
1119
+ }
1120
+ const { obj, prop } = calleeParts(call.callee);
1121
+ if (prop && [
1122
+ "startTransition",
1123
+ "setTimeout",
1124
+ "requestAnimationFrame",
1125
+ "then",
1126
+ "finally",
1127
+ "catch"
1128
+ ].includes(prop)) {
1129
+ const cu = unwrap(call.callee);
1130
+ if (cu.type === "MemberExpression") walkExpr(cu.object, ctx, out);
1131
+ for (const a of call.arguments) if (/Function/.test(a.type)) walkFn(a, ctx, out);
1132
+ return;
1133
+ }
1134
+ if (prop === "dispatch") {
1135
+ const a = unwrap(call.arguments[0]);
1136
+ if (a?.type === "CallExpression") {
1137
+ const c = calleeParts(a.callee).prop;
1138
+ if (c) out.push({
1139
+ kind: "call",
1140
+ name: c
1141
+ });
1142
+ } else if (a?.type === "ObjectExpression") {
1143
+ const p = a.properties.find((p) => keyName(p) === "type");
1144
+ const s = p && staticString(p.value);
1145
+ if (s) out.push({
1146
+ kind: "call",
1147
+ name: s
1148
+ });
1149
+ }
1150
+ return;
1151
+ }
1152
+ if (!prop || NOISE_CALLS.has(prop) || obj && NOISE_MEMBER.has(prop)) {
1153
+ for (const a of call.arguments) walkExpr(a, ctx, out);
1154
+ return;
1155
+ }
1156
+ if (obj && NOISE_OBJECTS.has(obj)) return;
1157
+ if (/^set[A-Z]/.test(prop)) {
1158
+ const arg = literalArg(call.arguments[0]);
1159
+ const promote = arg !== null && arg !== "false" && arg !== "null";
1160
+ out.push(arg && usable(`${prop}:${arg}`) ? {
1161
+ kind: promote ? "setterArg" : "setter",
1162
+ name: `${prop}:${arg}`
1163
+ } : {
1164
+ kind: "setter",
1165
+ name: prop
1166
+ });
1167
+ return;
1168
+ }
1169
+ if (GENERIC_MEMBER_PROP.has(prop) && !obj) {
1170
+ const init = unwrap(ctx.binding(prop, call)?.init);
1171
+ const key = init?.type === "CallExpression" ? normalizePath(pathTemplate(init.arguments[0])) : null;
1172
+ if (key?.startsWith("/")) out.push({
1173
+ kind: "api",
1174
+ name: key
1175
+ });
1176
+ return;
1177
+ }
1178
+ if (GENERIC_MEMBER_PROP.has(prop)) {
1179
+ if (obj && ![
1180
+ "props",
1181
+ "this",
1182
+ "state",
1183
+ "store",
1184
+ "ref",
1185
+ "current"
1186
+ ].includes(obj)) out.push({
1187
+ kind: "call",
1188
+ name: obj
1189
+ });
1190
+ else if (prop.startsWith("on") && !GENERIC_HANDLER.test(prop)) out.push({
1191
+ kind: "call",
1192
+ name: prop
1193
+ });
1194
+ return;
1195
+ }
1196
+ if (obj === "clipboard" || obj === "navigator") {
1197
+ out.push({
1198
+ kind: "call",
1199
+ name: `clipboard.${prop}`
1200
+ });
1201
+ return;
1202
+ }
1203
+ if (obj === "window" && [
1204
+ "print",
1205
+ "scrollTo",
1206
+ "close"
1207
+ ].includes(prop)) {
1208
+ out.push({
1209
+ kind: "call",
1210
+ name: prop
1211
+ });
1212
+ return;
1213
+ }
1214
+ if (GENERIC_HANDLER.test(prop)) return;
1215
+ const arg = literalArg(call.arguments[0]);
1216
+ out.push({
1217
+ kind: obj ? "mcall" : "call",
1218
+ name: arg && usable(`${prop}:${arg}`) ? `${prop}:${arg}` : prop
1219
+ });
1220
+ }
1221
+ function walkExpr(e, ctx, out) {
1222
+ const n = unwrap(e);
1223
+ if (!n) return;
1224
+ switch (n.type) {
1225
+ case "CallExpression":
1226
+ case "OptionalCallExpression":
1227
+ analyzeCall(n, ctx, out);
1228
+ return;
1229
+ case "LogicalExpression":
1230
+ walkExpr(n.left, ctx, out);
1231
+ walkExpr(n.right, ctx, out);
1232
+ return;
1233
+ case "ConditionalExpression":
1234
+ walkExpr(n.consequent, ctx, out);
1235
+ walkExpr(n.alternate, ctx, out);
1236
+ return;
1237
+ case "SequenceExpression":
1238
+ for (const x of n.expressions) walkExpr(x, ctx, out);
1239
+ return;
1240
+ case "UnaryExpression":
1241
+ walkExpr(n.argument, ctx, out);
1242
+ return;
1243
+ case "AssignmentExpression": {
1244
+ const { obj, prop } = calleeParts(unwrap(n.left));
1245
+ if (prop === "href" && /location/.test(obj ?? "")) {
1246
+ const p = normalizePath(pathTemplate(n.right));
1247
+ if (p) out.push({
1248
+ kind: "nav",
1249
+ name: p
1250
+ });
1251
+ } else walkExpr(n.right, ctx, out);
1252
+ return;
1253
+ }
1254
+ }
1255
+ }
1256
+ function walkStmts(stmts, ctx, out) {
1257
+ for (const st of stmts) switch (st.type) {
1258
+ case "ExpressionStatement":
1259
+ walkExpr(st.expression, ctx, out);
1260
+ break;
1261
+ case "ReturnStatement":
1262
+ walkExpr(st.argument, ctx, out);
1263
+ break;
1264
+ case "IfStatement":
1265
+ walkExpr(st.test, ctx, out);
1266
+ walkStmts([st.consequent], ctx, out);
1267
+ if (st.alternate) walkStmts([st.alternate], ctx, out);
1268
+ break;
1269
+ case "BlockStatement":
1270
+ walkStmts(st.body, ctx, out);
1271
+ break;
1272
+ case "TryStatement":
1273
+ walkStmts(st.block.body, ctx, out);
1274
+ break;
1275
+ case "VariableDeclaration": for (const d of st.declarations) walkExpr(d.init, ctx, out);
1276
+ }
1277
+ }
1278
+ function walkFn(fn, ctx, out) {
1279
+ if (fn.body.type === "BlockStatement") walkStmts(fn.body.body, ctx, out);
1280
+ else walkExpr(fn.body, ctx, out);
1281
+ }
1282
+ function pick(cands) {
1283
+ for (const k of PRIORITY) {
1284
+ const xs = cands.filter((x) => x.kind === k && usable(x.name));
1285
+ const found = LAST.has(k) ? xs[xs.length - 1] : xs[0];
1286
+ if (found) return found;
1287
+ }
1288
+ return null;
1289
+ }
1290
+ function fromBody(fn, ctx) {
1291
+ const out = [];
1292
+ walkFn(fn, ctx, out);
1293
+ const c = pick(out);
1294
+ return c ? {
1295
+ name: c.name,
1296
+ source: c.kind
1297
+ } : null;
1298
+ }
1299
+ /** ハンドラの式 → 名前 */
1300
+ function handlerName(expr, ctx, depth = 0) {
1301
+ const n = unwrap(expr);
1302
+ if (!n || depth > 3) return null;
1303
+ if (n.type === "Identifier") {
1304
+ const b = ctx.binding(n.name, n);
1305
+ const init = unwrap(b?.init);
1306
+ if (b?.kind === "destructure" && b.pattern && init?.type === "CallExpression" && /^(useActionState|useFormState)$/.test(calleeParts(init.callee).prop ?? "")) {
1307
+ const a0 = init.arguments[0];
1308
+ return a0?.type === "Identifier" ? {
1309
+ name: a0.name,
1310
+ source: "action"
1311
+ } : null;
1312
+ }
1313
+ if (!GENERIC_HANDLER.test(n.name)) return {
1314
+ name: n.name,
1315
+ source: "handler"
1316
+ };
1317
+ if (init && /Function/.test(init.type)) return fromBody(init, ctx);
1318
+ if (init?.type === "CallExpression" && /^(useCallback|useMemo)$/.test(calleeParts(init.callee).prop ?? "") && /Function/.test(init.arguments[0]?.type)) return fromBody(init.arguments[0], ctx);
1319
+ return null;
1320
+ }
1321
+ if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
1322
+ const { obj, prop } = calleeParts(n);
1323
+ if (!prop) return null;
1324
+ if (GENERIC_MEMBER_PROP.has(prop)) return obj && ![
1325
+ "props",
1326
+ "this",
1327
+ "state"
1328
+ ].includes(obj) ? {
1329
+ name: obj,
1330
+ source: "handler"
1331
+ } : null;
1332
+ if (/^set[A-Z]/.test(prop)) return {
1333
+ name: prop,
1334
+ source: "setter"
1335
+ };
1336
+ return GENERIC_HANDLER.test(prop) ? null : {
1337
+ name: prop,
1338
+ source: "handler"
1339
+ };
1340
+ }
1341
+ if (/Function/.test(n.type)) return fromBody(n, ctx);
1342
+ if (n.type === "CallExpression") {
1343
+ const { obj, prop } = calleeParts(n.callee);
1344
+ if (prop === "bind" && obj) return GENERIC_HANDLER.test(obj) ? null : {
1345
+ name: obj,
1346
+ source: "action"
1347
+ };
1348
+ if (prop && /^(useCallback|useMemo)$/.test(prop)) return handlerName(n.arguments[0], ctx, depth + 1);
1349
+ const arg = staticString(n.arguments[0]);
1350
+ if (prop && arg && usable(`${prop}:${arg}`)) return {
1351
+ name: `${prop}:${arg}`,
1352
+ source: "handler"
1353
+ };
1354
+ if (prop && !GENERIC_HANDLER.test(prop)) return (n.arguments[0] ? handlerName(n.arguments[0], ctx, depth + 1) : null) ?? {
1355
+ name: prop,
1356
+ source: "handler"
1357
+ };
1358
+ return n.arguments[0] ? handlerName(n.arguments[0], ctx, depth + 1) : null;
1359
+ }
1360
+ return null;
1361
+ }
1362
+ function tagName(o) {
1363
+ const nm = o.name;
1364
+ if (nm.type === "JSXIdentifier") return nm.name;
1365
+ if (nm.type === "JSXMemberExpression") {
1366
+ const parts = [];
1367
+ let x = nm;
1368
+ while (x.type === "JSXMemberExpression") {
1369
+ parts.unshift(x.property.name);
1370
+ x = x.object;
1371
+ }
1372
+ parts.unshift(x.name);
1373
+ return parts.join(".");
1374
+ }
1375
+ return "";
1376
+ }
1377
+ function jsxAttr(o, name) {
1378
+ return o.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1379
+ }
1380
+ function attrExpr(o, name) {
1381
+ const a = jsxAttr(o, name);
1382
+ return a ? a.value ?? {
1383
+ type: "BooleanLiteral",
1384
+ value: true
1385
+ } : null;
1386
+ }
1387
+ function attrStatic(o, name) {
1388
+ const e = attrExpr(o, name);
1389
+ return e ? staticString(e) : null;
1390
+ }
1391
+ /** クリックされうる要素: ネイティブの要素、クリックの role、クリックのハンドラ、クリックらしいコンポーネント */
1392
+ function isClickable(o) {
1393
+ const tag = tagName(o);
1394
+ const last = tag.split(".").pop() ?? "";
1395
+ const has = (p) => !!jsxAttr(o, p);
1396
+ const role = attrStatic(o, "role");
1397
+ if (NATIVE_CLICKABLE.has(tag)) return true;
1398
+ if (role && CLICK_ROLES.has(role)) return true;
1399
+ if (HANDLER_PROPS.some((p) => p !== "onChange" && has(p))) return true;
1400
+ if (/^[A-Z]/.test(last)) {
1401
+ if (has("href") || has("onChange") || has("formAction") || has("value") && VALUE_ELEMENTS.test(last)) return true;
1402
+ if (CLICKISH_COMPONENT.test(last)) return true;
1403
+ }
1404
+ return false;
1405
+ }
1406
+ /** 要素を含むコンポーネント(名前が大文字で始まる関数・クラス) */
1407
+ function enclosingComponent(ctx, n) {
1408
+ for (const a of ctx.ancestors(n)) {
1409
+ if (a.type === "FunctionDeclaration" && a.id && /^[A-Z]/.test(a.id.name)) return {
1410
+ name: a.id.name,
1411
+ fn: a
1412
+ };
1413
+ if (/Function/.test(a.type)) {
1414
+ let p = ctx.parent.get(a);
1415
+ while (p && p.type === "CallExpression" && /^(forwardRef|memo)$/.test(calleeParts(p.callee).prop ?? "")) p = ctx.parent.get(p);
1416
+ if (p?.type === "VariableDeclarator" && p.id.type === "Identifier" && /^[A-Z]/.test(p.id.name)) return {
1417
+ name: p.id.name,
1418
+ fn: a
1419
+ };
1420
+ }
1421
+ if (a.type === "ClassDeclaration" && a.id) return {
1422
+ name: a.id.name,
1423
+ fn: a
1424
+ };
1425
+ }
1426
+ return null;
1427
+ }
1428
+ /** 要素がコンポーネントの根か(return の直下 / 式本体。条件式・&& の枝を含む) */
1429
+ function isRootOf(ctx, el, fn) {
1430
+ let p = ctx.parent.get(el);
1431
+ while (p && [
1432
+ "ParenthesizedExpression",
1433
+ "ConditionalExpression",
1434
+ "LogicalExpression"
1435
+ ].includes(p.type)) p = ctx.parent.get(p);
1436
+ if (p === fn) return true;
1437
+ if (p?.type === "ReturnStatement") {
1438
+ for (const a of ctx.ancestors(p)) if (/Function|ClassMethod/.test(a.type)) return a === fn || fn.type === "ClassDeclaration" && ctx.ancestors(a).includes(fn);
1439
+ }
1440
+ return false;
1441
+ }
1442
+ function enclosingJsx(ctx, el, pred) {
1443
+ for (const a of ctx.ancestors(el)) if (a.type === "JSXElement" && pred(a.openingElement)) return a.openingElement;
1444
+ return null;
1445
+ }
1446
+ /** items.map(...) の中の要素なら配列の変数名 */
1447
+ function enclosingMapArray(ctx, el) {
1448
+ for (const a of ctx.ancestors(el)) {
1449
+ if (a.type === "CallExpression" && calleeParts(a.callee).prop === "map") {
1450
+ const o = unwrap(unwrap(a.callee).object);
1451
+ if (o?.type === "Identifier") return o.name;
1452
+ if (o?.type === "MemberExpression" && !o.computed) return o.property.name;
1453
+ return null;
1454
+ }
1455
+ if (a.type === "FunctionDeclaration") return null;
1456
+ }
1457
+ return null;
1458
+ }
1459
+ function nameOf(ctx, el) {
1460
+ const o = el.openingElement;
1461
+ const tag = tagName(o);
1462
+ const last = tag.split(".").pop() ?? "";
1463
+ const ok = (name, source) => usable(name) ? {
1464
+ name,
1465
+ source
1466
+ } : null;
1467
+ const value = VALUE_ELEMENTS.test(last) ? attrStatic(o, "value") : null;
1468
+ const withValue = (r) => value && usable(`${r.name}:${value}`) && !r.name.includes(":") ? {
1469
+ name: `${r.name}:${value}`,
1470
+ source: `${r.source}+value`
1471
+ } : r;
1472
+ for (const p of HANDLER_PROPS) {
1473
+ const e = attrExpr(o, p);
1474
+ if (!e) continue;
1475
+ const r = handlerName(e, ctx);
1476
+ if (r && usable(r.name)) return withValue(r);
1477
+ }
1478
+ const fa = attrExpr(o, "formAction");
1479
+ if (fa) {
1480
+ const r = handlerName(fa, ctx);
1481
+ if (r && usable(r.name)) return {
1482
+ name: r.name,
1483
+ source: "action"
1484
+ };
1485
+ }
1486
+ for (const p of [
1487
+ "id",
1488
+ "data-gtm-click",
1489
+ "data-gtm",
1490
+ "data-gtm-id",
1491
+ "name"
1492
+ ]) {
1493
+ if (p === "name" && tag !== "input" && tag !== "select" && tag !== "button") continue;
1494
+ const r = ok(attrStatic(o, p), p === "name" ? "name-attr" : "id");
1495
+ if (r) return r;
1496
+ }
1497
+ for (const p of ["href", "to"]) {
1498
+ const e = attrExpr(o, p);
1499
+ if (e) {
1500
+ const r = ok(normalizePath(pathTemplate(e)), "path");
1501
+ if (r) return r;
1502
+ }
1503
+ }
1504
+ if (value) {
1505
+ const group = enclosingJsx(ctx, el, (g) => GROUP_HANDLER_PROPS.some((p) => jsxAttr(g, p)));
1506
+ if (group) for (const p of GROUP_HANDLER_PROPS) {
1507
+ const e = attrExpr(group, p);
1508
+ if (!e) continue;
1509
+ const r = handlerName(e, ctx);
1510
+ if (r && usable(r.name)) return withValue(r);
1511
+ }
1512
+ if (usable(value)) return {
1513
+ name: value,
1514
+ source: "value"
1515
+ };
1516
+ }
1517
+ const type = attrStatic(o, "type");
1518
+ if (tag === "button" && (type === null || type === "submit") || tag === "input" && type === "submit" || /Submit/.test(last)) {
1519
+ const form = enclosingJsx(ctx, el, (f) => tagName(f) === "form" || /Form$/.test(tagName(f)));
1520
+ if (form) for (const p of ["action", "onSubmit"]) {
1521
+ const e = attrExpr(form, p);
1522
+ if (!e) continue;
1523
+ const r = handlerName(e, ctx);
1524
+ if (r && usable(r.name)) return {
1525
+ name: r.name,
1526
+ source: "form"
1527
+ };
1528
+ }
1529
+ }
1530
+ const arr = enclosingMapArray(ctx, el);
1531
+ if (arr && !GENERIC_ARRAY.test(arr)) {
1532
+ const r = ok(arr, "map-array");
1533
+ if (r) return r;
1534
+ }
1535
+ if (/^[A-Z]/.test(last) && !tag.includes(".") && !GENERIC_COMPONENT.test(last) && !/^(Select|Tabs|TabsList|Dialog|Menu)/.test(last)) {
1536
+ const r = ok(last, "component");
1537
+ if (r) return r;
1538
+ }
1539
+ const enc = enclosingComponent(ctx, el);
1540
+ if (enc && !GENERIC_COMPONENT.test(enc.name)) {
1541
+ const r = ok(enc.name, isRootOf(ctx, el, enc.fn) ? "own-component" : "context");
1542
+ if (r) return r;
1543
+ }
1544
+ if (arr) {
1545
+ const r = ok(arr, "map-array");
1546
+ if (r) return r;
1547
+ }
1548
+ return {
1549
+ name: null,
1550
+ source: "none"
1551
+ };
1552
+ }
1553
+ /** プログラム中の JSX 要素ごとに名前を決める(既定ではクリックされうる要素だけを返す) */
1554
+ function extractFromProgram(program, options = {}) {
1555
+ const ctx = new Ctx(program);
1556
+ const out = [];
1557
+ const visit = (n) => {
1558
+ if (n.type === "JSXElement") {
1559
+ const o = n.openingElement;
1560
+ const clickable = isClickable(o);
1561
+ if (clickable || options.all) {
1562
+ const { name, source } = nameOf(ctx, n);
1563
+ let receiver = null;
1564
+ const enc = enclosingComponent(ctx, n);
1565
+ if (enc && /^[a-z]/.test(tagName(o)) && isRootOf(ctx, n, enc.fn)) {
1566
+ const p0 = enc.fn.params?.[0];
1567
+ receiver = p0?.type === "ObjectPattern" ? {
1568
+ kind: "pattern",
1569
+ pattern: p0
1570
+ } : p0?.type === "Identifier" ? {
1571
+ kind: "ident",
1572
+ name: p0.name
1573
+ } : { kind: "none" };
1574
+ }
1575
+ out.push({
1576
+ element: n,
1577
+ tag: tagName(o),
1578
+ name,
1579
+ source,
1580
+ label: labelOf(n),
1581
+ clickable,
1582
+ receiver
1583
+ });
1584
+ }
1585
+ }
1586
+ for (const c of children(n)) visit(c);
1587
+ };
1588
+ visit(program);
1589
+ return out;
1590
+ }
1591
+ //#endregion
1592
+ //#region ../build-names/src/element-name/transform.ts
1593
+ /** 書く属性。@auspex-ai/core の ELEMENT_REF_ATTR(読む側)と同じ値(test/element-names.test.ts で一致を確かめる) */
1594
+ const REF_ATTR = "data-auspex-ref";
1595
+ /** ラベル(NAL-124)の参照。@auspex-ai/core の LABEL_REF_ATTR(読む側)と同じ値(test/element-names.test.ts で一致を確かめる) */
1596
+ const LABEL_REF_ATTR = "data-auspex-label-ref";
1597
+ /** 手書きは常に消す。アイコンの属性(#50 段階 2)も同じ理由 */
1598
+ const HAND_WRITTEN = /* @__PURE__ */ new Set([
1599
+ REF_ATTR,
1600
+ LABEL_REF_ATTR,
1601
+ "data-auspex-name",
1602
+ ICON_REF_ATTR,
1603
+ LEGACY_ICON_ATTR
1604
+ ]);
1605
+ /** 定義側で受け取った参照を入れる変数(この名前がソースにあれば変換済みとみなす) */
1606
+ const RECEIVED = "__auspexRef";
1607
+ /** 同じく、ラベルの参照を入れる変数 */
1608
+ const RECEIVED_LABEL = "__auspexLabelRef";
1609
+ const startOf = (n) => n.start ?? 0;
1610
+ const endOf = (n) => n.end ?? 0;
1611
+ /** JSX を含みうるファイルか(解析する前の絞り込み) */
1612
+ function mayHaveElements(source, file) {
1613
+ return !file.endsWith(".ts") && /<[A-Za-z]/.test(source) && !source.includes(RECEIVED);
1614
+ }
1615
+ /** 手書きの属性を消す対象になりうるソースか(mayHaveElements とは別の判定。.ts など JSX を含まないファイルも) */
1616
+ function mayHaveHandWritten(source) {
1617
+ return source.includes("data-auspex-ref") || source.includes("data-auspex-label-ref") || source.includes("data-auspex-name") || source.includes("data-auspex-icon");
1618
+ }
1619
+ function passedValue(receiver, attr, received) {
1620
+ if (receiver.kind === "pattern") return received;
1621
+ if (receiver.kind === "ident") return `${receiver.name}?.[${JSON.stringify(attr)}]`;
1622
+ return null;
1623
+ }
1624
+ const SKIP_KEYS = /* @__PURE__ */ new Set([
1625
+ "loc",
1626
+ "start",
1627
+ "end",
1628
+ "extra",
1629
+ "range",
1630
+ "leadingComments",
1631
+ "trailingComments",
1632
+ "innerComments"
1633
+ ]);
1634
+ /** オブジェクトリテラルのプロパティのキーが、リテラルで書かれた手書きの属性名か */
1635
+ function isHandWrittenKey(p) {
1636
+ if (p.type === "SpreadElement") return false;
1637
+ const k = p.key;
1638
+ if (k.type === "StringLiteral") return HAND_WRITTEN.has(k.value);
1639
+ return !p.computed && k.type === "Identifier" && HAND_WRITTEN.has(k.name);
1640
+ }
1641
+ /** JSX の属性と、オブジェクトリテラルのリテラルのキー(スプレッド・createElement・jsx() の props を含む)から手書きの属性を消す */
1642
+ function removeHandWritten(node, s) {
1643
+ if (node.type === "JSXOpeningElement") {
1644
+ for (const a of node.attributes) if (a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && HAND_WRITTEN.has(a.name.name)) s.remove(startOf(a), endOf(a));
1645
+ }
1646
+ if (node.type === "ObjectExpression") {
1647
+ const props = node.properties;
1648
+ for (let i = 0; i < props.length; i++) {
1649
+ const p = props[i];
1650
+ if (!p || !isHandWrittenKey(p)) continue;
1651
+ let j = i;
1652
+ while (j + 1 < props.length && props[j + 1] && isHandWrittenKey(props[j + 1])) j++;
1653
+ const first = props[i];
1654
+ const last = props[j];
1655
+ const next = props[j + 1];
1656
+ const prev = props[i - 1];
1657
+ if (next) s.remove(startOf(first), startOf(next));
1658
+ else if (prev) s.remove(endOf(prev), endOf(last));
1659
+ else s.remove(startOf(first), endOf(last));
1660
+ i = j;
1661
+ }
1662
+ }
1663
+ for (const key of Object.keys(node)) {
1664
+ if (SKIP_KEYS.has(key)) continue;
1665
+ const v = node[key];
1666
+ for (const c of Array.isArray(v) ? v : [v]) if (c && typeof c.type === "string") removeHandWritten(c, s);
1667
+ }
1668
+ }
1669
+ /** 手書きの属性の削除だけを `s` に積む(strip のみのモード。JSX を含まないファイルも) */
1670
+ function stripHandWritten(program, s) {
1671
+ removeHandWritten(program, s);
1672
+ }
1673
+ /**
1674
+ * 要素の参照の書き込みを `s` に積む(手書きの属性の削除は stripHandWritten で別に行う)。書いた名前とラベルを返す。
1675
+ * `refOf` は名前(kind = 'element')・ラベル(kind = 'label')から参照を計算する(鍵はここに渡らない)。
1676
+ * ラベルの参照(data-auspex-label-ref)も要素の参照と同じ仕組みで書く
1677
+ */
1678
+ function applyElementNames(program, s, refOf) {
1679
+ const written = /* @__PURE__ */ new Set();
1680
+ const writtenLabels = /* @__PURE__ */ new Set();
1681
+ const patched = /* @__PURE__ */ new Set();
1682
+ for (const { element, tag, name, label, receiver } of extractFromProgram(program)) {
1683
+ const dom = /^[a-z]/.test(tag);
1684
+ const attrs = [];
1685
+ const write = (attr, received, own) => {
1686
+ const passed = receiver ? passedValue(receiver, attr, received) : null;
1687
+ const fallback = own ?? (dom ? "\"\"" : null);
1688
+ const value = passed && fallback ? `${passed} ?? ${fallback}` : passed ?? fallback;
1689
+ return value === null ? null : { value };
1690
+ };
1691
+ const refValue = write(REF_ATTR, RECEIVED, name === null ? null : JSON.stringify(refOf(name, "element")));
1692
+ const labelValue = write(LABEL_REF_ATTR, RECEIVED_LABEL, label === null ? null : JSON.stringify(refOf(label, "label")));
1693
+ if (refValue === null && labelValue === null) continue;
1694
+ if (refValue && name !== null) written.add(name);
1695
+ if (labelValue && label !== null) writtenLabels.add(label);
1696
+ if (receiver?.kind === "pattern" && !patched.has(receiver.pattern)) {
1697
+ s.appendRight(startOf(receiver.pattern) + 1, ` ${JSON.stringify(REF_ATTR)}: ${RECEIVED}, ${JSON.stringify(LABEL_REF_ATTR)}: ${RECEIVED_LABEL},`);
1698
+ patched.add(receiver.pattern);
1699
+ }
1700
+ if (refValue) attrs.push(` ${REF_ATTR}={${refValue.value}}`);
1701
+ if (labelValue) attrs.push(` ${LABEL_REF_ATTR}={${labelValue.value}}`);
1702
+ s.appendLeft(endOf(element.openingElement.name), attrs.join(""));
1703
+ }
1704
+ return {
1705
+ names: [...written],
1706
+ labels: [...writtenLabels]
1707
+ };
1708
+ }
1709
+ //#endregion
1710
+ //#region ../build-names/src/transform.ts
1711
+ /** 変換が不要なら null */
1712
+ function transformNames(source, file, options = {}) {
1713
+ const refOf = options.elementNames === "strip" ? void 0 : options.refOf;
1714
+ const icons = refOf !== void 0 && options.iconNames !== false && mayHaveIcons(source);
1715
+ const elements = refOf !== void 0 && options.elementAttrs !== false && mayHaveElements(source, file);
1716
+ const strip = mayHaveHandWritten(source);
1717
+ if (!icons && !elements && !strip) return null;
1718
+ const program = parseSource(source, file);
1719
+ if (!program) return null;
1720
+ const s = new MagicString(source);
1721
+ if (strip) stripHandWritten(program, s);
1722
+ const { names, labels } = elements && refOf ? applyElementNames(program, s, (name, kind) => refOf(name, kind)) : {
1723
+ names: [],
1724
+ labels: []
1725
+ };
1726
+ const iconNames = icons && refOf ? applyIconNames(program, source, file, s, (name) => refOf(name, "icon")) : [];
1727
+ if (!s.hasChanged()) return null;
1728
+ return {
1729
+ code: s.toString(),
1730
+ map: s.generateMap({
1731
+ hires: true,
1732
+ source: file,
1733
+ includeContent: true
1734
+ }),
1735
+ names,
1736
+ icons: iconNames,
1737
+ labels
1738
+ };
1739
+ }
1740
+ //#endregion
1741
+ //#region src/vite.ts
1742
+ const BUILD_ID_ENV = "VITE_AUSPEX_BUILD_ID";
1743
+ const BUILD_ID_DEFINE = `import.meta.env.${BUILD_ID_ENV}`;
1744
+ const SOURCE = /\.(?:[jt]sx?|m[jt]s)$/;
1745
+ const NODE_MODULES = /[\\/]node_modules[\\/]/;
1746
+ const silent = {
1747
+ info: () => {},
1748
+ warn: () => {}
1749
+ };
1750
+ const nonEmpty = (v) => v ? v : void 0;
1751
+ function auspex(options = {}) {
1752
+ const names = /* @__PURE__ */ new Set();
1753
+ const icons = /* @__PURE__ */ new Set();
1754
+ const labels = /* @__PURE__ */ new Set();
1755
+ let env = {};
1756
+ let buildId;
1757
+ let isBuild = false;
1758
+ let ssr = false;
1759
+ let failed = false;
1760
+ let registered = false;
1761
+ let refs = null;
1762
+ let host;
1763
+ let buildKey;
1764
+ const hostOf = () => registrationHost(options.host ?? nonEmpty(process.env.VITE_AUSPEX_HOST) ?? nonEmpty(env.VITE_AUSPEX_HOST));
1765
+ return {
1766
+ name: "auspex",
1767
+ enforce: "pre",
1768
+ async config(userConfig, { mode }) {
1769
+ if (buildId === void 0) {
1770
+ const root = path.resolve(userConfig.root ?? process.cwd());
1771
+ const envDir = userConfig.envDir;
1772
+ if (envDir !== false) {
1773
+ const { loadEnv } = await import("vite");
1774
+ env = loadEnv(mode, envDir === void 0 ? root : path.resolve(root, envDir), "VITE_");
1775
+ }
1776
+ const given = nonEmpty(process.env["VITE_AUSPEX_BUILD_ID"]) ?? nonEmpty(env["VITE_AUSPEX_BUILD_ID"]);
1777
+ buildId = await resolveBuildId("", given, given ? consoleLogger : silent, false);
1778
+ }
1779
+ return { define: { [BUILD_ID_DEFINE]: JSON.stringify(buildId) } };
1780
+ },
1781
+ configResolved(config) {
1782
+ isBuild = config.command === "build";
1783
+ ssr = Boolean(config.build.ssr);
1784
+ const key = nonEmpty(process.env.AUSPEX_BUILD_KEY);
1785
+ buildKey = key;
1786
+ host = hostOf();
1787
+ refs = null;
1788
+ if (!isBuild) return;
1789
+ if (options.elementNames === false && options.iconNames === false) return;
1790
+ if (!key) warnOnce("vite:no-key", "[auspex] 要素名が付かない: ビルド鍵(環境変数 AUSPEX_BUILD_KEY)が無い。アプリの設定のページで発行して CI の秘密に置く(VITE_ は付けない)");
1791
+ else if (!host || !isValidHost(host)) warnOnce("vite:no-host", "[auspex] 要素名が付かない: 登録の受け口 host が無い、または https ではない。auspex({ host }) か環境変数 VITE_AUSPEX_HOST に受信口の https の URL を指定する(セルフホストで /api/names を internal の面に置くなら AUSPEX_INTERNAL_HOST)");
1792
+ else refs = (name, kind) => nameRef(key, name, kind);
1793
+ if (!process.env["VITE_AUSPEX_BUILD_ID"] && !env["VITE_AUSPEX_BUILD_ID"]) warnOnce("vite:local-build-id", `[auspex] ${BUILD_ID_ENV} が未設定なので、自前のビルド ID(${buildId})を使う`);
1794
+ },
1795
+ buildStart() {
1796
+ names.clear();
1797
+ icons.clear();
1798
+ labels.clear();
1799
+ failed = false;
1800
+ registered = false;
1801
+ },
1802
+ buildEnd(error) {
1803
+ if (error) failed = true;
1804
+ },
1805
+ transform(code, id) {
1806
+ if (id.startsWith("\0")) return null;
1807
+ const file = id.split("?")[0] ?? id;
1808
+ if (NODE_MODULES.test(file) || !SOURCE.test(file)) return null;
1809
+ const nameOptions = { elementNames: "strip" };
1810
+ if (options.iconNames !== void 0) nameOptions.iconNames = options.iconNames;
1811
+ if (options.elementNames === false) nameOptions.elementAttrs = false;
1812
+ if (refs) {
1813
+ nameOptions.elementNames = "refs";
1814
+ nameOptions.refOf = refs;
1815
+ }
1816
+ const out = transformNames(code, file, nameOptions);
1817
+ if (!out) return null;
1818
+ for (const n of out.names) names.add(n);
1819
+ for (const n of out.icons) icons.add(n);
1820
+ for (const n of out.labels) labels.add(n);
1821
+ return {
1822
+ code: out.code,
1823
+ map: out.map
1824
+ };
1825
+ },
1826
+ async closeBundle() {
1827
+ if (!isBuild || ssr || failed || registered || !refs || !buildKey || !host || buildId === void 0) return;
1828
+ registered = true;
1829
+ try {
1830
+ await registerNames({
1831
+ key: buildKey,
1832
+ host,
1833
+ names: [...names],
1834
+ icons: [...icons],
1835
+ labels: [...labels],
1836
+ buildId
1837
+ });
1838
+ } catch {}
1839
+ }
1840
+ };
1841
+ }
1842
+ //#endregion
1843
+ export { BUILD_ID_ENV, auspex };