@takagaki/cortex-decisions-viewer 0.4.45 → 0.4.48
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/build.js +10 -2
- package/dist/cli.js +2 -0
- package/dist/render.js +214 -1
- package/package.json +2 -1
package/dist/build.js
CHANGED
|
@@ -182,7 +182,10 @@ async function readFleetSources(repoRoot) {
|
|
|
182
182
|
tools = undefined;
|
|
183
183
|
}
|
|
184
184
|
const fleetGeneratedAt = typeof obj.generatedAt === "string" ? obj.generatedAt : undefined;
|
|
185
|
-
|
|
185
|
+
const secretSources = obj.secretSources && typeof obj.secretSources === "object"
|
|
186
|
+
? obj.secretSources
|
|
187
|
+
: undefined;
|
|
188
|
+
return { externalSources, internalSources, pipelines, tools, fleetGeneratedAt, secretSources };
|
|
186
189
|
}
|
|
187
190
|
/** パスがディレクトリとして存在するか */
|
|
188
191
|
async function isDirectory(dirAbs) {
|
|
@@ -975,15 +978,20 @@ async function build(opts) {
|
|
|
975
978
|
graph,
|
|
976
979
|
categories,
|
|
977
980
|
months,
|
|
978
|
-
intake: opts.intakeUrl && opts.projectKey
|
|
981
|
+
intake: opts.intakeUrl && opts.projectKey && opts.intakeToken
|
|
979
982
|
? {
|
|
980
983
|
url: opts.intakeUrl,
|
|
981
984
|
token: opts.intakeToken ?? "",
|
|
982
985
|
projectKey: opts.projectKey,
|
|
983
986
|
}
|
|
984
987
|
: undefined,
|
|
988
|
+
secretKinds: (opts.secretKinds ?? "")
|
|
989
|
+
.split(",")
|
|
990
|
+
.map((s) => s.trim())
|
|
991
|
+
.filter(Boolean),
|
|
985
992
|
externalSources: fleet.externalSources,
|
|
986
993
|
fleetGeneratedAt: fleet.fleetGeneratedAt,
|
|
994
|
+
secretSources: fleet.secretSources,
|
|
987
995
|
internalSources: fleet.internalSources,
|
|
988
996
|
pipelines: fleet.pipelines,
|
|
989
997
|
tools: fleet.tools,
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ program
|
|
|
19
19
|
.option("--intake-url <url>", "投入フォームのLambda URL(環境変数 INTAKE_URL でも可)")
|
|
20
20
|
.option("--intake-token <token>", "投入フォームの共有秘密トークン(環境変数 INTAKE_TOKEN でも可)")
|
|
21
21
|
.option("--project-key <key>", "案件キー(投入フォームのルーティングキー)")
|
|
22
|
+
.option("--secret-kinds <kinds>", "鍵の入力欄を出す連携(カンマ区切り。例: figma,external-sources,backlog)")
|
|
22
23
|
.action(async (dir, opts) => {
|
|
23
24
|
try {
|
|
24
25
|
const result = await (0, build_1.build)({
|
|
@@ -32,6 +33,7 @@ program
|
|
|
32
33
|
intakeUrl: opts.intakeUrl ?? process.env.INTAKE_URL,
|
|
33
34
|
intakeToken: opts.intakeToken ?? process.env.INTAKE_TOKEN,
|
|
34
35
|
projectKey: opts.projectKey,
|
|
36
|
+
secretKinds: opts.secretKinds,
|
|
35
37
|
});
|
|
36
38
|
console.log(`✓ ${result.count} 件のレコードを ${result.outDir} にビルドしました`);
|
|
37
39
|
}
|
package/dist/render.js
CHANGED
|
@@ -743,6 +743,13 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
743
743
|
.badge.ds-gold { background: #fef3c7; color: #92400e; border: 1px solid #fcd34d; }
|
|
744
744
|
.badge.ds-excluded { background: #eceef1; color: #6b7280; }
|
|
745
745
|
.badge.ds-undeclared { background: #fff4e5; color: #9a6700; }
|
|
746
|
+
.ds-toggle { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
|
|
747
|
+
.ds-toggle-btn { font-size: 12px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--text); cursor: pointer; }
|
|
748
|
+
.ds-toggle-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
|
|
749
|
+
.ds-toggle-btn:disabled { opacity: .6; cursor: default; }
|
|
750
|
+
.ds-toggle-msg { font-size: 12px; color: var(--muted); }
|
|
751
|
+
.ds-toggle-msg.is-ok { color: #1a7f37; }
|
|
752
|
+
.ds-toggle-msg.is-error { color: #b42318; }
|
|
746
753
|
.badge.ds-notify { background: #eff6ff; color: #1e40af; border: 1px solid #bfdbfe; }
|
|
747
754
|
.ds-gate { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600; flex: 0 0 auto; }
|
|
748
755
|
.ds-gate::before { content: ""; width: 8px; height: 8px; border-radius: 999px; background: currentColor; }
|
|
@@ -760,6 +767,12 @@ a.ds-name:hover { text-decoration: underline; }
|
|
|
760
767
|
/* ---- 操作タブ ---- */
|
|
761
768
|
.ops-intro { color: var(--muted); font-size: 14px; line-height: 1.8; margin: 4px 0 8px; }
|
|
762
769
|
.ops-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
|
|
770
|
+
.sec-state { font-size: 12.5px; font-weight: 600; margin: 0 0 8px; color: var(--muted); }
|
|
771
|
+
.sec-state.is-managed { color: #1a7f37; }
|
|
772
|
+
.sec-state.is-none { color: var(--muted); font-weight: 400; }
|
|
773
|
+
.sec-how { color: var(--muted); font-size: 12px; line-height: 1.7; margin: 0 0 10px; }
|
|
774
|
+
.sec-input { width: 100%; box-sizing: border-box; font-size: 13px; padding: 7px 10px; margin-bottom: 8px; border: 1px solid var(--border); border-radius: 6px; background: #fff; color: var(--text); }
|
|
775
|
+
.sec-input:focus { outline: none; border-color: var(--accent); }
|
|
763
776
|
.ops-card {
|
|
764
777
|
background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
765
778
|
padding: 18px; box-shadow: var(--shadow); display: flex; flex-direction: column; gap: 10px;
|
|
@@ -1525,6 +1538,93 @@ const CLIENT_JS = `
|
|
|
1525
1538
|
]);
|
|
1526
1539
|
}
|
|
1527
1540
|
// 外部ソース1件のカード(グループ化表示・展開表示の両方から使う)
|
|
1541
|
+
/**
|
|
1542
|
+
* Gold昇格の切り替えボタン。押すと設定ファイル(channels.json)が書き換わる。
|
|
1543
|
+
*
|
|
1544
|
+
* **広げる方向(OFF→ON)には確認を出す。** そのチャンネルの内容をAIが読んで、顧客も見る
|
|
1545
|
+
* Decision・用語集に蒸留するようになる。社内限定のチャンネルをONにすると、顧客が見られない
|
|
1546
|
+
* 会話が顧客可視の層に出てくる(実際にその事故が起きている)。狭める方向は安全なので確認しない。
|
|
1547
|
+
*
|
|
1548
|
+
* 保存しても画面はすぐには変わらない(接続状況は夜間バッチのスナップショット)。
|
|
1549
|
+
* それを伝えないと「保存できていない」と受け取られるので、応答のメッセージをそのまま出す。
|
|
1550
|
+
*/
|
|
1551
|
+
function goldToggle(s) {
|
|
1552
|
+
var wrap = el("div", { class: "ds-toggle" });
|
|
1553
|
+
var on = s.gold === true;
|
|
1554
|
+
var btn = el("button", {
|
|
1555
|
+
class: "ds-toggle-btn" + (on ? " is-on" : ""),
|
|
1556
|
+
type: "button",
|
|
1557
|
+
text: on ? "Gold昇格を止める" : "Gold昇格を始める",
|
|
1558
|
+
});
|
|
1559
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
1560
|
+
wrap.appendChild(btn);
|
|
1561
|
+
wrap.appendChild(msg);
|
|
1562
|
+
|
|
1563
|
+
btn.addEventListener("click", async function () {
|
|
1564
|
+
var next = !on;
|
|
1565
|
+
if (next) {
|
|
1566
|
+
var ok = window.confirm(
|
|
1567
|
+
"「" + (s.name || s.ref) + "」の内容を、顧客も見る Decision・用語集に蒸留するようになります。" +
|
|
1568
|
+
String.fromCharCode(10, 10) +
|
|
1569
|
+
"社内限定の会話が含まれるチャンネルでは、顧客に見せられない内容が出てしまう可能性があります。" +
|
|
1570
|
+
String.fromCharCode(10) +
|
|
1571
|
+
"顧客がこのチャンネルにいる(=すでに会話を見ている)場合にだけ有効にしてください。" +
|
|
1572
|
+
String.fromCharCode(10, 10) +
|
|
1573
|
+
"※ 今夜の蒸留(22時ごろ)までに止めれば取り込まれません。一度生成された記録は自動では消えません。" +
|
|
1574
|
+
String.fromCharCode(10, 10) +
|
|
1575
|
+
"有効にしますか?",
|
|
1576
|
+
);
|
|
1577
|
+
if (!ok) return;
|
|
1578
|
+
}
|
|
1579
|
+
var editor = window.prompt("変更者のお名前を入力してください(履歴に残ります)", "");
|
|
1580
|
+
if (editor === null) return;
|
|
1581
|
+
if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
|
|
1582
|
+
|
|
1583
|
+
btn.disabled = true;
|
|
1584
|
+
msg.className = "ds-toggle-msg";
|
|
1585
|
+
msg.textContent = "保存中…";
|
|
1586
|
+
var intake = SITE.intake;
|
|
1587
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
1588
|
+
try {
|
|
1589
|
+
var res = await fetch(pfx + "op=config&t=" + encodeURIComponent(intake.token), {
|
|
1590
|
+
method: "POST",
|
|
1591
|
+
headers: { "content-type": "application/json" },
|
|
1592
|
+
body: JSON.stringify({
|
|
1593
|
+
file: "チャット/channels.json",
|
|
1594
|
+
op: "set-channel-gold",
|
|
1595
|
+
ref: s.ref,
|
|
1596
|
+
value: next,
|
|
1597
|
+
editor: editor,
|
|
1598
|
+
}),
|
|
1599
|
+
});
|
|
1600
|
+
var j = await res.json().catch(function () { return {}; });
|
|
1601
|
+
if (!res.ok) {
|
|
1602
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1603
|
+
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
1604
|
+
btn.disabled = false;
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
msg.className = "ds-toggle-msg is-ok";
|
|
1608
|
+
msg.textContent = "✓ " + (j.message || "保存しました。");
|
|
1609
|
+
// **押した内容をボタンに反映し、逆向きの操作を必ず残す。**
|
|
1610
|
+
// バッジ(Gold昇格の読み取り対象/除外)は夜間バッチのスナップショット由来なので変えない。
|
|
1611
|
+
// 変えると「もう効いている」と誤解させるため。変えるのはボタンだけ。
|
|
1612
|
+
// ここを disabled のままにすると、誤って有効にした直後に**同じ画面から止められなくなる**。
|
|
1613
|
+
// 表示が直るのは翌朝で、その間に22時のGold昇格が走り、生成されたレコードは自動削除されない
|
|
1614
|
+
// (Gold層は追記専用)。危険な方向は押せるのに戻す方向が塞がる、という状態は許容できない。
|
|
1615
|
+
on = next;
|
|
1616
|
+
btn.textContent = on ? "Gold昇格を止める" : "Gold昇格を始める";
|
|
1617
|
+
btn.className = "ds-toggle-btn" + (on ? " is-on" : "");
|
|
1618
|
+
btn.disabled = false;
|
|
1619
|
+
} catch (e) {
|
|
1620
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1621
|
+
msg.textContent = "✗ 通信に失敗しました。";
|
|
1622
|
+
btn.disabled = false;
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
return wrap;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1528
1628
|
function extSourceCard(s, meta) {
|
|
1529
1629
|
var badges = el("div", { class: "ds-badges" });
|
|
1530
1630
|
// 「意図的に外した」と「宣言し忘れ」を分けて出す。同じ「除外」と書くと、
|
|
@@ -1534,7 +1634,7 @@ const CLIENT_JS = `
|
|
|
1534
1634
|
else if (s.gold === false) badges.appendChild(el("span", { class: "badge ds-excluded", text: "除外" }));
|
|
1535
1635
|
if (s.notify === true) badges.appendChild(el("span", { class: "badge ds-notify", text: "通知先" }));
|
|
1536
1636
|
var gate = DS_GATE[s.gate] || DS_GATE["unknown"];
|
|
1537
|
-
|
|
1637
|
+
var card = el("div", { class: "ds-card" }, [
|
|
1538
1638
|
el("div", { class: "ds-card-main" }, [
|
|
1539
1639
|
el("span", { class: "ds-icon", text: meta.icon }),
|
|
1540
1640
|
el("span", { class: "ds-type", text: meta.label }),
|
|
@@ -1543,6 +1643,12 @@ const CLIENT_JS = `
|
|
|
1543
1643
|
badges,
|
|
1544
1644
|
el("span", { class: "ds-gate " + gate.cls, text: gate.text }),
|
|
1545
1645
|
]);
|
|
1646
|
+
// Gold昇格の切り替え。書き込み経路(intake Lambda)がある案件でだけ出す。
|
|
1647
|
+
// Slack以外は読み手が導出しないので出さない(押せても効かないボタンは出さない)。
|
|
1648
|
+
if (SITE.intake && s.type === "slack" && s.ref) {
|
|
1649
|
+
card.appendChild(goldToggle(s));
|
|
1650
|
+
}
|
|
1651
|
+
return card;
|
|
1546
1652
|
}
|
|
1547
1653
|
|
|
1548
1654
|
// ---------- 操作タブ(接続マップ・データソースと自動化・取り込み。今後この受け皿に機能を足す) ----------
|
|
@@ -1556,9 +1662,116 @@ const CLIENT_JS = `
|
|
|
1556
1662
|
grid.appendChild(opsCard("🗒️", "会議の文字起こしを追加", "顧客主催の会議など、自動取り込みできない会議の文字起こしファイル(.txt / .docx 等)から議事録を生成します。", "transcript", "文字起こしを追加"));
|
|
1557
1663
|
grid.appendChild(opsCard("📎", "共有資料を追加", "提案書・仕様書・議事メモなどの資料を Markdown 化して 共有資料 に取り込みます。", "material", "資料を追加"));
|
|
1558
1664
|
app.appendChild(grid);
|
|
1665
|
+
renderSecrets();
|
|
1559
1666
|
}
|
|
1560
1667
|
}
|
|
1561
1668
|
|
|
1669
|
+
/** 鍵の入力欄に出す説明。kind は Lambda 側(resolveSecretTarget)と綴りを合わせること */
|
|
1670
|
+
var SECRET_FORMS = {
|
|
1671
|
+
"figma": {
|
|
1672
|
+
icon: "🎨",
|
|
1673
|
+
title: "Figma のトークン",
|
|
1674
|
+
desc: "デザインの画面インベントリを毎晩同期するために使います。",
|
|
1675
|
+
how: "Figma → Settings → Security → Personal access tokens で発行(file_content:read があれば足ります)",
|
|
1676
|
+
},
|
|
1677
|
+
"backlog": {
|
|
1678
|
+
icon: "📋",
|
|
1679
|
+
title: "Backlog のAPIキー",
|
|
1680
|
+
desc: "課題・Wiki・ドキュメントを毎晩同期するために使います。",
|
|
1681
|
+
how: "Backlog → 個人設定 → API で発行",
|
|
1682
|
+
},
|
|
1683
|
+
"external-sources": {
|
|
1684
|
+
icon: "🐙",
|
|
1685
|
+
title: "GitHub の読み取りトークン",
|
|
1686
|
+
desc: "開発リポジトリの Issues / Discussions を読み、Gold の原料にするために使います。",
|
|
1687
|
+
how: "GitHub → Settings → Developer settings → Personal access tokens で、対象リポジトリに Contents / Issues の Read-only を付けて発行",
|
|
1688
|
+
},
|
|
1689
|
+
};
|
|
1690
|
+
|
|
1691
|
+
/**
|
|
1692
|
+
* 連携の鍵(トークン)の投入。
|
|
1693
|
+
*
|
|
1694
|
+
* **入れた値は読み出せない。** 表示・確認の経路は作っていないので、間違えたら入れ直す。
|
|
1695
|
+
* 保存先は案件ごとに分かれており、他の案件には影響しない。
|
|
1696
|
+
* 反映は次回のワークフロー実行から(接続状況の表示が変わるのは翌朝の自己チェック後)。
|
|
1697
|
+
*/
|
|
1698
|
+
function renderSecrets() {
|
|
1699
|
+
var kinds = (SITE.secretKinds || []).filter(function (k) { return SECRET_FORMS[k]; });
|
|
1700
|
+
if (!kinds.length) return;
|
|
1701
|
+
|
|
1702
|
+
app.appendChild(el("div", { class: "section-label", text: "連携の鍵" }));
|
|
1703
|
+
app.appendChild(el("p", { class: "ops-intro", text: "各ツールを読むためのトークンです。入れた値はここには表示されません(保存だけできます)。保存先は案件ごとに分かれているので、他の案件には影響しません。" }));
|
|
1704
|
+
var grid = el("div", { class: "ops-grid" });
|
|
1705
|
+
kinds.forEach(function (k) { grid.appendChild(secretCard(k, SECRET_FORMS[k])); });
|
|
1706
|
+
app.appendChild(grid);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function secretCard(kind, meta) {
|
|
1710
|
+
// **いま何で動いているか**を先に出す。入れ直したのに反映されていない状態が
|
|
1711
|
+
// 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
|
|
1712
|
+
// 保存の直後は前日の値のまま。時刻は上の節に出している)。
|
|
1713
|
+
var src = (SITE.secretSources || {})[kind] || "";
|
|
1714
|
+
var state = src
|
|
1715
|
+
? el("p", { class: "sec-state" + (src === "Secrets Manager" ? " is-managed" : ""),
|
|
1716
|
+
text: "現在: " + src + " から取得中" })
|
|
1717
|
+
: el("p", { class: "sec-state is-none", text: "現在: 未設定" });
|
|
1718
|
+
|
|
1719
|
+
var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
|
|
1720
|
+
var who = el("input", { class: "sec-input", type: "text", placeholder: "変更者のお名前(履歴に残ります)", autocomplete: "off" });
|
|
1721
|
+
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
|
|
1722
|
+
var msg = el("div", { class: "ds-toggle-msg" });
|
|
1723
|
+
|
|
1724
|
+
btn.addEventListener("click", async function () {
|
|
1725
|
+
var value = input.value;
|
|
1726
|
+
var editor = who.value;
|
|
1727
|
+
if (!value.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ トークンを入力してください。"; return; }
|
|
1728
|
+
if (!editor.trim()) { msg.className = "ds-toggle-msg is-error"; msg.textContent = "⚠ お名前を入力してください。"; return; }
|
|
1729
|
+
|
|
1730
|
+
btn.disabled = true;
|
|
1731
|
+
msg.className = "ds-toggle-msg";
|
|
1732
|
+
msg.textContent = "保存中…";
|
|
1733
|
+
var intake = SITE.intake;
|
|
1734
|
+
var pfx = intake.url + (intake.url.indexOf("?") === -1 ? "?" : "&");
|
|
1735
|
+
try {
|
|
1736
|
+
var res = await fetch(pfx + "op=secret&t=" + encodeURIComponent(intake.token), {
|
|
1737
|
+
method: "POST",
|
|
1738
|
+
headers: { "content-type": "application/json" },
|
|
1739
|
+
// **シークレット名は送らない。** どの連携かだけを送り、書き込み先はサーバが案件から決める。
|
|
1740
|
+
body: JSON.stringify({ projectKey: intake.projectKey, kind: kind, value: value, editor: editor }),
|
|
1741
|
+
});
|
|
1742
|
+
var j = await res.json().catch(function () { return {}; });
|
|
1743
|
+
if (!res.ok) {
|
|
1744
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1745
|
+
msg.textContent = "✗ " + (j.message || "保存できませんでした。");
|
|
1746
|
+
btn.disabled = false;
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
msg.className = "ds-toggle-msg is-ok";
|
|
1750
|
+
msg.textContent = "✓ " + (j.message || "保存しました。");
|
|
1751
|
+
// 入れた値は画面に残さない(肩越しに見えるのを防ぐ)。お名前は続けて直せるよう残す。
|
|
1752
|
+
input.value = "";
|
|
1753
|
+
btn.disabled = false;
|
|
1754
|
+
} catch (e) {
|
|
1755
|
+
msg.className = "ds-toggle-msg is-error";
|
|
1756
|
+
msg.textContent = "✗ 通信に失敗しました。";
|
|
1757
|
+
btn.disabled = false;
|
|
1758
|
+
}
|
|
1759
|
+
});
|
|
1760
|
+
|
|
1761
|
+
return el("div", { class: "ops-card" }, [
|
|
1762
|
+
el("div", { class: "ops-card-head" }, [
|
|
1763
|
+
el("span", { class: "ops-card-icon", text: meta.icon }),
|
|
1764
|
+
el("h3", { class: "ops-card-title", text: meta.title }),
|
|
1765
|
+
]),
|
|
1766
|
+
el("p", { class: "ops-card-desc", text: meta.desc }),
|
|
1767
|
+
state,
|
|
1768
|
+
el("p", { class: "sec-how", text: meta.how }),
|
|
1769
|
+
input,
|
|
1770
|
+
who,
|
|
1771
|
+
el("div", { class: "ds-toggle" }, [btn, msg]),
|
|
1772
|
+
]);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1562
1775
|
// ---------- 接続マップ(ビルド時生成のSVGを<template>から取り出して表示) ----------
|
|
1563
1776
|
function renderConnMap() {
|
|
1564
1777
|
var tpl = document.getElementById("conn-map");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takagaki/cortex-decisions-viewer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.48",
|
|
4
4
|
"description": "Cortexの意思決定記録(Decisions/*.md)を静的サイトにビルドして閲覧する。各レコードに「Edit on GitHub」リンクを付与する。",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^22.10.0",
|
|
32
|
+
"linkedom": "^0.18.13",
|
|
32
33
|
"typescript": "^5.7.0"
|
|
33
34
|
}
|
|
34
35
|
}
|