@auspex-ai/next 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.
@@ -0,0 +1,700 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
3
+ let node_module = require("node:module");
4
+ let node_path = require("node:path");
5
+ node_path = require_rolldown_runtime.__toESM(node_path, 1);
6
+ let node_url = require("node:url");
7
+ let node_crypto = require("node:crypto");
8
+ let node_fs = require("node:fs");
9
+ //#region ../build-names/src/element-ref.ts
10
+ /** sha256(ビルド鍵)の先頭 16 文字(16 進)。秘密ではなく(先頭 64 bit から鍵は戻せない)、キャッシュやオプションに残ってよい */
11
+ function keyIdOf(key) {
12
+ return (0, node_crypto.createHash)("sha256").update(key, "utf8").digest("hex").slice(0, 16);
13
+ }
14
+ //#endregion
15
+ //#region ../build-names/src/host.ts
16
+ function isValidHost(host) {
17
+ let u;
18
+ try {
19
+ u = new URL(host);
20
+ } catch {
21
+ return false;
22
+ }
23
+ if (u.username || u.password) return false;
24
+ if (u.protocol === "https:") return true;
25
+ return u.protocol === "http:" && (u.hostname === "localhost" || u.hostname === "127.0.0.1");
26
+ }
27
+ /** セルフホスト版で /api/names を受ける internal の面の URL。あれば通常の host より優先する(ビルド時だけ読む) */
28
+ const INTERNAL_HOST_ENV = "AUSPEX_INTERNAL_HOST";
29
+ /**
30
+ * 名前の登録の送り先。環境変数 AUSPEX_INTERNAL_HOST が空でなければそれ(不正な値でも通常の host に
31
+ * 落とさない。落とすと 404 になる面へビルド鍵を送るため、呼び出し側の isValidHost で弾く)。無ければ通常の host
32
+ */
33
+ function registrationHost(host, internal = process.env[INTERNAL_HOST_ENV]) {
34
+ return internal?.trim() ? internal.trim() : host;
35
+ }
36
+ //#endregion
37
+ //#region ../build-names/src/iap-token.ts
38
+ const IAP_SERVICE_ACCOUNT_ENV = "AUSPEX_IAP_SERVICE_ACCOUNT";
39
+ const TOKEN_LIFETIME_SECONDS = 3600;
40
+ /** IAP が検証する aud。host の origin 全体(`<origin>/*`) */
41
+ function iapAudience(host) {
42
+ return `${new URL(host).origin}/*`;
43
+ }
44
+ function iapPayload(serviceAccount, host, nowMs = Date.now()) {
45
+ const iat = Math.floor(nowMs / 1e3);
46
+ return JSON.stringify({
47
+ iss: serviceAccount,
48
+ sub: serviceAccount,
49
+ aud: iapAudience(host),
50
+ iat,
51
+ exp: iat + TOKEN_LIFETIME_SECONDS
52
+ });
53
+ }
54
+ /** 既定の署名: google-auth-library(任意の依存)で ADC を読み、IAM Credentials の signJwt を呼ぶ */
55
+ const signJwtWithAdc = async (serviceAccount, payload) => {
56
+ let lib;
57
+ try {
58
+ lib = await import("google-auth-library");
59
+ } catch {
60
+ throw new Error(`${IAP_SERVICE_ACCOUNT_ENV} が設定されているが google-auth-library が入っていない。ビルドの環境に google-auth-library を入れる(例: pnpm add -D google-auth-library)`);
61
+ }
62
+ const res = await new lib.GoogleAuth({ scopes: ["https://www.googleapis.com/auth/cloud-platform"] }).request({
63
+ url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(serviceAccount)}:signJwt`,
64
+ method: "POST",
65
+ data: { payload },
66
+ timeout: 2e4
67
+ });
68
+ if (!res.data.signedJwt) throw new Error("signJwt の応答に signedJwt が無い");
69
+ return res.data.signedJwt;
70
+ };
71
+ /** `Proxy-Authorization` の値(`Bearer <JWT>`)。失敗は投げる(呼び出し側が警告にする) */
72
+ async function iapProxyAuthorization(host, serviceAccount, sign = signJwtWithAdc) {
73
+ return `Bearer ${await sign(serviceAccount, iapPayload(serviceAccount, host))}`;
74
+ }
75
+ //#endregion
76
+ //#region ../build-names/src/log.ts
77
+ const WARNED = Symbol.for("auspex.warned");
78
+ const PREFIX = "[auspex]";
79
+ const consoleLogger = {
80
+ info: (m) => console.log(`${PREFIX} ${m}`),
81
+ warn: (m) => console.warn(`${PREFIX} ${m}`)
82
+ };
83
+ /** `id` の警告を、このプロセスでまだ出していなければ出す(出したら true) */
84
+ function warnOnce(id, message, logger = consoleLogger) {
85
+ const g = globalThis;
86
+ const seen = g[WARNED] ?? /* @__PURE__ */ new Set();
87
+ g[WARNED] = seen;
88
+ if (seen.has(id)) return false;
89
+ seen.add(id);
90
+ logger.warn(message);
91
+ return true;
92
+ }
93
+ const MAX_BUILD_ID = 128;
94
+ const MAX_RETRIES = 3;
95
+ /** ディレクトリのプロジェクトからの相対パス(ローダーが this.rootContext に対して解決する)。POSIX の区切りで書く */
96
+ const fragmentsSubdir = (keyId) => `cache/auspex/names/${keyId}`;
97
+ function fragmentsDirFor(distDir, keyId) {
98
+ return `${distDir.replace(/\\/g, "/").replace(/\/+$/, "")}/${fragmentsSubdir(keyId)}`;
99
+ }
100
+ const KIND_ORDER = {
101
+ element: 0,
102
+ icon: 1,
103
+ label: 2
104
+ };
105
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
106
+ /**
107
+ * 断片を読んで要素名・アイコン名の集合(それぞれ辞書順)にする。壊れた断片は警告して飛ばす。
108
+ * `icons` の無い段階 1 の断片も読める
109
+ */
110
+ async function readFragments(dir, logger) {
111
+ let files;
112
+ try {
113
+ files = (await node_fs.promises.readdir(dir)).filter((f) => f.endsWith(".json"));
114
+ } catch {
115
+ return {
116
+ names: [],
117
+ icons: [],
118
+ labels: []
119
+ };
120
+ }
121
+ const names = /* @__PURE__ */ new Set();
122
+ const icons = /* @__PURE__ */ new Set();
123
+ const labels = /* @__PURE__ */ new Set();
124
+ for (const f of files) try {
125
+ const parsed = JSON.parse(await node_fs.promises.readFile(node_path.default.join(dir, f), "utf8"));
126
+ if (!Array.isArray(parsed.names)) throw new Error("names が配列ではない");
127
+ if (parsed.icons !== void 0 && !Array.isArray(parsed.icons)) throw new Error("icons が配列ではない");
128
+ if (parsed.labels !== void 0 && !Array.isArray(parsed.labels)) throw new Error("labels が配列ではない");
129
+ for (const n of parsed.names) if (typeof n === "string") names.add(n);
130
+ for (const n of parsed.icons ?? []) if (typeof n === "string") icons.add(n);
131
+ for (const n of parsed.labels ?? []) if (typeof n === "string") labels.add(n);
132
+ } catch (e) {
133
+ logger.warn(`壊れた断片を飛ばした: ${node_path.default.join(dir, f)}(${e.message})`);
134
+ }
135
+ return {
136
+ names: [...names].sort(),
137
+ icons: [...icons].sort(),
138
+ labels: [...labels].sort()
139
+ };
140
+ }
141
+ /** build_id: `<distDir>/BUILD_ID`(か --build-id)。無い・128 文字超は自前の id(1 回の実行で 1 度だけ作る) */
142
+ async function resolveBuildId(distDirAbs, override, logger, readFile = true) {
143
+ let raw = override;
144
+ if (raw === void 0 && readFile) try {
145
+ raw = (await node_fs.promises.readFile(node_path.default.join(distDirAbs, "BUILD_ID"), "utf8")).trim();
146
+ } catch {}
147
+ if (raw && raw.length <= MAX_BUILD_ID) return raw;
148
+ const own = `local-${Date.now()}-${(0, node_crypto.randomBytes)(4).toString("hex")}`;
149
+ logger.info(raw ? `build_id が長すぎる(${raw.length} 文字。上限 ${MAX_BUILD_ID})ので、自前の id を使う: ${own}` : `build_id(${node_path.default.join(distDirAbs, "BUILD_ID")})が無いので、自前の id を使う: ${own}`);
150
+ return own;
151
+ }
152
+ async function post(o, buildId, names, icons, labels) {
153
+ const url = `${o.host.replace(/\/+$/, "")}/api/names`;
154
+ let reason = "";
155
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
156
+ if (attempt > 0) await o.sleep(500 * 2 ** (attempt - 1));
157
+ try {
158
+ const res = await o.fetch(url, {
159
+ method: "POST",
160
+ headers: {
161
+ "content-type": "application/json",
162
+ authorization: `Bearer ${o.key}`,
163
+ ...o.proxyAuthorization ? { "proxy-authorization": o.proxyAuthorization } : {}
164
+ },
165
+ body: JSON.stringify({
166
+ build_id: buildId,
167
+ names,
168
+ ...icons.length > 0 ? { icons } : {},
169
+ ...labels.length > 0 ? { labels } : {}
170
+ }),
171
+ signal: AbortSignal.timeout(2e4),
172
+ redirect: "error"
173
+ });
174
+ if (res.status === 401) return { kind: "unauthorized" };
175
+ if (res.status >= 500) {
176
+ reason = `HTTP ${res.status}`;
177
+ continue;
178
+ }
179
+ if (res.status !== 200) return {
180
+ kind: "rejected",
181
+ status: res.status
182
+ };
183
+ const body = await res.json();
184
+ if (!Array.isArray(body.results)) return {
185
+ kind: "rejected",
186
+ status: res.status
187
+ };
188
+ return {
189
+ kind: "ok",
190
+ results: body.results
191
+ };
192
+ } catch (e) {
193
+ reason = e.message;
194
+ }
195
+ }
196
+ return {
197
+ kind: "failed",
198
+ reason
199
+ };
200
+ }
201
+ /**
202
+ * 断片の名前を登録する。例外は投げない(ネットワーク・5xx・401・400・413 のどれでもビルドを落とさず警告する)
203
+ */
204
+ async function registerNames(options) {
205
+ const logger = options.logger ?? consoleLogger;
206
+ const summary = {
207
+ buildId: null,
208
+ names: 0,
209
+ icons: 0,
210
+ labels: 0,
211
+ registered: 0,
212
+ already: 0,
213
+ dropped: [],
214
+ collisions: [],
215
+ unregistered: []
216
+ };
217
+ try {
218
+ let names;
219
+ let icons;
220
+ let labels;
221
+ let buildId;
222
+ if ("names" in options) {
223
+ names = [...new Set(options.names)].sort();
224
+ icons = [...new Set(options.icons)].sort();
225
+ summary.names = names.length;
226
+ labels = [...new Set(options.labels ?? [])].sort();
227
+ summary.icons = icons.length;
228
+ summary.labels = labels.length;
229
+ if (names.length + icons.length + labels.length === 0) logger.info("登録する名前が無い");
230
+ buildId = await resolveBuildId("", options.buildId, logger, false);
231
+ } else {
232
+ const distDirAbs = node_path.default.resolve(options.projectDir ?? process.cwd(), options.distDir);
233
+ const dir = node_path.default.join(distDirAbs, fragmentsSubdir(options.keyId));
234
+ ({names, icons, labels} = await readFragments(dir, logger));
235
+ summary.names = names.length;
236
+ summary.icons = icons.length;
237
+ summary.labels = labels.length;
238
+ if (names.length + icons.length + labels.length === 0) logger.info(`登録する名前が無い(断片: ${dir})`);
239
+ buildId = await resolveBuildId(distDirAbs, options.buildId, logger, options.readBuildIdFile ?? true);
240
+ }
241
+ summary.buildId = buildId;
242
+ const iapServiceAccount = options.iapServiceAccount ?? process.env["AUSPEX_IAP_SERVICE_ACCOUNT"];
243
+ const proxyAuthorization = iapServiceAccount ? await iapProxyAuthorization(options.host, iapServiceAccount, options.iapSigner).catch((e) => {
244
+ throw new Error(`IAP 用のトークンを作れなかった: ${e.message}`);
245
+ }) : void 0;
246
+ const o = {
247
+ key: options.key,
248
+ host: options.host,
249
+ proxyAuthorization,
250
+ fetch: options.fetch ?? fetch,
251
+ sleep: options.sleep ?? defaultSleep
252
+ };
253
+ const items = (ns, is, ls) => [
254
+ ...ns.map((name) => ({
255
+ kind: "element",
256
+ name
257
+ })),
258
+ ...is.map((name) => ({
259
+ kind: "icon",
260
+ name
261
+ })),
262
+ ...ls.map((name) => ({
263
+ kind: "label",
264
+ name
265
+ }))
266
+ ];
267
+ const chunks = (list) => {
268
+ const out = [];
269
+ for (let i = 0; i < list.length; i += 100) out.push(list.slice(i, i + 100));
270
+ return out;
271
+ };
272
+ const retry = [];
273
+ const unregistered = /* @__PURE__ */ new Map();
274
+ const markUnregistered = (item, reason) => {
275
+ unregistered.set(`${item.kind}\0${item.name}`, {
276
+ item,
277
+ reason
278
+ });
279
+ };
280
+ const label = (kind) => kind === "icon" ? "アイコン名" : kind === "label" ? "ラベル" : "名前";
281
+ const send = async (list, second) => {
282
+ const all = list.length === 0 ? [[]] : chunks(list);
283
+ for (const [ci, chunk] of all.entries()) {
284
+ 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));
285
+ if (out.kind === "unauthorized") {
286
+ logger.warn("ビルド鍵が受け付けられなかった(401)。鍵が違うか、再発行された可能性がある。登録せずに続ける(名前なしのクリックとして入る)");
287
+ for (const n of all.slice(ci).flat()) markUnregistered(n, "401");
288
+ return "stop";
289
+ }
290
+ if (out.kind === "rejected") {
291
+ logger.warn(`登録が拒否された(HTTP ${out.status}、${chunk.length} 件のチャンク)。再送しない`);
292
+ for (const n of chunk) markUnregistered(n, `HTTP ${out.status}`);
293
+ continue;
294
+ }
295
+ if (out.kind === "failed") {
296
+ logger.warn(`登録を送れなかった(${MAX_RETRIES} 回送り直した。${out.reason})。ビルドは続ける。次のビルドで再登録される`);
297
+ for (const n of chunk) markUnregistered(n, out.reason);
298
+ continue;
299
+ }
300
+ for (const r of out.results) {
301
+ const kind = r.kind === "icon" ? "icon" : r.kind === "label" ? "label" : "element";
302
+ if (r.status === "registered") summary.registered++;
303
+ else if (r.status === "already") summary.already++;
304
+ else if (r.status === "collision") {
305
+ summary.collisions.push({
306
+ kind,
307
+ name: r.name,
308
+ winner: r.winner
309
+ });
310
+ logger.warn(`${label(kind)} "${r.name}" は登録済みの "${r.winner}" と同じ参照になった。"${r.name}" の${kind === "icon" ? "アイコン" : kind === "label" ? "ラベル" : "要素"}は "${r.winner}" の名前で記録される`);
311
+ } else if (r.status === "retry") {
312
+ if (second) markUnregistered({
313
+ kind,
314
+ name: r.name
315
+ }, "retry");
316
+ else retry.push({
317
+ kind,
318
+ name: r.name
319
+ });
320
+ } else {
321
+ summary.dropped.push({
322
+ kind,
323
+ name: r.name,
324
+ status: r.status
325
+ });
326
+ logger.warn(`${label(kind)} "${r.name}" は登録されなかった(${r.status})`);
327
+ }
328
+ }
329
+ }
330
+ return "go";
331
+ };
332
+ 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);
333
+ for (const { item, reason } of unregistered.values()) {
334
+ summary.unregistered.push(item);
335
+ if (reason !== "401") logger.warn(`${label(item.kind)} "${item.name}" は未登録(${reason})。次のビルドで再登録される`);
336
+ }
337
+ logger.info(`名前の登録: 要素名 ${summary.names} 件・アイコン名 ${summary.icons} 件・ラベル ${summary.labels} 件中 新規 ${summary.registered}・登録済み ${summary.already}・捨てた ${summary.dropped.length}・衝突 ${summary.collisions.length}・未登録 ${summary.unregistered.length}(build_id ${buildId})`);
338
+ } catch (e) {
339
+ logger.warn(`名前の登録に失敗した(ビルドは続ける): ${e.message}`);
340
+ }
341
+ return summary;
342
+ }
343
+ //#endregion
344
+ //#region src/config.ts
345
+ /** 二重掛け(withAuspex(withAuspex(cfg)))を 1 回分にする印。値は今のオプション */
346
+ const MARK = Symbol.for("auspex.withAuspex");
347
+ /** hook に付ける、オプションの入れ物(二重掛けの外側がオプションを合流させる先) */
348
+ const HOOK_BOX = Symbol.for("auspex.withAuspex.hook");
349
+ /** 登録を 1 プロセスで 1 回にする(印を落とすラッパーで hook が二重になっても 1 回) */
350
+ const REGISTERED = Symbol.for("auspex.registered");
351
+ const here = node_path.default.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
352
+ /** ビルド済みのローダー(dist/ に config と並べて置く) */
353
+ const ICON_NAME_LOADER = node_path.default.join(here, "icon-name-loader.cjs");
354
+ const SVG_NAME_LOADER = node_path.default.join(here, "svg-name-loader.cjs");
355
+ const JS_GLOBS = [
356
+ "*.tsx",
357
+ "*.jsx",
358
+ "*.ts",
359
+ "*.js"
360
+ ];
361
+ /** 顧客のプロジェクトの next のバージョン(取れなければ null) */
362
+ function nextVersion() {
363
+ for (const base of [node_path.default.join(process.cwd(), "package.json"), require("url").pathToFileURL(__filename).href]) try {
364
+ const { version } = (0, node_module.createRequire)(base)("next/package.json");
365
+ if (typeof version === "string") return version;
366
+ } catch {}
367
+ return null;
368
+ }
369
+ /** 顧客のプロジェクトの next のメジャーバージョン(取れなければ 0) */
370
+ function nextMajor() {
371
+ return Number(nextVersion()?.split(".")[0]) || 0;
372
+ }
373
+ /** runAfterProductionCompile が使えるか(Design Doc #50 の 3 節の表。線は暫定)。数値で比べる */
374
+ function hookRow(version, turbopack) {
375
+ const m = /^(\d+)\.(\d+)\.(\d+)(-.+)?/.exec(version ?? "");
376
+ if (!m) return "unknown";
377
+ const [major, minor, patch] = [
378
+ Number(m[1]),
379
+ Number(m[2]),
380
+ Number(m[3])
381
+ ];
382
+ if (major >= 16) return "usable";
383
+ if (m[4]) return "unusable";
384
+ if (!(major === 15 && (minor > 4 || minor === 4 && patch >= 1))) return "unusable";
385
+ return turbopack ? "turbopack-15" : "usable";
386
+ }
387
+ /**
388
+ * 新しく作る JS/TS のルール。Next 16 以降は `condition: { not: 'foreign' }` で node_modules を
389
+ * ローダーに通さない(ファイルごとに Node のローダーを呼ぶコストを避ける)。それより前の Next の
390
+ * Turbopack には condition が無いので付けず、ローダーの中で node_modules を素通しする
391
+ */
392
+ function iconNameRule(major, loader = ICON_NAME_LOADER) {
393
+ return major >= 16 ? {
394
+ condition: { not: "foreign" },
395
+ loaders: [loader]
396
+ } : { loaders: [loader] };
397
+ }
398
+ const key = () => process.env.AUSPEX_BUILD_KEY || void 0;
399
+ /** 本番のビルドで参照を書けるか(1 節の (i)〜(v)、3 節の表)。開発は呼び出し側が先に 'strip' にする */
400
+ function planFor(options, turbopack) {
401
+ const k = key();
402
+ const keyId = k ? keyIdOf(k) : null;
403
+ const strip = (reason) => ({
404
+ refs: false,
405
+ reason,
406
+ hook: false,
407
+ keyId
408
+ });
409
+ if (options.elementNames === false && options.iconNames === false) return strip(null);
410
+ if (!k) return strip("no-key");
411
+ if (options.registerNames === "cli") return {
412
+ refs: true,
413
+ reason: null,
414
+ hook: false,
415
+ keyId
416
+ };
417
+ const host = registrationHost(options.host ?? process.env.NEXT_PUBLIC_AUSPEX_HOST);
418
+ if (!host || !isValidHost(host)) return strip("no-host");
419
+ const row = hookRow(nextVersion(), turbopack);
420
+ if (row === "usable") return {
421
+ refs: true,
422
+ reason: null,
423
+ hook: true,
424
+ keyId
425
+ };
426
+ return strip(row === "unknown" ? "version-unknown" : row === "turbopack-15" ? "turbopack-15" : "hook-unusable");
427
+ }
428
+ const CLI_HINT = "有効にするには withAuspex(cfg, { registerNames: 'cli' }) を渡し、next build の後に auspex-register-names を流す";
429
+ const MESSAGES = {
430
+ "no-key": "要素名が付かない: ビルド鍵(環境変数 AUSPEX_BUILD_KEY)が無い。アプリの設定のページで発行して CI の秘密に置く(NEXT_PUBLIC_ は付けない)",
431
+ "no-host": "要素名が付かない: 登録の受け口 host が無い、または https ではない。withAuspex(cfg, { host }) か環境変数 NEXT_PUBLIC_AUSPEX_HOST に受信口の https の URL を指定する(セルフホストで /api/names を internal の面に置くなら AUSPEX_INTERNAL_HOST)",
432
+ "hook-unusable": `要素名が付かない: この版の Next は runAfterProductionCompile が使えない(15.4.1 以上の安定版が要る)。${CLI_HINT}`,
433
+ "turbopack-15": `要素名が付かない: 15.x の Turbopack が runAfterProductionCompile を呼ぶか確認できていない。${CLI_HINT}(webpack なら hook で登録される)`,
434
+ "version-unknown": `要素名が付かない: Next の版を判定できなかった。${CLI_HINT}`
435
+ };
436
+ const UNKNOWN_PHASE = "ビルドか開発かを判定できないので、要素名の参照を書かない(手書きの data-auspex-name・data-auspex-ref を消すだけ)。next dev でも出うる";
437
+ function warnReason(reason) {
438
+ if (reason) warnOnce(`reason:${reason}`, `[auspex] ${MESSAGES[reason]}`);
439
+ }
440
+ /** `next build` / `next dev` のどちらで設定を読んでいるか(webpack は ctx.dev が正。ここは警告と Turbopack の判定用) */
441
+ function buildKind() {
442
+ const phase = process.env.NEXT_PHASE;
443
+ if (phase === "phase-production-build") return "build";
444
+ if (phase === "phase-development-server") return "dev";
445
+ const cmd = process.argv.slice(2).find((a) => !a.startsWith("-"));
446
+ if (cmd === "build") return "build";
447
+ if (cmd === "dev") return "dev";
448
+ return "unknown";
449
+ }
450
+ /** ローダー。オプションは常に渡す(両方 on でも)。秘密は入れない(keyId は入れてよい) */
451
+ function nameLoader(options, mode, plan, distDir, loader = ICON_NAME_LOADER) {
452
+ const loaderOptions = {
453
+ iconNames: options.iconNames !== false,
454
+ elementNames: mode
455
+ };
456
+ if (options.elementNames === false) loaderOptions.elementAttrs = false;
457
+ if (mode === "refs" && plan.keyId) {
458
+ loaderOptions.keyId = plan.keyId;
459
+ loaderOptions.fragmentsDir = fragmentsDirFor(distDir, plan.keyId);
460
+ }
461
+ return {
462
+ loader,
463
+ options: loaderOptions
464
+ };
465
+ }
466
+ const loaderName = (l) => typeof l === "string" ? l : l.loader;
467
+ const isOurs = (l) => loaderName(l) === ICON_NAME_LOADER;
468
+ const isSvgr = (l) => /@svgr[\\/]webpack/.test(loaderName(l));
469
+ const isRuleItemList = (r) => Array.isArray(r) && r.length > 0 && typeof r[0] === "object" && r[0] !== null && "loaders" in r[0];
470
+ /** ルールをルール項目の列にする(利用者の項目と、前の withAuspex が足した項目) */
471
+ function toItems(rule) {
472
+ if (rule === void 0) return [];
473
+ if (isRuleItemList(rule)) return rule;
474
+ if (Array.isArray(rule)) return [{ loaders: rule }];
475
+ return [rule];
476
+ }
477
+ /** 前の withAuspex(二重掛けの内側)が足した自分のローダーを取り除く。空になった項目は捨てる */
478
+ function withoutOurs(items) {
479
+ return items.flatMap((item) => {
480
+ if (!item.loaders.some(isOurs)) return [item];
481
+ const loaders = item.loaders.filter((l) => !isOurs(l));
482
+ return loaders.length > 0 ? [{
483
+ ...item,
484
+ loaders
485
+ }] : [];
486
+ });
487
+ }
488
+ /**
489
+ * Turbopack のルールに loader を足す。loaders は後ろから順に適用される(webpack と同じ)ので、
490
+ * 末尾に足すと元のソースに最初に掛かる。`when` を満たす loaders にだけ足す
491
+ */
492
+ function appendToRule(rule, loader, when) {
493
+ if (rule === void 0) return void 0;
494
+ const add = (ls) => {
495
+ if (!when(ls)) return ls;
496
+ const same = (l) => loaderName(l) === loaderName(loader);
497
+ return ls.some(same) ? ls.map((l) => same(l) ? loader : l) : [...ls, loader];
498
+ };
499
+ if (isRuleItemList(rule)) return rule.map((item) => ({
500
+ ...item,
501
+ loaders: add(item.loaders)
502
+ }));
503
+ if (Array.isArray(rule)) return add(rule);
504
+ return {
505
+ ...rule,
506
+ loaders: add(rule.loaders)
507
+ };
508
+ }
509
+ const isOursSvg = (l) => loaderName(l) === SVG_NAME_LOADER;
510
+ /**
511
+ * SVGR を含む項目を、production(refs)と development(strip)の 2 項目に分ける。前の withAuspex(二重掛けの内側)が
512
+ * 分けた項目は、development 側を捨てて production 側の条件を戻してから分け直す
513
+ */
514
+ function splitSvgItems(items, refs, strip) {
515
+ const split = (c) => {
516
+ const all = c?.all;
517
+ const kind = all?.at(-1);
518
+ return all && (all.length === 1 || all.length === 2) && (kind === "production" || kind === "development") ? {
519
+ kind,
520
+ user: all[0] === kind ? void 0 : all[0]
521
+ } : null;
522
+ };
523
+ return items.flatMap((item) => {
524
+ if (!item.loaders.some(isOursSvg)) return [item];
525
+ const sp = split(item.condition);
526
+ if (sp?.kind === "development") return [];
527
+ const { condition: _c, ...rest } = item;
528
+ return [{
529
+ ...rest,
530
+ ...sp?.user === void 0 ? {} : { condition: sp.user },
531
+ loaders: item.loaders.filter((l) => !isOursSvg(l))
532
+ }];
533
+ }).flatMap((item) => {
534
+ if (!item.loaders.some(isSvgr)) return [item];
535
+ const cond = (kind) => item.condition === void 0 ? { all: [kind] } : { all: [item.condition, kind] };
536
+ return [{
537
+ ...item,
538
+ condition: cond("production"),
539
+ loaders: [...item.loaders, refs]
540
+ }, {
541
+ ...item,
542
+ condition: cond("development"),
543
+ loaders: [...item.loaders, strip]
544
+ }];
545
+ });
546
+ }
547
+ function withTurbopack(config, options, plan) {
548
+ const turbopack = config.turbopack ?? {};
549
+ const rules = { ...turbopack.rules ?? {} };
550
+ const major = nextMajor();
551
+ const distDir = config.distDir ?? ".next";
552
+ const strip = nameLoader(options, "strip", plan, distDir);
553
+ const refs = nameLoader(options, "refs", plan, distDir);
554
+ const kind = buildKind();
555
+ const conditional = plan.refs && major >= 16;
556
+ if (plan.refs && !conditional && kind === "unknown") warnOnce("phase-unknown", `[auspex] ${UNKNOWN_PHASE}`);
557
+ else if (!plan.refs && kind !== "dev") warnReason(plan.reason);
558
+ const single = plan.refs && !conditional && kind === "build" ? refs : strip;
559
+ const svgRefs = nameLoader(options, "refs", plan, distDir, SVG_NAME_LOADER);
560
+ const svgStrip = nameLoader(options, "strip", plan, distDir, SVG_NAME_LOADER);
561
+ const svgSingle = single === refs ? svgRefs : svgStrip;
562
+ for (const glob of JS_GLOBS) {
563
+ const user = withoutOurs(toItems(rules[glob]));
564
+ if (conditional) {
565
+ const foreign = { not: "foreign" };
566
+ const item = (condition, loader) => ({
567
+ condition: { all: [foreign, condition] },
568
+ loaders: [loader]
569
+ });
570
+ rules[glob] = [
571
+ ...user,
572
+ item("production", refs),
573
+ item("development", strip)
574
+ ];
575
+ } else rules[glob] = user.length === 0 ? iconNameRule(major, single) : appendToRule(user, single, () => true);
576
+ }
577
+ if (options.iconNames !== false) for (const glob of Object.keys(rules).filter((g) => /\bsvg\b/i.test(g))) if (conditional) rules[glob] = splitSvgItems(toItems(rules[glob]), svgRefs, svgStrip);
578
+ else rules[glob] = appendToRule(rules[glob], svgSingle, (ls) => ls.some(isSvgr));
579
+ return {
580
+ ...turbopack,
581
+ rules
582
+ };
583
+ }
584
+ /** webpack の既存ルールのうち @svgr/webpack を使うものに、前段(use の末尾)として SVG 用ローダーを足す */
585
+ function addSvgLoader(rules, svg) {
586
+ for (const r of rules) {
587
+ if (!r || typeof r !== "object") continue;
588
+ const rule = r;
589
+ if (Array.isArray(rule.oneOf)) addSvgLoader(rule.oneOf, svg);
590
+ if (Array.isArray(rule.rules)) addSvgLoader(rule.rules, svg);
591
+ if (typeof rule.loader === "string" && isSvgr(rule.loader)) {
592
+ rule.use = [{
593
+ loader: rule.loader,
594
+ options: rule.options
595
+ }, svg];
596
+ delete rule.loader;
597
+ delete rule.options;
598
+ } else if (typeof rule.use === "string" && isSvgr(rule.use)) rule.use = [rule.use, svg];
599
+ else if (Array.isArray(rule.use) && rule.use.some((u) => (typeof u === "string" || u && typeof u === "object" && "loader" in u) && isSvgr(u))) {
600
+ const use = rule.use;
601
+ const isSvg = (u) => loaderName(u) === SVG_NAME_LOADER;
602
+ rule.use = use.some(isSvg) ? use.map((u) => isSvg(u) ? svg : u) : [...use, svg];
603
+ }
604
+ }
605
+ }
606
+ const CACHE_SUFFIX = /\|auspex:[^|]*$/;
607
+ /** 自分のローダーのルールがあればオプションを置き換え、無ければ足す。キャッシュの版に keyId とモードを足す(二重掛けでは置き換える) */
608
+ function applyWebpack(cfg, loader, tag) {
609
+ cfg.module ??= {};
610
+ cfg.module.rules ??= [];
611
+ const existing = cfg.module.rules.find((r) => !!r && typeof r === "object" && Array.isArray(r.use) && r.use.some((u) => isOurs(u)));
612
+ if (existing) existing.use = [loader];
613
+ else cfg.module.rules.push({
614
+ test: /\.(t|j)sx?$/,
615
+ exclude: /[\\/]node_modules[\\/]/,
616
+ enforce: "pre",
617
+ use: [loader]
618
+ });
619
+ const cache = cfg.cache;
620
+ if (cache && typeof cache === "object") {
621
+ const c = cache;
622
+ c.version = `${(c.version ?? "").replace(CACHE_SUFFIX, "")}|auspex:${tag}`;
623
+ }
624
+ }
625
+ /** ビルドの終わりの登録(hook)。今のオプションと環境変数から鍵・host を決め直す。失敗してもビルドを落とさない */
626
+ function registrationHook(box, existing) {
627
+ const hook = (async (metadata) => {
628
+ if (existing) await existing(metadata);
629
+ const k = key();
630
+ const host = registrationHost(box.options.host ?? process.env.NEXT_PUBLIC_AUSPEX_HOST);
631
+ if (!k || !host || !isValidHost(host)) return;
632
+ const g = globalThis;
633
+ const done = g[REGISTERED] ?? /* @__PURE__ */ new Set();
634
+ g[REGISTERED] = done;
635
+ const id = `${keyIdOf(k)}:${metadata.projectDir}:${metadata.distDir}`;
636
+ if (done.has(id)) return;
637
+ done.add(id);
638
+ await registerNames({
639
+ key: k,
640
+ host,
641
+ keyId: keyIdOf(k),
642
+ projectDir: metadata.projectDir,
643
+ distDir: metadata.distDir,
644
+ readBuildIdFile: false
645
+ });
646
+ });
647
+ hook[HOOK_BOX] = box;
648
+ return hook;
649
+ }
650
+ function apply(config, options) {
651
+ const prev = config[MARK];
652
+ const merged = { ...prev };
653
+ for (const [k, v] of Object.entries(options)) {
654
+ if (v === void 0) continue;
655
+ if (prev && prev[k] !== void 0 && prev[k] !== v) console.log(`[auspex] withAuspex が二重に掛かっている: 外側のオプション ${k} を使う`);
656
+ merged[k] = v;
657
+ }
658
+ const plan = planFor(merged, Boolean(process.env.TURBOPACK));
659
+ const distDir = config.distDir ?? ".next";
660
+ const existingHook = config.compiler?.runAfterProductionCompile;
661
+ const box = existingHook?.[HOOK_BOX];
662
+ let compiler = config.compiler;
663
+ if (box) box.options = merged;
664
+ else if (plan.hook) compiler = {
665
+ ...config.compiler,
666
+ runAfterProductionCompile: registrationHook({ options: merged }, existingHook)
667
+ };
668
+ const userWebpack = config.webpack;
669
+ return {
670
+ ...config,
671
+ ...compiler === void 0 ? {} : { compiler },
672
+ turbopack: withTurbopack(config, merged, plan),
673
+ webpack(webpackConfig, context) {
674
+ const cfg = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
675
+ const dev = context?.dev;
676
+ let mode = "strip";
677
+ if (dev === void 0) warnOnce("phase-unknown", `[auspex] ${UNKNOWN_PHASE}`);
678
+ else if (!dev) {
679
+ if (plan.refs) mode = "refs";
680
+ else warnReason(plan.reason);
681
+ }
682
+ if (merged.iconNames !== false) addSvgLoader(cfg.module?.rules ?? [], nameLoader(merged, mode, plan, distDir, SVG_NAME_LOADER));
683
+ applyWebpack(cfg, nameLoader(merged, mode, plan, distDir), `${mode}:${mode === "refs" ? plan.keyId : "none"}`);
684
+ return cfg;
685
+ },
686
+ [MARK]: merged
687
+ };
688
+ }
689
+ function withAuspex(config = {}, options = {}) {
690
+ if (typeof config === "function") return async (phase, ctx) => apply(await config(phase, ctx), options);
691
+ return apply(config, options);
692
+ }
693
+ //#endregion
694
+ exports.ICON_NAME_LOADER = ICON_NAME_LOADER;
695
+ exports.SVG_NAME_LOADER = SVG_NAME_LOADER;
696
+ exports.hookRow = hookRow;
697
+ exports.iconNameRule = iconNameRule;
698
+ exports.nextMajor = nextMajor;
699
+ exports.nextVersion = nextVersion;
700
+ exports.withAuspex = withAuspex;