@takagaki/cortex-decisions-viewer 0.12.20 → 0.12.21
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 +49 -4
- 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をそのまま埋め込む。**
|
package/package.json
CHANGED