@takagaki/cortex-decisions-viewer 0.12.20 → 0.12.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/build.js +20 -1
- package/dist/render.js +253 -42
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Cortexの意思決定記録(`Decisions/*.md`)を**静的サイトにビルドして閲覧する**CLIツール。
|
|
4
4
|
各レコードに「**Edit on GitHub**」リンクを付与し、閲覧画面から直接GitHubのエディタで編集できる。
|
|
5
5
|
|
|
6
|
-
`md-meta-view` の代替として、Cortex
|
|
6
|
+
`md-meta-view` の代替として、Cortex専用に自前実装したもの。出力は外部依存もサーバーも要らない静的ファイル一式で、AWS Amplify Hosting や S3 + CloudFront にそのまま載る。**JS と CSS は HTML に埋め込まず、`assets/` の外部ファイルに出す**——配信側(AIS ポータル)の Content-Security-Policy がインラインの `<script>` / `<style>` を実行しないため([#301](https://github.com/classmethod-internal/cortex-tools/issues/301))。
|
|
7
7
|
|
|
8
8
|
## 使い方
|
|
9
9
|
|
|
@@ -13,7 +13,22 @@ Cortexの意思決定記録(`Decisions/*.md`)を**静的サイトにビル
|
|
|
13
13
|
npx -y @takagaki/cortex-decisions-viewer build --out site --title "Decisions"
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
ビルド結果は `site
|
|
16
|
+
ビルド結果は `site/` ディレクトリ一式。`?id=<id>` で個別レコードに直リンクできる。
|
|
17
|
+
|
|
18
|
+
| 出力 | 中身 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `index.html` | 画面の骨組みと、埋め込みデータ(`<script type="application/json">`) |
|
|
21
|
+
| `404.html` | `index.html` と同じもの(サブパス直叩きのフォールバック) |
|
|
22
|
+
| `assets/viewer.<内容ハッシュ>.js` / `.css` | 画面のJSとCSS。内容が同じなら名前も同じ(版が同じなら全案件で同じ名前) |
|
|
23
|
+
| `bodies/*.html` | レコードの本文(詳細を開いたときに取りに行く) |
|
|
24
|
+
| `thumbs/*` | グラフのサムネイル画像 |
|
|
25
|
+
|
|
26
|
+
**配置は `site/` を丸ごと置く**(`index.html` だけを置いても画面は出ない)。
|
|
27
|
+
ファイル名を内容ハッシュにしているのは、`Cache-Control` を付けずに置かれた
|
|
28
|
+
`index.html` をブラウザが発見的にキャッシュするため——固定名だと
|
|
29
|
+
「利用者の手元に残った古い `index.html` が、消えた JS を指す」形になり、
|
|
30
|
+
強制再読み込みまで画面が真っ白になる。中身が変われば URL も変わるなら起きない。
|
|
31
|
+
配置側では `index.html` / `404.html` を `no-cache`、`assets/` を長期キャッシュにするとよい。
|
|
17
32
|
|
|
18
33
|
## CLI
|
|
19
34
|
|
package/dist/build.js
CHANGED
|
@@ -1888,7 +1888,26 @@ async function build(opts) {
|
|
|
1888
1888
|
// 本文HTML(リッチ・かさばる)を bodies/ へ外出しし、inlineデータからは除外する。
|
|
1889
1889
|
// 一覧・グラフ・検索は inline の searchText(軽量plaintext)で動き、詳細表示時にbodyRefをfetchする。
|
|
1890
1890
|
await externalizeBodies(site, outAbs);
|
|
1891
|
-
|
|
1891
|
+
// CSSとJSは外部ファイルへ出す。**インラインのままだと配信先で画面が出ない**——
|
|
1892
|
+
// AISポータルは /p/<案件>/cm/ に厳格な Content-Security-Policy を返しており、
|
|
1893
|
+
// インラインの style / script を実行しない(cortex-tools #301)。
|
|
1894
|
+
//
|
|
1895
|
+
// ファイル名を内容ハッシュにする理由: 配置先の S3 は Cache-Control を付けずに
|
|
1896
|
+
// 置かれ、ブラウザは Last-Modified からの発見的キャッシュをする。固定名だと
|
|
1897
|
+
// 「利用者の手元に残った古い index.html が、消えた JS を指す」形になり、
|
|
1898
|
+
// 強制再読み込みまで真っ白になる。中身が違えば URL も違うなら、それが起きない。
|
|
1899
|
+
//
|
|
1900
|
+
// 配置側(cortex-engine の build-viewer.yml)の責務: aws s3 sync --delete が
|
|
1901
|
+
// 古いハッシュのファイルを消すこと、index.html に Cache-Control: no-cache を
|
|
1902
|
+
// 付けること(assets/ は内容ハッシュ名なので長期キャッシュでよい)。
|
|
1903
|
+
const assetSources = (0, render_1.viewerAssetSources)();
|
|
1904
|
+
const assets = (0, render_1.viewerAssetNames)(assetSources);
|
|
1905
|
+
await node_fs_1.promises.mkdir(path.join(outAbs, "assets"), { recursive: true });
|
|
1906
|
+
await node_fs_1.promises.writeFile(path.join(outAbs, assets.css), assetSources.css, "utf8");
|
|
1907
|
+
await node_fs_1.promises.writeFile(path.join(outAbs, assets.js), assetSources.js, "utf8");
|
|
1908
|
+
// **renderSite ではなく renderSiteWithAssets を呼ぶ。** 引数を1つ落としても
|
|
1909
|
+
// 型が通ってしまう形にすると、本番の出力が黙ってインラインに戻る
|
|
1910
|
+
const html = (0, render_1.renderSiteWithAssets)(site, assets);
|
|
1892
1911
|
await node_fs_1.promises.writeFile(path.join(outAbs, "index.html"), html, "utf8");
|
|
1893
1912
|
// SPA的フォールバック(?id= はクエリなので不要だが、サブパス直叩き対策として404も用意)
|
|
1894
1913
|
await node_fs_1.promises.writeFile(path.join(outAbs, "404.html"), html, "utf8");
|
package/dist/render.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UNSETTLED_LIMITS = exports.LIVE_CONFIRM_LIMITS = void 0;
|
|
4
4
|
exports.renderSite = renderSite;
|
|
5
|
+
exports.renderSiteWithAssets = renderSiteWithAssets;
|
|
6
|
+
exports.viewerAssetSources = viewerAssetSources;
|
|
7
|
+
exports.viewerAssetNames = viewerAssetNames;
|
|
5
8
|
exports.sanitizeRegisteredNow = sanitizeRegisteredNow;
|
|
6
9
|
exports.sanitizeHarnessSchedules = sanitizeHarnessSchedules;
|
|
7
10
|
exports.sanitizeHarnessShelf = sanitizeHarnessShelf;
|
|
@@ -29,9 +32,19 @@ exports.ingestedAtOf = ingestedAtOf;
|
|
|
29
32
|
exports.keyMissingText = keyMissingText;
|
|
30
33
|
exports.deriveCapTile = deriveCapTile;
|
|
31
34
|
exports.renderConnectionMap = renderConnectionMap;
|
|
35
|
+
const node_crypto_1 = require("node:crypto");
|
|
32
36
|
const meet_guide_images_1 = require("./meet-guide-images");
|
|
33
|
-
/**
|
|
34
|
-
|
|
37
|
+
/**
|
|
38
|
+
* SiteDataをHTMLにレンダリングする。
|
|
39
|
+
*
|
|
40
|
+
* `assets` を渡すと、CSSとJSを**外部ファイルへの参照**(link/script src)で出す。
|
|
41
|
+
* 配信側(AISポータル)の Content-Security-Policy がインラインの style / script を
|
|
42
|
+
* 実行しないため(cortex-tools #301)。本番の出力(build())は必ずこちら。
|
|
43
|
+
*
|
|
44
|
+
* `assets` 省略時は従来どおり単一の自己完結HTML(埋め込みデータ+CSS+JS)を返す。
|
|
45
|
+
* JSDOM で画面を動かす既存テストがこの形に依存している(外部ファイルは読まれない)。
|
|
46
|
+
*/
|
|
47
|
+
function renderSite(site, assets) {
|
|
35
48
|
// </script> によるブレイクアウトを防ぐため < をエスケープして埋め込む
|
|
36
49
|
// Claude Code Web ディープリンク(この案件リポ+狙ったプロンプトで開く)。repoBaseUrl から slug を導出(追加入力なし)。
|
|
37
50
|
const repoSlug = site.repoBaseUrl
|
|
@@ -54,6 +67,13 @@ function renderSite(site) {
|
|
|
54
67
|
? `<a class="gh-btn" href="${escapeHtml(site.repoBaseUrl)}" target="_blank" rel="noopener" title="コンテキストリポジトリをGitHubで開く">${GITHUB_ICON}</a>`
|
|
55
68
|
: "";
|
|
56
69
|
const askBtn = `<a class="ask-ai-btn" href="${escapeHtml(askUrl)}" target="_blank" rel="noopener" title="Claude Code Web でこの案件のリポジトリを開き、プロジェクトについて質問・調査できます">💬 プロジェクトについてAIに聞く</a>`;
|
|
70
|
+
// CSPでインラインが拒否される環境向けの外部参照形。省略時は従来どおり埋め込む
|
|
71
|
+
const styleTag = assets
|
|
72
|
+
? `<link rel="stylesheet" href="${escapeHtml(assets.css)}" />`
|
|
73
|
+
: `<style>${CSS}</style>`;
|
|
74
|
+
const scriptTag = assets
|
|
75
|
+
? `<script src="${escapeHtml(assets.js)}"></script>`
|
|
76
|
+
: `<script>${CLIENT_JS}</script>`;
|
|
57
77
|
return `<!DOCTYPE html>
|
|
58
78
|
<html lang="ja">
|
|
59
79
|
<head>
|
|
@@ -61,7 +81,7 @@ function renderSite(site) {
|
|
|
61
81
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
62
82
|
<meta name="robots" content="noindex, nofollow" />
|
|
63
83
|
<title>${safeTitle}</title>
|
|
64
|
-
|
|
84
|
+
${styleTag}
|
|
65
85
|
</head>
|
|
66
86
|
<body>
|
|
67
87
|
<header class="site-header">
|
|
@@ -77,7 +97,7 @@ function renderSite(site) {
|
|
|
77
97
|
</footer>
|
|
78
98
|
${connMap ? `<template id="conn-map">${connMap}</template>\n` : ""}<script type="application/json" id="tutorial-data">${tutorialJson}</script>
|
|
79
99
|
<script type="application/json" id="site-data">${dataJson}</script>
|
|
80
|
-
|
|
100
|
+
${scriptTag}
|
|
81
101
|
</body>
|
|
82
102
|
</html>`;
|
|
83
103
|
}
|
|
@@ -89,6 +109,31 @@ function escapeHtml(s) {
|
|
|
89
109
|
.replace(/>/g, ">")
|
|
90
110
|
.replace(/"/g, """);
|
|
91
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* 外部ファイル参照の形でレンダリングする(assets必須)。
|
|
114
|
+
*
|
|
115
|
+
* build() はこちらだけを呼ぶ。renderSite は assets を省略できるので、
|
|
116
|
+
* 本番の出力にインラインが戻る退行を型で止められない。その1点のための薄い関数。
|
|
117
|
+
*/
|
|
118
|
+
function renderSiteWithAssets(site, assets) {
|
|
119
|
+
return renderSite(site, assets);
|
|
120
|
+
}
|
|
121
|
+
/** 外部ファイルへ書き出す中身。埋め込み形(renderSite の assets 省略時)と同一の文字列 */
|
|
122
|
+
function viewerAssetSources() {
|
|
123
|
+
return { css: CSS, js: CLIENT_JS };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 中身から外部ファイル名を決める。
|
|
127
|
+
*
|
|
128
|
+
* 内容ハッシュ(sha256の先頭8桁)にするのは、配信先がブラウザのキャッシュを
|
|
129
|
+
* 持つため。固定名だと「古いHTMLと新しいJS」の組み合わせが起こりうるが、
|
|
130
|
+
* 中身が変われば名前も変わるなら起こらない。**同じ中身なら同じ名前**なので、
|
|
131
|
+
* 版が同じかぎり全案件で同じファイル名になる。
|
|
132
|
+
*/
|
|
133
|
+
function viewerAssetNames(sources) {
|
|
134
|
+
const h = (text) => (0, node_crypto_1.createHash)("sha256").update(text, "utf8").digest("hex").slice(0, 8);
|
|
135
|
+
return { css: `assets/viewer.${h(sources.css)}.css`, js: `assets/viewer.${h(sources.js)}.js` };
|
|
136
|
+
}
|
|
92
137
|
/** 接続マップの能力定義(表示順・絵文字は詳細カードの流儀を踏襲) */
|
|
93
138
|
/**
|
|
94
139
|
* アダプタのロゴ。**公式配布のSVGをそのまま埋め込む。**
|
|
@@ -714,7 +759,7 @@ const STATE_STATUSES = {
|
|
|
714
759
|
reachable: ["ok", "not_in_channel", "missing_scope", "no_token", "unreachable", "unknown", "not_in_space", "no_credentials"],
|
|
715
760
|
ingested: ["ok", "failed", "skipped"],
|
|
716
761
|
};
|
|
717
|
-
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "lastOkAt", "lastFailAt"];
|
|
762
|
+
const STATE_ROW_FIELDS = ["cap", "ref", "workspace", "question", "status", "at", "by", "needed", "detail", "lastOkAt", "lastFailAt"];
|
|
718
763
|
/** stateNow はライブ専用キーで build.ts の網にかからない。画面側でも許可リストを通す */
|
|
719
764
|
function sanitizeStateNow(v) {
|
|
720
765
|
if (!Array.isArray(v))
|
|
@@ -775,6 +820,11 @@ function sanitizeStateNow(v) {
|
|
|
775
820
|
* 書いた行だけスコープ名が消え、「権限が足りません」とだけ出て何を直せばよいか
|
|
776
821
|
* 分からない状態に戻る。上書きするときは空でも必ず載せる——夜間の古い needed が
|
|
777
822
|
* 新しい判定の横に残ると、直したはずのスコープ名がいつまでも出る
|
|
823
|
+
*
|
|
824
|
+
* **detail(最後に測った理由)も載せる(#346)。** 夜間が書く理由文はここを通らないと
|
|
825
|
+
* 画面に届かない(fleet-status.json には焼き込まれないので、ライブが届かない案件では出ない
|
|
826
|
+
* ——それは意図。古いスナップショットの理由を出すより、出さないほうが嘘にならない)。
|
|
827
|
+
* needed と同じく、上書きするときは空でも必ず載せる
|
|
778
828
|
*/
|
|
779
829
|
function overlayStateOnFleet(view, rows) {
|
|
780
830
|
const v = view || {};
|
|
@@ -810,6 +860,7 @@ function overlayStateOnFleet(view, rows) {
|
|
|
810
860
|
gateAt: hit.at,
|
|
811
861
|
gateBy: hit.by,
|
|
812
862
|
needed: hit.needed || "",
|
|
863
|
+
detail: hit.detail || "",
|
|
813
864
|
});
|
|
814
865
|
});
|
|
815
866
|
return changed ? Object.assign({}, v, { externalSources: externals }) : v;
|
|
@@ -4427,6 +4478,99 @@ ${deriveCapTile.toString()}
|
|
|
4427
4478
|
cls: "ds-gate-muted",
|
|
4428
4479
|
},
|
|
4429
4480
|
};
|
|
4481
|
+
/**
|
|
4482
|
+
* 休止状態の行に、状態の語に添えて**押す前から**出す定型の補足(#346)。
|
|
4483
|
+
* 鍵は「アダプタ種別(helpTypeOf)× 状態」。値は文のパーツの配列で、文字列はテキスト、
|
|
4484
|
+
* { link: <リンクの文字列> } は GitHub App のインストール設定へのリンク(GITHUB_APP_INSTALL_URL)。
|
|
4485
|
+
*
|
|
4486
|
+
* ## なぜ画面の表で持つのか
|
|
4487
|
+
* サーバの needed / detail は「何が起きているか」と「どこを開くか」までを持つが、
|
|
4488
|
+
* **誰がその操作をできるか**(リポジトリの admin なら自分で・でなければ admin に依頼・
|
|
4489
|
+
* request は組織オーナーの承認待ち)は、夜間(engine)とボタン(Lambda)の2つの書き手が
|
|
4490
|
+
* 別々に文を作るので、サーバ側で足すと片方だけ古くなる。判定と URL はサーバ、定型の補足は
|
|
4491
|
+
* ここ、という役割分担にする。
|
|
4492
|
+
*
|
|
4493
|
+
* 🔴 **DS_GATE には入れない**(実測が返す語彙の表で、突き合わせテストの対象)。
|
|
4494
|
+
* DS_GATE_NO_TOKEN と同じく、仕組みはアダプタ非依存・データだけがアダプタ固有。
|
|
4495
|
+
*
|
|
4496
|
+
* 🔴 **DS_GATE[状態] が help を持つ状態にはエントリを置かない。** 置くと行の補足が2段になる
|
|
4497
|
+
* (github-state-reason.test.mjs の「行の補足は最大1段」が見張る)。
|
|
4498
|
+
*
|
|
4499
|
+
* 🔴 **URL を文字列で書き写さない**(帯・押した結果と同じ定数を使う。写経すると張り替えで
|
|
4500
|
+
* 片方だけ古くなる)。
|
|
4501
|
+
*
|
|
4502
|
+
* エントリがある状態では**この文だけ**を出し、サーバの needed / detail は出さない
|
|
4503
|
+
* ——理由文・補足・確認の一文の3段が issues / discussions の2行 × N リポに並ぶと、
|
|
4504
|
+
* 一覧が案内で埋まる(DS_GATE_NO_TOKEN.github の項と同じ判断)。needed は押した結果が持つ。
|
|
4505
|
+
*/
|
|
4506
|
+
var GITHUB_ADMIN_HELP = "このリポジトリの admin(管理者権限を持つ人)であれば、Configure から自分で追加できます(承認は不要です)。"
|
|
4507
|
+
+ "admin でない場合は、そのリポジトリの admin の人に「Cortex の GitHub App の対象にこのリポジトリを追加してください」と依頼してください。"
|
|
4508
|
+
+ "GitHub 側で request と表示された場合は、組織オーナーの承認待ちです。"
|
|
4509
|
+
+ "追加したあと「接続を確認」を押して、読めるようになったことを確かめてください。";
|
|
4510
|
+
var DS_GATE_RESTING_HELP = {
|
|
4511
|
+
github: {
|
|
4512
|
+
unreachable: [
|
|
4513
|
+
"App は導入済みですが、このリポジトリが読み取り対象に選ばれていない(または移動・改名・削除された)可能性があります。",
|
|
4514
|
+
{ link: "インストール設定を開く" },
|
|
4515
|
+
" の Configure から対象の組織を選び、Repository access に追加してください。",
|
|
4516
|
+
GITHUB_ADMIN_HELP,
|
|
4517
|
+
],
|
|
4518
|
+
},
|
|
4519
|
+
};
|
|
4520
|
+
/**
|
|
4521
|
+
* 表を引くときのアダプタ種別。**probeTypeOf を使わない**——あちらは「op=probe の受け口」の
|
|
4522
|
+
* 語彙で、Google Chat に空文字を返す(実測の口が無いため)。ここで使うと Google Chat の
|
|
4523
|
+
* エントリを永久に置けない
|
|
4524
|
+
*/
|
|
4525
|
+
function helpTypeOf(type) {
|
|
4526
|
+
var t = String(type || "");
|
|
4527
|
+
return t.indexOf("github") === 0 ? "github" : t;
|
|
4528
|
+
}
|
|
4529
|
+
/** 表のパーツ(無ければ null)。行と押した結果の**両方**がここを通す(写経しない) */
|
|
4530
|
+
function restingHelpPartsOf(type, gate) {
|
|
4531
|
+
var byType = DS_GATE_RESTING_HELP[helpTypeOf(type)];
|
|
4532
|
+
return (byType && byType[gate]) || null;
|
|
4533
|
+
}
|
|
4534
|
+
/** パーツを1段落にする。包む要素の class は置き場所で違う(行は ds-gate-help・押した結果は ds-probe-help) */
|
|
4535
|
+
function restingHelpNode(parts, cls) {
|
|
4536
|
+
var p = el("p", { class: cls });
|
|
4537
|
+
parts.forEach(function (part) {
|
|
4538
|
+
if (typeof part === "string") p.appendChild(document.createTextNode(part));
|
|
4539
|
+
else if (part && part.link) p.appendChild(el("a", { href: GITHUB_APP_INSTALL_URL, target: "_blank", rel: "noopener", text: part.link }));
|
|
4540
|
+
});
|
|
4541
|
+
return p;
|
|
4542
|
+
}
|
|
4543
|
+
/**
|
|
4544
|
+
* 休止状態の行に出す「最後に測った理由」(表にエントリが無い状態のフォールバック)。
|
|
4545
|
+
* needed(次の一手・URL 込み)を優先し、無ければ detail。
|
|
4546
|
+
*
|
|
4547
|
+
* 🔴 **書き手が button の detail は出さない。** ボタンは detail に判定の語そのもの
|
|
4548
|
+
* ("unreachable")や生のエラー("ratelimited")を書く——lastFailDetail を空で潰さない
|
|
4549
|
+
* ための痕跡で、表示用の値ではない(intake の op=probe のコメント)。人向けの文を持つのは
|
|
4550
|
+
* needed の側。夜間(gold)の detail は人向けの文なので出す
|
|
4551
|
+
*/
|
|
4552
|
+
function stateReasonOf(s) {
|
|
4553
|
+
if (s.needed) return String(s.needed);
|
|
4554
|
+
if (s.detail && s.gateBy !== "button") return String(s.detail);
|
|
4555
|
+
return "";
|
|
4556
|
+
}
|
|
4557
|
+
/**
|
|
4558
|
+
* この行に、押す前から理由・補足を出してよいか(#346)。**語の列挙で持たない**——
|
|
4559
|
+
* 別の作業が DS_GATE に語や help を足した日に、黙って漏れたり2段になったりしないよう
|
|
4560
|
+
* 導出で決める:
|
|
4561
|
+
* - 状態がある(未測定の行には出すものが無い)
|
|
4562
|
+
* - ok でない
|
|
4563
|
+
* - missing_scope でない(スコープ名の札 .ds-probe-scope が既に持つ)
|
|
4564
|
+
* - no_token でない(GitHub は組織の帯が導線を1つだけ持つ設計・Slack は DS_GATE_NO_TOKEN の help)
|
|
4565
|
+
* - not_in_channel でない(help は無いが、**状態の語そのものが操作の指示**
|
|
4566
|
+
* 「Slackで招待してください」。その下に夜間の「読めませんでした: not_in_channel」を
|
|
4567
|
+
* 並べても情報が増えず、招待という具体的な指示を薄める。help の有無とは別の軸)
|
|
4568
|
+
* - dsGateOf(s).help が無い(あれば DS_GATE の固定文が次の一手を持っている)
|
|
4569
|
+
*/
|
|
4570
|
+
function showsRestingReason(s) {
|
|
4571
|
+
if (!s.gate || s.gate === "ok" || s.gate === "missing_scope" || s.gate === "no_token" || s.gate === "not_in_channel") return false;
|
|
4572
|
+
return !dsGateOf(s).help;
|
|
4573
|
+
}
|
|
4430
4574
|
/**
|
|
4431
4575
|
* Slack のチャンネルが社内ワークスペースのものか。**workspace の有無だけで決まる**
|
|
4432
4576
|
* (省略=社内。#104 でこの値が画面まで届いていることは slack-workspace.test.mjs が見張る)。
|
|
@@ -4972,9 +5116,14 @@ ${deriveCapTile.toString()}
|
|
|
4972
5116
|
var owners = githubOwnersView();
|
|
4973
5117
|
var out = [];
|
|
4974
5118
|
groupGithubByOwner(items, owners).forEach(function (g) {
|
|
4975
|
-
// **行ごとに meta を引く。** issues と discussions
|
|
5119
|
+
// **行ごとに meta を引く。** issues と discussions でアイコンとラベルが違う。
|
|
5120
|
+
// Issues と Discussions は同じ接続先(同じ dev 行)なので、理由・補足は1本目だけが
|
|
5121
|
+
// 持つ(#346)。同じ長文が2行に並ぶと一覧が案内で埋まる
|
|
5122
|
+
var seenRef = {};
|
|
4976
5123
|
var cards = g.items.map(function (x) {
|
|
4977
|
-
|
|
5124
|
+
var again = !!(x.ref && seenRef[x.ref]);
|
|
5125
|
+
if (x.ref) seenRef[x.ref] = 1;
|
|
5126
|
+
return extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" }, { quietHelp: again });
|
|
4978
5127
|
});
|
|
4979
5128
|
if (g.state === "warn") {
|
|
4980
5129
|
out.push(orgNoteBox(githubNotConnectedNodes(g), cards));
|
|
@@ -5037,10 +5186,18 @@ ${deriveCapTile.toString()}
|
|
|
5037
5186
|
var repos = o.repos != null ? o.repos : g.repos != null ? g.repos : g.items.length;
|
|
5038
5187
|
var unread = o.unreadable != null ? o.unreadable : g.unread != null ? g.unread : g.items.length;
|
|
5039
5188
|
var installed = o.appInstalled;
|
|
5040
|
-
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " はまだ接続されていません" }));
|
|
5041
5189
|
// **読める件数を出す。** 総数だけ出すと、同じ画面の集計と食い違う(#101 で3度直した形)
|
|
5042
5190
|
var head = repos + "リポジトリ中 " + unread + "件が読めていません。";
|
|
5191
|
+
// 🔴 **見出しは 「installed」 の分岐の中で出す(#346)。** ここで1つに畳んでいたので、
|
|
5192
|
+
// App 導入済みの組織にも「まだ接続されていません」と出て、**同じ帯の本文と矛盾**した
|
|
5193
|
+
// (実機でオーナーが踏み、利用者が「組織の接続からやり直し」と誤解した)
|
|
5043
5194
|
if (installed === true) {
|
|
5195
|
+
// **見出しでは「読み取り対象に選ばれていません」と言い切らない。** 下のコメントの
|
|
5196
|
+
// とおり②未選択と③移動・改名・削除は分かれないので、見出しは事実だけを言う
|
|
5197
|
+
out.push(el("div", {
|
|
5198
|
+
class: "ds-orgnote-title",
|
|
5199
|
+
text: "⚠️ " + g.title + " は App 導入済みですが、" + unread + "件のリポジトリを読み取れていません",
|
|
5200
|
+
}));
|
|
5044
5201
|
// App は入っている。**インストールをもう一度促さないこと**——済んでいる操作を促すのは
|
|
5045
5202
|
// 「設定する必要が無いものに設定を促す」偽の警告と同じで、しかも直らない
|
|
5046
5203
|
//
|
|
@@ -5049,15 +5206,19 @@ ${deriveCapTile.toString()}
|
|
|
5049
5206
|
// 「対象に選ばれていません」と言い切ると、③の人は一覧に無いリポジトリを
|
|
5050
5207
|
// 探し続けることになる(押しても直らない導線と同じ形)。逃げ道を1文で示す。
|
|
5051
5208
|
//
|
|
5052
|
-
//
|
|
5053
|
-
//
|
|
5054
|
-
//
|
|
5055
|
-
//
|
|
5209
|
+
// **設定画面の宛先は App 個別ページに統一した(#346)。** 以前の
|
|
5210
|
+
// 「/settings/installations」 は**押した本人が入れた App しか出ない**個人の一覧で、
|
|
5211
|
+
// App を入れたのが別のメンバーなら着いた先が空だった(そのための断り書きも要らなくなった)。
|
|
5212
|
+
// いまの宛先と、そこに着ける人の条件は 「appSettingsLink」 の項にある。
|
|
5213
|
+
//
|
|
5214
|
+
// **誰がその操作をできるかまで書く(2026-09-02 の実測)。** リポジトリの admin なら
|
|
5215
|
+
// Configure から自分で追加でき、admin でなければ GitHub が 「request」 を出して
|
|
5216
|
+
// 組織オーナーの承認待ちになる。片方に断定せず両方の道を並べる
|
|
5056
5217
|
out.push(el("p", {
|
|
5057
5218
|
class: "ds-group-note",
|
|
5058
|
-
text: head + "GitHub App
|
|
5059
|
-
+ "
|
|
5060
|
-
+
|
|
5219
|
+
text: head + "GitHub App はこの組織に入っていますが、これらのリポジトリが読み取りの対象に選ばれていない(または移動・改名・削除された)可能性があります。"
|
|
5220
|
+
+ "「インストール設定を開く」から " + g.title + " の Configure を開き、Repository access に対象のリポジトリを追加してください。"
|
|
5221
|
+
+ GITHUB_ADMIN_HELP
|
|
5061
5222
|
+ "リポジトリ自体が一覧に見当たらない場合は、移動・改名・削除されている可能性があるので、登録から外してください。"
|
|
5062
5223
|
+ "お客様の組織では、組織のオーナーに承認を依頼してください。",
|
|
5063
5224
|
}));
|
|
@@ -5065,6 +5226,7 @@ ${deriveCapTile.toString()}
|
|
|
5065
5226
|
return out;
|
|
5066
5227
|
}
|
|
5067
5228
|
if (installed === false) {
|
|
5229
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " はまだ接続されていません" }));
|
|
5068
5230
|
out.push(el("p", {
|
|
5069
5231
|
class: "ds-group-note",
|
|
5070
5232
|
text: head + "この組織に Cortex の GitHub App を入れると読めるようになります(Issues の読み取りだけ)。"
|
|
@@ -5075,7 +5237,13 @@ ${deriveCapTile.toString()}
|
|
|
5075
5237
|
}
|
|
5076
5238
|
// 「null」(確かめられなかった)と、この値を知らない古い fleet-status.json。
|
|
5077
5239
|
// **導線を出さない。** どちらが原因か分からないので、押させると外れたほうを
|
|
5078
|
-
//
|
|
5240
|
+
// 押させることになる(済んでいるインストールをもう一度、あるいは効かない設定画面へ)。
|
|
5241
|
+
//
|
|
5242
|
+
// 見出しは件数を言わない(件数は本文だけが持つ・見出しと二重に言わない)。
|
|
5243
|
+
// 「githubUnsureNodes」 の「接続を確認できませんでした」(=その夜、確かめる処理自体が
|
|
5244
|
+
// 走らなかった)とは意味が違う——こちらは**読めなかった事実はあるが、App の導入状況が
|
|
5245
|
+
// 分からない**ので、語を分ける
|
|
5246
|
+
out.push(el("div", { class: "ds-orgnote-title", text: "⚠️ " + g.title + " のリポジトリが読めていません(原因は確かめられていません)" }));
|
|
5079
5247
|
out.push(el("p", {
|
|
5080
5248
|
class: "ds-group-note",
|
|
5081
5249
|
text: head + "原因(GitHub App が入っていない/入っているがこれらのリポジトリが選ばれていない)は、"
|
|
@@ -5315,29 +5483,37 @@ ${deriveCapTile.toString()}
|
|
|
5315
5483
|
/**
|
|
5316
5484
|
* すでに入っている App の**インストール設定**(対象リポジトリの追加)へ行くリンク。
|
|
5317
5485
|
*
|
|
5318
|
-
*
|
|
5319
|
-
*
|
|
5320
|
-
*
|
|
5321
|
-
*
|
|
5322
|
-
*
|
|
5323
|
-
*
|
|
5324
|
-
*
|
|
5325
|
-
*
|
|
5326
|
-
*
|
|
5327
|
-
*
|
|
5328
|
-
*
|
|
5329
|
-
*
|
|
5330
|
-
*
|
|
5331
|
-
*
|
|
5332
|
-
*
|
|
5333
|
-
*
|
|
5486
|
+
* **宛先は App 個別ページ(「GITHUB_APP_INSTALL_URL」 と同じ定数・#346)。**
|
|
5487
|
+
* 「infra/lambda/shared/github-gate.mjs」 の 「githubConfigureUrl」 とも同じ宛先なので、
|
|
5488
|
+
* **押す前(この帯・行の補足)と押した後(確認結果の文中)でリンク先が割れない**。
|
|
5489
|
+
* 関数は残してある——呼び手が「入れる」ではなく「設定を開く」意図で呼ぶ場所だから。
|
|
5490
|
+
*
|
|
5491
|
+
* ## なぜ他の2つの宛先を使わないか
|
|
5492
|
+
*
|
|
5493
|
+
* - 個人の一覧 「github.com/settings/installations」 は**「個人アカウントに入れた App」の
|
|
5494
|
+
* 画面**で、組織のインストールは別立て
|
|
5495
|
+
* (https://docs.github.com/en/apps/using-github-apps/reviewing-and-modifying-installed-github-apps )。
|
|
5496
|
+
* 押した本人が入れた App しか出ないので、**別の人が入れた組織では空**になる。
|
|
5497
|
+
* 0.12.21 まではこちらを指しており、着いた先が空の人が詰まった(#346)
|
|
5498
|
+
* - 組織の設定ページ 「github.com/organizations/<組織>/settings/installations」 は
|
|
5499
|
+
* **owner 以外 404**(2026-09-01 オーナーが踏んだ実害)。読み取り対象の組織の
|
|
5500
|
+
* オーナーが Cortex の利用者とは限らない(お客様の組織がまさにそれ)
|
|
5501
|
+
*
|
|
5502
|
+
* ⚠ **App 個別ページが「導入済みの組織で Configure に着く」ことは、GitHub の文書に
|
|
5503
|
+
* 記述が無い。根拠は 2026-09-02 のオーナーの実測**(cortex-test と GitHub の Configure
|
|
5504
|
+
* 画面で、admin のリポジトリは即追加・admin でないリポジトリには 「request」 タグ)。
|
|
5505
|
+
* 文書化されている一次ソースは次の2つで、**着いた先の画面そのものは書かれていない**:
|
|
5506
|
+
* - インストール URL と repository admin の設置条件
|
|
5507
|
+
* https://docs.github.com/en/apps/using-github-apps/installing-a-github-app-from-a-third-party
|
|
5508
|
+
* - 非オーナーの申請は組織オーナーへ通知される
|
|
5509
|
+
* https://docs.github.com/en/apps/using-github-apps/requesting-a-github-app-from-your-organization-owner
|
|
5510
|
+
* **GitHub 側の画面が変わったらここが最初に嘘になる。** 宛先を変えるときは
|
|
5511
|
+
* 「githubConfigureUrl」(サーバ)と揃えること。
|
|
5334
5512
|
*/
|
|
5335
|
-
var GITHUB_APP_SETTINGS_URL = "https://github.com/settings/installations";
|
|
5336
|
-
|
|
5337
5513
|
function appSettingsLink(label) {
|
|
5338
5514
|
return el("a", {
|
|
5339
5515
|
class: "ds-owner-install",
|
|
5340
|
-
href:
|
|
5516
|
+
href: GITHUB_APP_INSTALL_URL,
|
|
5341
5517
|
target: "_blank", rel: "noopener",
|
|
5342
5518
|
text: label || "インストール設定を開く →",
|
|
5343
5519
|
});
|
|
@@ -8171,9 +8347,13 @@ ${deriveCapTile.toString()}
|
|
|
8171
8347
|
* 「<a>」 の組み立てだけで作る(この画面の他の表示と同じ流儀)。
|
|
8172
8348
|
*
|
|
8173
8349
|
* **URL 以外は素のまま出す。** 言い換えると検索できないので、サーバの文言を削らない。
|
|
8350
|
+
*
|
|
8351
|
+
* **行に置くときは 「ds-gate-help」 を付ける**(第2引数)。「.ds-card」 は横並びの flex で、
|
|
8352
|
+
* 自分の行を占めるのは 「flex: 1 1 100%」 の 「.ds-gate-help」 だけ。押した結果で置き換える
|
|
8353
|
+
* とき 「probeSection」 が 「.ds-gate-help」 をまとめて隠すので、この class が要る。
|
|
8174
8354
|
*/
|
|
8175
|
-
function neededNode(text) {
|
|
8176
|
-
var p = el("p", { class: "ds-probe-help" });
|
|
8355
|
+
function neededNode(text, cls) {
|
|
8356
|
+
var p = el("p", { class: cls || "ds-probe-help" });
|
|
8177
8357
|
splitNeededParts(text).forEach(function (part) {
|
|
8178
8358
|
if (part.url) p.appendChild(el("a", { href: part.text, target: "_blank", rel: "noopener", text: part.text }));
|
|
8179
8359
|
else p.appendChild(document.createTextNode(part.text));
|
|
@@ -8188,10 +8368,20 @@ ${deriveCapTile.toString()}
|
|
|
8188
8368
|
* 「App が入っていない」と「App は入っているがこのリポジトリが選ばれていない」の
|
|
8189
8369
|
* 言い分けも、設定ページの URL もあちらが決める——**画面で分岐を書き足さない**
|
|
8190
8370
|
* (書いた瞬間に判定が2箇所になり、夜間の実測とボタンでずれる)。
|
|
8371
|
+
* **ただし admin の定型補足だけは画面の表(DS_GATE_RESTING_HELP)から引く**(#346)——
|
|
8372
|
+
* 「誰がその操作をできるか」はサーバの2つの書き手が別々に文を作るので、片方だけ古くなる。
|
|
8191
8373
|
*/
|
|
8192
8374
|
function githubProbeHelpNodes(r) {
|
|
8193
8375
|
if (r.gate === "ok") return [];
|
|
8194
|
-
if (r.needed)
|
|
8376
|
+
if (r.needed) {
|
|
8377
|
+
var nodes = [neededNode(r.needed)];
|
|
8378
|
+
// 定型の補足(admin なら自分で・でなければ admin に依頼)は**行と同じ表から引く**。
|
|
8379
|
+
// 言い分けと URL はサーバ(needed)、定型の補足だけ画面の表——ここに文を直書きすると
|
|
8380
|
+
// 休止状態の行と押した結果で言うことが割れる。理由と URL は needed が既に持っているので、
|
|
8381
|
+
// 表のパーツ全部ではなく admin の4文(GITHUB_ADMIN_HELP)だけを添える
|
|
8382
|
+
if (restingHelpPartsOf("github", r.gate)) nodes.push(restingHelpNode([GITHUB_ADMIN_HELP], "ds-probe-help"));
|
|
8383
|
+
return nodes;
|
|
8384
|
+
}
|
|
8195
8385
|
// needed が無いのは gate に落とし込めなかったとき。**生のエラーは隠さない**
|
|
8196
8386
|
return [el("p", { class: "ds-probe-help", text: "GitHub の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。" })];
|
|
8197
8387
|
}
|
|
@@ -8440,9 +8630,12 @@ ${deriveCapTile.toString()}
|
|
|
8440
8630
|
at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
|
|
8441
8631
|
probeHelpNodes(s, j).forEach(function (n) { body.appendChild(n); });
|
|
8442
8632
|
// **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が
|
|
8443
|
-
// 下に残ると、同じ行に2
|
|
8444
|
-
|
|
8445
|
-
|
|
8633
|
+
// 下に残ると、同じ行に2つのことが書いてある状態になる。
|
|
8634
|
+
// **1つ目だけでなく全部隠す(#346)。** 理由文(.ds-gate-reason)・定型補足
|
|
8635
|
+
// (.ds-gate-resting)・DS_GATE の help のどれが付いていても、押した結果と
|
|
8636
|
+
// 食い違う案内を残さない
|
|
8637
|
+
var stale = card.querySelectorAll(".ds-gate-help");
|
|
8638
|
+
for (var si = 0; si < stale.length; si++) stale[si].hidden = true;
|
|
8446
8639
|
// 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)。
|
|
8447
8640
|
// **GitHub は揃えない。** 行の名前は owner/repo そのもので、書き換える口
|
|
8448
8641
|
//(op=config の refresh-channel-name)は channels.json 専用
|
|
@@ -10233,7 +10426,11 @@ ${deriveCapTile.toString()}
|
|
|
10233
10426
|
}
|
|
10234
10427
|
|
|
10235
10428
|
|
|
10236
|
-
|
|
10429
|
+
/**
|
|
10430
|
+
* 接続先1件の行。「opts.quietHelp」 が真なら休止状態の補足(#346)を出さない
|
|
10431
|
+
* ——同じリポジトリの2本目の行(Discussions)用で、同じ長文が2行に並ぶのを避ける
|
|
10432
|
+
*/
|
|
10433
|
+
function extSourceCard(s, meta, opts) {
|
|
10237
10434
|
var badges = el("div", { class: "ds-badges" });
|
|
10238
10435
|
// **Gold 対象かのバッジは持たない。** 一覧に並ぶ Slack チャンネルは例外なく全て対象で、
|
|
10239
10436
|
// 行ごとに「対象です」と書いても情報が増えない。
|
|
@@ -10413,6 +10610,20 @@ ${deriveCapTile.toString()}
|
|
|
10413
10610
|
// **ボタンより後ろに置く。** カードは横並びの flex なので、間に挟むと
|
|
10414
10611
|
// 「登録を解除」が案内文の下の行へ落ちて、既存案件の行の形が変わる
|
|
10415
10612
|
if (gate.help) card.appendChild(el("p", { class: "ds-gate-help", text: gate.help }));
|
|
10613
|
+
// **最後に測った理由と直し方を、押す前から出す(#346)。** 「アクセス不可 8時間前に確認」
|
|
10614
|
+
// だけでは、押さない人(初見の PM 等)が止まる。表にエントリがある状態は定型の補足
|
|
10615
|
+
// (リンク込み)だけ、無い状態は needed → detail のフォールバック。どちらも ds-gate-help
|
|
10616
|
+
// を付ける——押した結果で置き換えるとき probeSection がまとめて隠す。
|
|
10617
|
+
// **同じリポジトリの2本目の行(Discussions)には出さない**(opts.quietHelp)。
|
|
10618
|
+
// 同じ長文が2行に並ぶと一覧が案内で埋まる。URL のリンク化は neededNode(innerHTML は使わない)
|
|
10619
|
+
if (!(opts && opts.quietHelp) && showsRestingReason(s)) {
|
|
10620
|
+
var restingParts = restingHelpPartsOf(s.type, s.gate);
|
|
10621
|
+
if (restingParts) card.appendChild(restingHelpNode(restingParts, "ds-gate-help ds-gate-resting"));
|
|
10622
|
+
else {
|
|
10623
|
+
var reason = stateReasonOf(s);
|
|
10624
|
+
if (reason) card.appendChild(neededNode(reason, "ds-gate-help ds-gate-reason"));
|
|
10625
|
+
}
|
|
10626
|
+
}
|
|
10416
10627
|
// 「接続を確認」(2026-08-19)。**押せる人を絞らない**——読むだけで設定を変えないので、
|
|
10417
10628
|
// 気づいた人がその場で確かめられるほうがよい(オーナー判断)。書き込みになる
|
|
10418
10629
|
// 「表示名を更新する」は、押した結果の中から別のボタンとして出る(probeSection)。
|
package/package.json
CHANGED