@squadbase/vantage-template 0.0.1 → 0.0.2
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 +12 -6
- package/base-template/.squadbase/skills/vantage-add-feature/SKILL.md +15 -6
- package/base-template/.squadbase/skills/vantage-app/SKILL.md +12 -7
- package/base-template/AGENTS.md +139 -7
- package/base-template/package.json +3 -2
- package/dist/index.js +149 -61
- package/package.json +6 -3
- package/ui-templates/CLAUDE.md +32 -21
- package/ui-templates/blank/preview-square.png +0 -0
- package/ui-templates/blank/preview-wide.png +0 -0
- package/ui-templates/blank-ja/preview-square.png +0 -0
- package/ui-templates/blank-ja/preview-wide.png +0 -0
- package/ui-templates/chart-grid/preview-square.png +0 -0
- package/ui-templates/chart-grid/preview-wide.png +0 -0
- package/ui-templates/chart-grid-ja/preview-square.png +0 -0
- package/ui-templates/chart-grid-ja/preview-wide.png +0 -0
- package/ui-templates/funnel/preview-square.png +0 -0
- package/ui-templates/funnel/preview-wide.png +0 -0
- package/ui-templates/funnel-ja/preview-square.png +0 -0
- package/ui-templates/funnel-ja/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-advanced/preview-square.png +0 -0
- package/ui-templates/kpi-chart-advanced/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-advanced-ja/preview-square.png +0 -0
- package/ui-templates/kpi-chart-advanced-ja/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-simple/manifest.json +1 -1
- package/ui-templates/kpi-chart-simple/preview-square.png +0 -0
- package/ui-templates/kpi-chart-simple/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-simple/server/kpi-summary.ts +1 -1
- package/ui-templates/kpi-chart-simple-ja/manifest.json +1 -1
- package/ui-templates/kpi-chart-simple-ja/preview-square.png +0 -0
- package/ui-templates/kpi-chart-simple-ja/preview-wide.png +0 -0
- package/ui-templates/kpi-chart-simple-ja/server/kpi-summary.ts +1 -1
- package/ui-templates/table-focus/preview-square.png +0 -0
- package/ui-templates/table-focus/preview-wide.png +0 -0
- package/ui-templates/table-focus-ja/preview-square.png +0 -0
- package/ui-templates/table-focus-ja/preview-wide.png +0 -0
- /package/base-template/{_404.tsx → src/_404.tsx} +0 -0
- /package/base-template/{_error.tsx → src/_error.tsx} +0 -0
- /package/base-template/{_layout.tsx → src/_layout.tsx} +0 -0
- /package/base-template/{components → src/components}/user-card.tsx +0 -0
- /package/base-template/{index.tsx → src/index.tsx} +0 -0
- /package/base-template/{styles.css → src/styles.css} +0 -0
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ npx @squadbase/vantage-template <command> [options]
|
|
|
14
14
|
|
|
15
15
|
#### `init`
|
|
16
16
|
|
|
17
|
-
Initialize a new Vantage project in the current directory. Copies the base template — `index.tsx`, `_layout.tsx`, `_404.tsx`, `_error.tsx`, `
|
|
17
|
+
Initialize a new Vantage project in the current directory. Copies the base template — `src/index.tsx`, `src/_layout.tsx`, `src/_404.tsx`, `src/_error.tsx`, `src/styles.css`, `server/api/`, `tsconfig.json`, `squadbase.yml`, plus the agent guidance in `AGENTS.md` and `.squadbase/skills/` — and installs dependencies.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
npx @squadbase/vantage-template init
|
|
@@ -26,17 +26,23 @@ npx @squadbase/vantage-template init --chart sunset # Apply a chart pr
|
|
|
26
26
|
After initialization:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
npm run dev # vantage dev — dev server + API on :5173
|
|
29
|
+
npm run dev # vantage dev --no-overlay — dev server + API on :5173
|
|
30
30
|
npm run build # vantage build → dist/
|
|
31
31
|
npm run check # vantage check — static diagnostics
|
|
32
32
|
npm run routes # vantage routes — page/API route map
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
There is no `vite.config.ts`, no `main.tsx`, and no route table: adding a file adds a route (`index.tsx` → `/`, `sales/[id].tsx` → `/sales/:id`), and `_layout.tsx` builds the nav from `useRoutes()`.
|
|
35
|
+
There is no `vite.config.ts`, no `main.tsx`, and no route table: adding a file under `src/` adds a route (`src/index.tsx` → `/`, `src/sales/[id].tsx` → `/sales/:id`), and `src/_layout.tsx` builds the nav from `useRoutes()`.
|
|
36
|
+
|
|
37
|
+
Since `@squadbase/vantage` v0.3.0, `src/` is the page-scan root and does not appear in URLs. `server/` and `public/` stay at the project root — `src/server/` is never scanned.
|
|
38
|
+
|
|
39
|
+
Since `@squadbase/vantage` v0.5.0, the dev terminal receives only `console.warn` / `console.error` and uncaught errors (with source-mapped positions and a code frame) — `console.log` is not forwarded. The generated `dev` script passes `--no-overlay`, keeping errors in the terminal instead of behind the full-screen browser overlay; remove the flag (or pass `--overlay`) to restore it.
|
|
36
40
|
|
|
37
41
|
#### `add <template-name>`
|
|
38
42
|
|
|
39
|
-
Apply a UI-pattern template to an existing Vantage project. The entry page lands on `index.tsx`, and template-owned files go under `components/<slug>/` (plus `server/api/` for API-backed templates).
|
|
43
|
+
Apply a UI-pattern template to an existing Vantage project. The entry page lands on `src/index.tsx`, and template-owned files go under `src/components/<slug>/` (plus `server/api/` at the project root for API-backed templates).
|
|
44
|
+
|
|
45
|
+
Both layouts are supported: Vantage scans `src/` when that directory exists and the project root otherwise, and `add` follows whichever one the project already uses — a project keeping its pages at the root gets `index.tsx` and `components/<slug>/` there instead, with relative imports that cross the boundary rewritten to match. `server/` and `public/` always stay at the project root.
|
|
40
46
|
|
|
41
47
|
```bash
|
|
42
48
|
npx @squadbase/vantage-template add kpi-chart-simple
|
|
@@ -44,11 +50,11 @@ npx @squadbase/vantage-template add funnel --dry-run # Preview without writin
|
|
|
44
50
|
npx @squadbase/vantage-template add funnel --force # Overwrite existing files
|
|
45
51
|
```
|
|
46
52
|
|
|
47
|
-
`add` replaces
|
|
53
|
+
`add` replaces the entry page, so it warns before overwriting it, and it lists leftover files from a previously applied template (it never deletes them — you may have edited them). A project with pages on *both* sides — `src/` present but `index.tsx` / `_layout.tsx` / `styles.css` still at the root — is refused with the list of files to move into `src/` first: Vantage ignores the root ones, so `vantage check` already fails there with `SRC_DIR_SPLIT`.
|
|
48
54
|
|
|
49
55
|
#### `chart <preset-name>`
|
|
50
56
|
|
|
51
|
-
Switch the chart color preset. Rewrites a marked block of `--chart-1`–`--chart-5` inside the project's `styles.css
|
|
57
|
+
Switch the chart color preset. Rewrites a marked block of `--chart-1`–`--chart-5` inside the project's `styles.css` (under `src/` when that layout is in use), leaving any other overrides in that file intact. Re-running replaces the block rather than stacking.
|
|
52
58
|
|
|
53
59
|
```bash
|
|
54
60
|
npx @squadbase/vantage-template chart ocean
|
|
@@ -16,8 +16,8 @@ CLI は雛形を出すだけで、規約の遵守はこちらの責任。**追
|
|
|
16
16
|
```bash
|
|
17
17
|
vantage add page <name> # ルートページ(.tsx)
|
|
18
18
|
vantage add api <name> # server/api/**.ts の API モジュール
|
|
19
|
-
vantage add ui <name> # registry から UI ソースを components/ui/ にコピー
|
|
20
|
-
vantage add block <name> # registry からブロックを components/blocks/ にコピー
|
|
19
|
+
vantage add ui <name> # registry から UI ソースを components/ui/ にコピー(src/ があればその中)
|
|
20
|
+
vantage add block <name> # registry からブロックを components/blocks/ にコピー(同上)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
`--force` で既存ファイルを上書き。`add` の雛形は import に `@squadbase/vantage` を焼き込む。
|
|
@@ -25,12 +25,18 @@ vantage add block <name> # registry からブロックを components/blocks/
|
|
|
25
25
|
## ページを追加する
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
vantage add page monthly-analysis # →
|
|
29
|
-
vantage add page sales/index # →
|
|
28
|
+
vantage add page monthly-analysis # → monthly-analysis.tsx → ルート /monthly-analysis
|
|
29
|
+
vantage add page sales/index # → sales/index.tsx → ルート /sales
|
|
30
30
|
vantage add page "sales/[customerId]" # → 動的ルート /sales/:customerId
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
**引数はページルートからの相対パス**(= ルートそのもの)。`add` は置き場所を自分で判断する ―
|
|
34
|
+
プロジェクトに `src/` があれば `src/monthly-analysis.tsx`、無ければ直下に書く。手でファイルを
|
|
35
|
+
作るときも同じ規則で、**混ぜてはならない**(`src/` があるのに直下に置くと `SRC_DIR_SPLIT`
|
|
36
|
+
エラー。詳細は AGENTS.md の「ディレクトリ構造」)。
|
|
37
|
+
|
|
38
|
+
出力される雛形は `definePage` + デフォルトエクスポートのコンポーネント。ファイル名の規約
|
|
39
|
+
(ページルートからの相対パス):
|
|
34
40
|
|
|
35
41
|
| ファイル | ルート | 用途 |
|
|
36
42
|
| --- | --- | --- |
|
|
@@ -51,6 +57,9 @@ vantage add page "sales/[customerId]" # → 動的ルート /sales/:customerI
|
|
|
51
57
|
- 表示ルート `/foo/:id`
|
|
52
58
|
- リンク `to="/foo/$id"` + `params={{ id }}`、取り出しは `const { id } = useParams()`
|
|
53
59
|
4. `components/`・`hooks/`・`lib/`・`server/`・`public/` はページにならない(ルート走査対象外)。
|
|
60
|
+
**それ以外のディレクトリ名はそのまま URL のセグメントになる** ― `pages/`・`app/`・`utils/`
|
|
61
|
+
のような足場ディレクトリを切ると `/pages/report` のような URL ができる
|
|
62
|
+
(`SUSPICIOUS_ROUTE_DIR` 警告)。ページでないモジュールは上の 3 つに置く。
|
|
54
63
|
|
|
55
64
|
## API を追加する(fullstack)
|
|
56
65
|
|
|
@@ -130,7 +139,7 @@ vantage add block sales-overview # → components/blocks/sales-overview.tsx
|
|
|
130
139
|
```bash
|
|
131
140
|
vantage routes # 追加したページ/API が意図した URL に出ているか
|
|
132
141
|
vantage check # 規約違反が無いか(exit 1 ならエラーあり)
|
|
133
|
-
vantage dev # 実際に動くか(
|
|
142
|
+
vantage dev # 実際に動くか(warn/error と未捕捉エラーは開発ターミナルへ転送される)
|
|
134
143
|
```
|
|
135
144
|
|
|
136
145
|
`vantage routes` は `--pages` / `--apis` で片側だけに絞れる。`--detail` を足すと各ルートの静的な
|
|
@@ -19,14 +19,17 @@ description: Vantage(@squadbase/vantage)ダッシュボードアプリを一か
|
|
|
19
19
|
3. **データを繋ぐ** — 外部 API か、自前の `server/api` か
|
|
20
20
|
4. **仕上げ** — `check` → `build` → `preview`
|
|
21
21
|
|
|
22
|
-
**各段階の終わりに `vantage check` を通す。** 静的診断(
|
|
23
|
-
API export・env 誤用)はユーザーコードを実行しないので速く、エラーがあれば
|
|
22
|
+
**各段階の終わりに `vantage check` を通す。** 静的診断(禁止ファイル・`src/` の分裂・ルート
|
|
23
|
+
衝突・境界違反・API export・env 誤用)はユーザーコードを実行しないので速く、エラーがあれば
|
|
24
|
+
exit 1 になる。
|
|
24
25
|
まとめて最後に回すと、原因の切り分けが難しくなる。
|
|
25
26
|
|
|
26
27
|
## Step 1 — 骨組み
|
|
27
28
|
|
|
28
29
|
新規なら `package.json` と `index.tsx` の 2 ファイルだけ。設定ファイルは**作らない**
|
|
29
|
-
(`vite.config.*` 等は `vantage check` がエラーにする)
|
|
30
|
+
(`vite.config.*` 等は `vantage check` がエラーにする)。ページを `src/` にまとめたいなら
|
|
31
|
+
`src/index.tsx` にする ― **`src/` があればページ走査はその中だけ**になり、直下に残した `.tsx`
|
|
32
|
+
は無視される(`SRC_DIR_SPLIT` エラー)。どちらか一方に寄せること。
|
|
30
33
|
|
|
31
34
|
```json
|
|
32
35
|
{
|
|
@@ -41,7 +44,7 @@ API export・env 誤用)はユーザーコードを実行しないので速く
|
|
|
41
44
|
"routes": "vantage routes"
|
|
42
45
|
},
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"@squadbase/vantage": "^0.
|
|
47
|
+
"@squadbase/vantage": "^0.5.0",
|
|
45
48
|
"react": "^19.2.7",
|
|
46
49
|
"react-dom": "^19.2.7"
|
|
47
50
|
}
|
|
@@ -54,8 +57,9 @@ API export・env 誤用)はユーザーコードを実行しないので速く
|
|
|
54
57
|
**既存アプリに合流したときは、作る前に現状を読む:**
|
|
55
58
|
|
|
56
59
|
```bash
|
|
57
|
-
vantage routes # 既にあるページと API の URL マップ
|
|
60
|
+
vantage routes # 既にあるページと API の URL マップ(file 列がページの置き場所)
|
|
58
61
|
vantage check # いま壊れていないか(これから出すエラーと切り分ける)
|
|
62
|
+
ls src/ # あればページは src/ の中だけ。無ければプロジェクトルート直下
|
|
59
63
|
ls server/ # あれば fullstack モード。無ければ SPA
|
|
60
64
|
vantage add skill # 配置済みの skill(このファイルの仲間)と、その場所
|
|
61
65
|
```
|
|
@@ -112,8 +116,9 @@ pnpm preview # 本番ビルドをローカルで動かす
|
|
|
112
116
|
|
|
113
117
|
## 詰まったら
|
|
114
118
|
|
|
115
|
-
- ブラウザの `console
|
|
116
|
-
|
|
119
|
+
- ブラウザの `console.warn` / `console.error` とランタイムエラーは、**開発ターミナルへ転送
|
|
120
|
+
される**(`[console.warn] …` / `[Unhandled error] …` + コードフレーム)。devtools を開かずに
|
|
121
|
+
読める。`console.log` は転送されないので、**ターミナルで見たいログは `console.warn`** で出す。
|
|
117
122
|
- `vantage check --json` / `vantage routes --json` は機械可読出力。
|
|
118
123
|
- props や規約を確かめたいときは `vantage docs <name>`、名前が思い出せないときは
|
|
119
124
|
`vantage search <やりたいこと>`。**推測で props を書かない。**
|
package/base-template/AGENTS.md
CHANGED
|
@@ -22,6 +22,9 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
22
22
|
|
|
23
23
|
## 不変条件(破ると静かに壊れる)
|
|
24
24
|
|
|
25
|
+
- **ページは `src/` の中か、プロジェクトルート直下か、どちらか一方。** `src/` があれば走査は
|
|
26
|
+
`src/` だけになり、ルート直下に残した `.tsx` は無視される(`SRC_DIR_SPLIT` エラー)。
|
|
27
|
+
新しいファイルは既にある側に置く(→「ディレクトリ構造」)。
|
|
25
28
|
- **設定ファイルを作らない。** `vite.config.*`・`tailwind.config.*`・`postcss.config.*`・
|
|
26
29
|
`components.json`・`vantage.config.*` はすべて禁止。存在すると `vantage check` がエラーにする
|
|
27
30
|
(`FORBIDDEN_FILE`)。テーマ調整は `styles.css` のトークンで行う。
|
|
@@ -39,6 +42,43 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
39
42
|
ため、変数・関数呼び出し・テンプレート補間は使わない。`page` エクスポートはランタイムでは
|
|
40
43
|
読まれない。
|
|
41
44
|
|
|
45
|
+
## ディレクトリ構造(ページをどこに置くか)
|
|
46
|
+
|
|
47
|
+
**ページの置き場所は 2 通りあり、`src/` があるかどうかだけで決まる。混ぜてはならない。**
|
|
48
|
+
|
|
49
|
+
- `src/` が**ある** → ページは `src/` の中だけ。ルート直下に置いた `.tsx` はルートにならず、
|
|
50
|
+
`SRC_DIR_SPLIT` エラーになる。
|
|
51
|
+
- `src/` が**無い** → ページはプロジェクトルート直下。
|
|
52
|
+
|
|
53
|
+
**ファイルを足す前に `src/` の有無を確認し、既にある側に置く**(`ls` するだけでよい)。URL は
|
|
54
|
+
どちらでも同じで、`src/` は URL に現れない(`src/sales/index.tsx` → `/sales`)。
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
root/
|
|
58
|
+
├── src/ # ある場合、ページはこの中だけ(無ければ以下が root 直下)
|
|
59
|
+
│ ├── index.tsx # /
|
|
60
|
+
│ ├── monthly-analysis.tsx # /monthly-analysis
|
|
61
|
+
│ ├── sales/index.tsx # /sales
|
|
62
|
+
│ ├── _layout.tsx # ルートレイアウト
|
|
63
|
+
│ ├── components/ # ページ走査の対象外(URL にならない)
|
|
64
|
+
│ ├── hooks/ lib/ # 同上
|
|
65
|
+
│ └── styles.css # 任意のグローバル CSS
|
|
66
|
+
├── server/ # 常にプロジェクトルート直下。src/ の中ではない
|
|
67
|
+
│ ├── api/… # /api/*
|
|
68
|
+
│ └── utils.ts
|
|
69
|
+
├── public/ # 静的ファイル。常にプロジェクトルート直下
|
|
70
|
+
├── package.json # managed
|
|
71
|
+
└── tsconfig.json # managed
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- **`server/` と `public/` は常にプロジェクトルート直下。** `src/server/` は決してスキャン
|
|
75
|
+
されない(`SRC_DIR_SPLIT` エラー)。
|
|
76
|
+
- **`styles.css` はページと同じ側**に置く(`src/` があれば `src/styles.css`)。
|
|
77
|
+
- **ディレクトリ名はそのまま URL のセグメントになる。** `pages/`・`app/`・`utils/` のような
|
|
78
|
+
足場ディレクトリを切ると、その名前が URL に出る(`pages/report.tsx` → `/pages/report`)。
|
|
79
|
+
`SUSPICIOUS_ROUTE_DIR` 警告で知らされる。ページでないモジュールは `components/`・`hooks/`・
|
|
80
|
+
`lib/` に置く ― この 3 つ(と `server/`・`public/`)はどの階層でも走査されない。
|
|
81
|
+
|
|
42
82
|
## import サブパスの地図
|
|
43
83
|
|
|
44
84
|
| import 元 | 提供するもの |
|
|
@@ -82,7 +122,7 @@ UI キット・開発サーバー・API サーバー・ビルドはすべて Van
|
|
|
82
122
|
"routes": "vantage routes"
|
|
83
123
|
},
|
|
84
124
|
"dependencies": {
|
|
85
|
-
"@squadbase/vantage": "^0.
|
|
125
|
+
"@squadbase/vantage": "^0.5.0",
|
|
86
126
|
"react": "^19.2.7",
|
|
87
127
|
"react-dom": "^19.2.7"
|
|
88
128
|
}
|
|
@@ -108,12 +148,15 @@ pnpm check # 静的診断(エラーがあれば exit 1)
|
|
|
108
148
|
pnpm routes # ページ/API の URL マップ
|
|
109
149
|
```
|
|
110
150
|
|
|
111
|
-
`server/` ディレクトリが無いので、この構成は **SPA
|
|
151
|
+
`server/` ディレクトリが無いので、この構成は **SPA モード**。ページを直下に置く形だが、
|
|
152
|
+
`src/` を作ってその中に集めてもよい(テンプレートから始めた場合は最初から `src/` がある)。
|
|
153
|
+
どちらか一方に寄せること(→「ディレクトリ構造」)。
|
|
112
154
|
|
|
113
155
|
## ルーティング規約(ファイル名 → ルート)
|
|
114
156
|
|
|
115
|
-
|
|
116
|
-
|
|
157
|
+
ページルート(`src/` があればその中、無ければプロジェクトルート)からの相対パスが、そのまま
|
|
158
|
+
URL になる。各ページは**デフォルトエクスポートの React コンポーネントが必須**(無いと
|
|
159
|
+
`MISSING_DEFAULT_EXPORT`)。以下の「ファイル」列はページルートからの相対パス。
|
|
117
160
|
|
|
118
161
|
| ファイル | ルート |
|
|
119
162
|
| --- | --- |
|
|
@@ -125,7 +168,8 @@ pnpm routes # ページ/API の URL マップ
|
|
|
125
168
|
| `_404.tsx` | Not Found ページ |
|
|
126
169
|
| `_error.tsx` | ルートが throw したときのエラーページ |
|
|
127
170
|
|
|
128
|
-
`components/`・`hooks/`・`lib/`・`server/`・`public/`
|
|
171
|
+
`components/`・`hooks/`・`lib/`・`server/`・`public/` はどの階層でもルート走査の対象外
|
|
172
|
+
(ページにならない)。それ以外のディレクトリ名は URL のセグメントになる(→「ディレクトリ構造」)。
|
|
129
173
|
|
|
130
174
|
```tsx
|
|
131
175
|
import { definePage } from "@squadbase/vantage"
|
|
@@ -302,7 +346,7 @@ vantage search テーブル --limit 5 --json # 機械可読(slug + score + sn
|
|
|
302
346
|
## 検証・ビルド・プレビュー
|
|
303
347
|
|
|
304
348
|
```bash
|
|
305
|
-
pnpm check # 静的診断(
|
|
349
|
+
pnpm check # 静的診断(禁止ファイル・src/ の分裂・ルート衝突・境界・API export・env 誤用)
|
|
306
350
|
pnpm routes # ページ + API の URL マップ
|
|
307
351
|
pnpm build # dist/ に client(+ server)+ vantage-manifest.json
|
|
308
352
|
pnpm preview # 本番ビルドをローカル実行
|
|
@@ -312,7 +356,8 @@ pnpm preview # 本番ビルドをローカル実行
|
|
|
312
356
|
(手で書かない)。`vantage routes` は `--pages` / `--apis` で片側だけに絞れ、`--detail` を足すと
|
|
313
357
|
各ルートの静的な仕様(ページ: パスパラメータ + `definePage` のメタ / API: メソッド・query・
|
|
314
358
|
body・レスポンスの status と形)まで出る。`vantage check --json` / `vantage routes --json` は
|
|
315
|
-
|
|
359
|
+
エージェント向けの機械可読出力。dev では `console.warn` / `console.error` とランタイムエラーが開発ターミナルへ
|
|
360
|
+
転送される(`console.log` は転送されない)。エラーの全画面オーバーレイは `vantage dev --no-overlay` で消せる。
|
|
316
361
|
|
|
317
362
|
## さらに詳しく(同梱 Skill)
|
|
318
363
|
|
|
@@ -337,3 +382,90 @@ vantage add skill --all --dir .claude/skills # 置き場所を指定(既定は
|
|
|
337
382
|
`add skill` はプロジェクト内を走査してから動くので、**再実行しても二重に配置されない**。
|
|
338
383
|
配置済みのコピーを新しいバージョンに更新したいときだけ `--force` を付ける(見つかった場所を
|
|
339
384
|
そのまま書き換える)。
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
# テンプレート固有の規約(Squadbase テンプレート)
|
|
389
|
+
|
|
390
|
+
> ここから下は、Squadbase のテンプレートがフレームワーク正本に足した規約です。上の
|
|
391
|
+
> フレームワーク規約と矛盾する場合は**こちらが優先**します。
|
|
392
|
+
>
|
|
393
|
+
> `vantage upgrade` はこのファイルをフレームワーク同梱の正本で丸ごと上書きするため、
|
|
394
|
+
> このセクションは消えます。消えたら squadbase-templates リポジトリの `agents/vantage/`
|
|
395
|
+
> から貼り直してください(リポジトリ側では `npm run agents` が正本と結合して再生成します)。
|
|
396
|
+
|
|
397
|
+
## Squadbase の Connection / Query からデータを読む
|
|
398
|
+
|
|
399
|
+
**`server/api/**` のハンドラが実データを取りにいく唯一の方法**が `@squadbase/vantage-sdk`
|
|
400
|
+
(`@squadbase/vantage-sdk/server` の `connection`)。資格情報・接続先・SQL は Squadbase 側の
|
|
401
|
+
**Connection** と **Query** が持ち、ハンドラは「どの Query を、どの引数で回すか」だけを知る。
|
|
402
|
+
|
|
403
|
+
### 追加の不変条件
|
|
404
|
+
|
|
405
|
+
- **`server/` からデータを読むときは `connection` を使う。** ハンドラに SQL・接続文字列・
|
|
406
|
+
DB クライアント・上流サービスの URL や API キーを書かない。データソース用のライブラリを
|
|
407
|
+
`package.json` に足すのも不要(足す前に、その接続が Connection にできないか疑う)。
|
|
408
|
+
- **`connection` は `server/` の中でしか呼べない。** クライアントから import すると
|
|
409
|
+
`CLIENT_IMPORTS_SERVER` エラー(フレームワーク側の不変条件と同じ)。
|
|
410
|
+
- **CONNECTION_ID / QUERY_ID を推測で埋めない。** 値はアプリを動かす Squadbase プロジェクト側の
|
|
411
|
+
設定なので、分からなければユーザーに聞く。実データがまだ無い段階は `lib/mock-data.ts` の
|
|
412
|
+
モックで進め、id が確定したらハンドラだけ差し替える(クライアントの `useApiQuery` は変わらない)。
|
|
413
|
+
|
|
414
|
+
### 追加の import サブパス
|
|
415
|
+
|
|
416
|
+
| import 元 | 提供するもの |
|
|
417
|
+
| --- | --- |
|
|
418
|
+
| `@squadbase/vantage-sdk/server` | `connection`(Connection の宛先と認証を解決する。server/ 側でのみ使う) |
|
|
419
|
+
|
|
420
|
+
### 使い方
|
|
421
|
+
|
|
422
|
+
`connection(ctx, connectionId)` は**宛先(`baseUrl`)と認証ヘッダー(`headers`)を解決するだけ**で、
|
|
423
|
+
リクエストは自分で `fetch` する。
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
import type { ApiContext } from "@squadbase/vantage/server"
|
|
427
|
+
import { HttpError } from "@squadbase/vantage/server"
|
|
428
|
+
import { connection } from "@squadbase/vantage-sdk/server"
|
|
429
|
+
|
|
430
|
+
const CONNECTION_ID = "..."
|
|
431
|
+
const QUERY_ID = "..."
|
|
432
|
+
|
|
433
|
+
export async function GET(ctx: ApiContext) {
|
|
434
|
+
const { baseUrl, headers } = await connection(ctx, CONNECTION_ID)
|
|
435
|
+
|
|
436
|
+
const res = await fetch(`${baseUrl}/queries/${QUERY_ID}`, {
|
|
437
|
+
method: "POST",
|
|
438
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
439
|
+
body: JSON.stringify({ params: {} }),
|
|
440
|
+
})
|
|
441
|
+
if (!res.ok) throw new HttpError(502, `query execution failed: ${res.status}`)
|
|
442
|
+
|
|
443
|
+
const { columns, rows } = await res.json()
|
|
444
|
+
return Response.json({ columns, rows })
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
- **`ctx` をそのまま渡す。** `connection` が読むのは `ctx.request` と `ctx.env` だけなので、
|
|
449
|
+
`ApiContext` をそのまま渡せる(第 1 引数を自作しない)。
|
|
450
|
+
- **`baseUrl` はバージョン接頭辞込み・末尾スラッシュ無し。** そのままパスを繋ぐ。
|
|
451
|
+
- **`headers` は必ずスプレッドする**(`{ "Content-Type": "application/json", ...headers }`)。
|
|
452
|
+
- **Query は id だけで指す**: `POST {baseUrl}/queries/{queryId}`。Connection は Query 側から
|
|
453
|
+
解決されるので、**パスに connection id を入れない**(`connection` の引数として渡すだけ)。
|
|
454
|
+
- **プレビューでもデプロイ済みアプリでも同じコードが動く。** Squadbase の外で走らせると
|
|
455
|
+
`connection` は throw する。
|
|
456
|
+
|
|
457
|
+
### SQL Query と HTTP Query で body の形が違う
|
|
458
|
+
|
|
459
|
+
| Query 種別 | リクエスト | レスポンス |
|
|
460
|
+
| --- | --- | --- |
|
|
461
|
+
| SQL Query | 引数は `{ params: { … } }` に入れる | `{ columns: [{ name, type? }], rows: [{ … }] }` |
|
|
462
|
+
| HTTP Query | Query が宣言した形をそのまま(`params` で包まない)。クエリ文字列は URL に載せる | 上流サービスのレスポンスをそのまま通す(非 2xx も来るので `res.ok` を必ず見る) |
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// HTTP Query — クエリ文字列は URL、body は Query の宣言どおり
|
|
466
|
+
const res = await fetch(`${baseUrl}/queries/${QUERY_ID}?since=2026-01-01`, {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
469
|
+
body: JSON.stringify({ customerId: "c_123" }),
|
|
470
|
+
})
|
|
471
|
+
```
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
|
-
"dev": "vantage dev",
|
|
6
|
+
"dev": "vantage dev --no-overlay",
|
|
7
7
|
"build": "vantage build",
|
|
8
8
|
"start": "node dist/server/index.mjs",
|
|
9
9
|
"preview": "vantage preview",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@squadbase/react": "^0.0.1",
|
|
15
|
-
"@squadbase/vantage": "^0.
|
|
15
|
+
"@squadbase/vantage": "^0.5.0",
|
|
16
|
+
"@squadbase/vantage-sdk": "^0.1.0-dev.0",
|
|
16
17
|
"date-fns": "^4.1.0",
|
|
17
18
|
"lucide-react": "^0.468.0",
|
|
18
19
|
"react": "^19.2.7",
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
5
|
|
|
6
6
|
// src/commands/add.ts
|
|
7
|
-
import { existsSync as
|
|
8
|
-
import { join as
|
|
7
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
8
|
+
import { join as join5 } from "path";
|
|
9
9
|
|
|
10
10
|
// src/ai/customize.ts
|
|
11
11
|
import { readFileSync, writeFileSync } from "fs";
|
|
12
|
-
import { join } from "path";
|
|
12
|
+
import { join as join2 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/logger.ts
|
|
15
15
|
var colors = {
|
|
@@ -24,6 +24,35 @@ function log(color, message) {
|
|
|
24
24
|
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// src/project.ts
|
|
28
|
+
import { existsSync } from "fs";
|
|
29
|
+
import { join } from "path";
|
|
30
|
+
var ROOT_LAYOUT_MARKERS = ["index.tsx", "_layout.tsx", "_404.tsx", "_error.tsx", "styles.css"];
|
|
31
|
+
var AUTHORED_PAGE_PREFIX = "src/";
|
|
32
|
+
function resolvePagePrefix(projectRoot) {
|
|
33
|
+
if (existsSync(join(projectRoot, "src"))) return AUTHORED_PAGE_PREFIX;
|
|
34
|
+
return findRootLayoutFiles(projectRoot).length > 0 ? "" : AUTHORED_PAGE_PREFIX;
|
|
35
|
+
}
|
|
36
|
+
function resolvePageRoot(projectRoot) {
|
|
37
|
+
return join(projectRoot, resolvePagePrefix(projectRoot));
|
|
38
|
+
}
|
|
39
|
+
function resolveStylesPath(projectRoot) {
|
|
40
|
+
return join(resolvePageRoot(projectRoot), "styles.css");
|
|
41
|
+
}
|
|
42
|
+
function resolveDest(entry, pagePrefix) {
|
|
43
|
+
return entry.scope === "root" ? entry.dest : pagePrefix + entry.dest;
|
|
44
|
+
}
|
|
45
|
+
function authoredDest(entry) {
|
|
46
|
+
return resolveDest(entry, AUTHORED_PAGE_PREFIX);
|
|
47
|
+
}
|
|
48
|
+
function findRootLayoutFiles(projectRoot) {
|
|
49
|
+
return ROOT_LAYOUT_MARKERS.filter((name) => existsSync(join(projectRoot, name)));
|
|
50
|
+
}
|
|
51
|
+
function findSplitLayoutFiles(projectRoot) {
|
|
52
|
+
if (!existsSync(join(projectRoot, "src"))) return [];
|
|
53
|
+
return findRootLayoutFiles(projectRoot);
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
// src/ai/diff.ts
|
|
28
57
|
var ANSI = {
|
|
29
58
|
reset: "\x1B[0m",
|
|
@@ -300,12 +329,13 @@ function buildUserPrompt(intent, files) {
|
|
|
300
329
|
parts.push(`</FILES_TO_CUSTOMIZE>`);
|
|
301
330
|
return parts.join("\n");
|
|
302
331
|
}
|
|
303
|
-
function readInputFiles(projectRoot, templateDir, entries, fromTemplate) {
|
|
332
|
+
function readInputFiles(projectRoot, templateDir, entries, pagePrefix, fromTemplate) {
|
|
304
333
|
const files = /* @__PURE__ */ new Map();
|
|
305
334
|
for (const entry of entries) {
|
|
306
|
-
const
|
|
335
|
+
const dest = resolveDest(entry, pagePrefix);
|
|
336
|
+
const path = fromTemplate ? join2(templateDir, entry.src) : join2(projectRoot, dest);
|
|
307
337
|
try {
|
|
308
|
-
files.set(
|
|
338
|
+
files.set(dest, readFileSync(path, "utf-8"));
|
|
309
339
|
} catch (err) {
|
|
310
340
|
throw new Error(
|
|
311
341
|
`Failed to read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -400,13 +430,14 @@ ${snippet}`);
|
|
|
400
430
|
if (e.usage) parts.push(`Usage: ${JSON.stringify(e.usage)}`);
|
|
401
431
|
return parts.join("\n");
|
|
402
432
|
}
|
|
403
|
-
async function customizeWithAI(projectRoot, manifest, templateDir, options) {
|
|
433
|
+
async function customizeWithAI(projectRoot, manifest, templateDir, options, pagePrefix) {
|
|
404
434
|
const relabelFiles = manifest.files.filter((f) => f.relabel !== false);
|
|
405
|
-
const allowedPaths = relabelFiles.map((f) => f
|
|
435
|
+
const allowedPaths = relabelFiles.map((f) => resolveDest(f, pagePrefix));
|
|
406
436
|
const inputFiles = readInputFiles(
|
|
407
437
|
projectRoot,
|
|
408
438
|
templateDir,
|
|
409
439
|
relabelFiles,
|
|
440
|
+
pagePrefix,
|
|
410
441
|
options.dryRun
|
|
411
442
|
);
|
|
412
443
|
if (!options.json) {
|
|
@@ -515,7 +546,7 @@ ${edit.path} (+${edit.added} -${edit.removed})`);
|
|
|
515
546
|
}
|
|
516
547
|
} else {
|
|
517
548
|
for (const edit of edits) {
|
|
518
|
-
writeFileSync(
|
|
549
|
+
writeFileSync(join2(projectRoot, edit.path), edit.after, "utf-8");
|
|
519
550
|
if (!options.json) {
|
|
520
551
|
log("cyan", ` rewrote: ${edit.path} (+${edit.added} -${edit.removed})`);
|
|
521
552
|
}
|
|
@@ -584,34 +615,34 @@ function parseEditsString(raw) {
|
|
|
584
615
|
}
|
|
585
616
|
|
|
586
617
|
// src/apply.ts
|
|
587
|
-
import { copyFileSync, existsSync as
|
|
588
|
-
import { dirname as dirname2, join as
|
|
618
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
619
|
+
import { dirname as dirname2, join as join4, posix } from "path";
|
|
589
620
|
|
|
590
621
|
// src/manifest.ts
|
|
591
|
-
import { existsSync, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
592
|
-
import { dirname, join as
|
|
622
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
623
|
+
import { dirname, join as join3 } from "path";
|
|
593
624
|
import { fileURLToPath } from "url";
|
|
594
625
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
595
626
|
function getTemplatesDir(source = "ui-templates") {
|
|
596
|
-
return
|
|
627
|
+
return join3(__dirname, "..", source);
|
|
597
628
|
}
|
|
598
629
|
function getTemplateDir(templateName, source = "ui-templates") {
|
|
599
|
-
return
|
|
630
|
+
return join3(getTemplatesDir(source), templateName);
|
|
600
631
|
}
|
|
601
632
|
function listTemplateNames(source = "ui-templates") {
|
|
602
633
|
const templatesDir = getTemplatesDir(source);
|
|
603
|
-
if (!
|
|
634
|
+
if (!existsSync2(templatesDir)) {
|
|
604
635
|
return [];
|
|
605
636
|
}
|
|
606
637
|
return readdirSync(templatesDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
|
|
607
638
|
}
|
|
608
639
|
function loadManifest(templateName, source = "ui-templates") {
|
|
609
640
|
const templateDir = getTemplateDir(templateName, source);
|
|
610
|
-
if (!
|
|
641
|
+
if (!existsSync2(templateDir)) {
|
|
611
642
|
throw new Error(`Template "${templateName}" not found.`);
|
|
612
643
|
}
|
|
613
|
-
const manifestPath =
|
|
614
|
-
if (!
|
|
644
|
+
const manifestPath = join3(templateDir, "manifest.json");
|
|
645
|
+
if (!existsSync2(manifestPath)) {
|
|
615
646
|
throw new Error(`Template "${templateName}" is missing manifest.json.`);
|
|
616
647
|
}
|
|
617
648
|
const raw = JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
@@ -624,7 +655,8 @@ function loadManifest(templateName, source = "ui-templates") {
|
|
|
624
655
|
// src/apply.ts
|
|
625
656
|
function applyTemplate(projectRoot, manifest, options) {
|
|
626
657
|
const templateDir = getTemplateDir(manifest.name, options.source ?? "ui-templates");
|
|
627
|
-
const
|
|
658
|
+
const pagePrefix = resolvePagePrefix(projectRoot);
|
|
659
|
+
const conflicts = checkConflicts(projectRoot, manifest.files, pagePrefix, options.force);
|
|
628
660
|
if (conflicts.length > 0) {
|
|
629
661
|
log("red", "File conflicts detected (use --force to overwrite):");
|
|
630
662
|
for (const c of conflicts) {
|
|
@@ -633,36 +665,77 @@ function applyTemplate(projectRoot, manifest, options) {
|
|
|
633
665
|
process.exit(1);
|
|
634
666
|
}
|
|
635
667
|
for (const file of manifest.files) {
|
|
636
|
-
const src =
|
|
637
|
-
const
|
|
668
|
+
const src = join4(templateDir, file.src);
|
|
669
|
+
const destRel = resolveDest(file, pagePrefix);
|
|
670
|
+
const dest = join4(projectRoot, destRel);
|
|
638
671
|
if (options.dryRun) {
|
|
639
672
|
const label2 = file.action === "replace" ? "replace" : "add";
|
|
640
|
-
log("dim", ` [dry-run] ${label2}: ${
|
|
673
|
+
log("dim", ` [dry-run] ${label2}: ${destRel}`);
|
|
641
674
|
continue;
|
|
642
675
|
}
|
|
643
676
|
mkdirSync(dirname2(dest), { recursive: true });
|
|
644
|
-
|
|
677
|
+
const authored = isRewritable(file.src) ? readFileSync3(src, "utf-8") : null;
|
|
678
|
+
const retargeted = authored === null ? null : retargetRelativeImports(authored, file, pagePrefix);
|
|
679
|
+
if (retargeted === null) {
|
|
680
|
+
copyFileSync(src, dest);
|
|
681
|
+
} else {
|
|
682
|
+
writeFileSync2(dest, retargeted, "utf-8");
|
|
683
|
+
}
|
|
645
684
|
const label = file.action === "replace" ? "replaced" : "added";
|
|
646
|
-
|
|
685
|
+
const note = retargeted !== null && retargeted !== authored ? " (imports retargeted)" : "";
|
|
686
|
+
log("cyan", ` ${label}: ${destRel}${note}`);
|
|
647
687
|
}
|
|
648
688
|
}
|
|
649
|
-
function checkConflicts(projectRoot, files, force) {
|
|
689
|
+
function checkConflicts(projectRoot, files, pagePrefix, force) {
|
|
650
690
|
if (force) return [];
|
|
651
|
-
return files.filter((f) => f.action === "add"
|
|
691
|
+
return files.filter((f) => f.action === "add").map((f) => resolveDest(f, pagePrefix)).filter((dest) => existsSync3(join4(projectRoot, dest)));
|
|
692
|
+
}
|
|
693
|
+
var REWRITABLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs"];
|
|
694
|
+
function isRewritable(srcPath) {
|
|
695
|
+
return REWRITABLE_EXTENSIONS.some((ext) => srcPath.endsWith(ext));
|
|
696
|
+
}
|
|
697
|
+
var RELATIVE_SPECIFIER = /(\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|\bimport\s+)(["'])(\.{1,2}\/[^"']*)\2/g;
|
|
698
|
+
function retargetRelativeImports(source, entry, pagePrefix) {
|
|
699
|
+
if (pagePrefix === AUTHORED_PAGE_PREFIX) return source;
|
|
700
|
+
const fromAuthored = posix.dirname(authoredDest(entry));
|
|
701
|
+
const fromActual = posix.dirname(resolveDest(entry, pagePrefix));
|
|
702
|
+
return source.replace(RELATIVE_SPECIFIER, (match, head, quote, spec) => {
|
|
703
|
+
const authoredTarget = posix.normalize(posix.join(fromAuthored, spec));
|
|
704
|
+
const actualTarget = retargetPath(authoredTarget, pagePrefix);
|
|
705
|
+
if (actualTarget === authoredTarget && fromActual === fromAuthored) return match;
|
|
706
|
+
let next = posix.relative(fromActual, actualTarget);
|
|
707
|
+
if (!next.startsWith(".")) next = `./${next}`;
|
|
708
|
+
return `${head}${quote}${next}${quote}`;
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function retargetPath(authoredPath, pagePrefix) {
|
|
712
|
+
if (!authoredPath.startsWith(AUTHORED_PAGE_PREFIX)) return authoredPath;
|
|
713
|
+
return pagePrefix + authoredPath.slice(AUTHORED_PAGE_PREFIX.length);
|
|
652
714
|
}
|
|
653
715
|
|
|
654
716
|
// src/commands/add.ts
|
|
655
717
|
function isVantageProject(projectRoot) {
|
|
656
|
-
const pkgPath =
|
|
657
|
-
if (!
|
|
718
|
+
const pkgPath = join5(projectRoot, "package.json");
|
|
719
|
+
if (!existsSync4(pkgPath)) return false;
|
|
658
720
|
try {
|
|
659
|
-
const pkg = JSON.parse(
|
|
721
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
660
722
|
return Boolean(pkg.dependencies?.["@squadbase/vantage"] ?? pkg.devDependencies?.["@squadbase/vantage"]);
|
|
661
723
|
} catch {
|
|
662
724
|
return false;
|
|
663
725
|
}
|
|
664
726
|
}
|
|
665
|
-
function
|
|
727
|
+
function refuseSplitLayout(projectRoot) {
|
|
728
|
+
const rootPages = findSplitLayoutFiles(projectRoot);
|
|
729
|
+
if (rootPages.length === 0) return;
|
|
730
|
+
log("red", "This project has pages both in src/ and at the project root.");
|
|
731
|
+
log("yellow", " Vantage scans only src/ once it exists. Move these into src/, then re-run:");
|
|
732
|
+
for (const name of rootPages) log("dim", ` ${name}`);
|
|
733
|
+
log("dim", " Any components/ hooks/ lib/ directories holding page code move with them.");
|
|
734
|
+
log("dim", " server/ and public/ stay at the project root.");
|
|
735
|
+
log("dim", " Leaving both sides in place fails `vantage check` with SRC_DIR_SPLIT.");
|
|
736
|
+
process.exit(1);
|
|
737
|
+
}
|
|
738
|
+
function findStaleTemplateFiles(projectRoot, appliedTemplate, source, pagePrefix) {
|
|
666
739
|
const stale = [];
|
|
667
740
|
for (const name of listTemplateNames(source)) {
|
|
668
741
|
if (name === appliedTemplate) continue;
|
|
@@ -675,7 +748,8 @@ function findStaleTemplateFiles(projectRoot, appliedTemplate, source) {
|
|
|
675
748
|
}
|
|
676
749
|
for (const file of manifest.files) {
|
|
677
750
|
if (file.action !== "add") continue;
|
|
678
|
-
|
|
751
|
+
const dest = resolveDest(file, pagePrefix);
|
|
752
|
+
if (existsSync4(join5(projectRoot, dest))) stale.push(dest);
|
|
679
753
|
}
|
|
680
754
|
}
|
|
681
755
|
return [...new Set(stale)].sort();
|
|
@@ -688,6 +762,8 @@ async function addTemplate(templateName, options) {
|
|
|
688
762
|
log("dim", "Run 'npx @squadbase/vantage-template init' first.");
|
|
689
763
|
process.exit(1);
|
|
690
764
|
}
|
|
765
|
+
refuseSplitLayout(projectRoot);
|
|
766
|
+
const pagePrefix = resolvePagePrefix(projectRoot);
|
|
691
767
|
const available = listTemplateNames(source);
|
|
692
768
|
if (!available.includes(templateName)) {
|
|
693
769
|
log("red", `Template "${templateName}" not found in ${source}/.`);
|
|
@@ -704,13 +780,14 @@ async function addTemplate(templateName, options) {
|
|
|
704
780
|
log("yellow", "(dry run \u2014 no files will be changed)");
|
|
705
781
|
}
|
|
706
782
|
const entry = manifest.files.find((f) => f.action === "replace");
|
|
707
|
-
|
|
708
|
-
|
|
783
|
+
const entryDest = entry ? resolveDest(entry, pagePrefix) : void 0;
|
|
784
|
+
if (entryDest && existsSync4(join5(projectRoot, entryDest))) {
|
|
785
|
+
log("yellow", ` ${entryDest} will be overwritten \u2014 commit first if it holds your work.`);
|
|
709
786
|
}
|
|
710
787
|
}
|
|
711
788
|
applyTemplate(projectRoot, manifest, options);
|
|
712
789
|
if (!options.ai?.json) {
|
|
713
|
-
const stale = findStaleTemplateFiles(projectRoot, templateName, source);
|
|
790
|
+
const stale = findStaleTemplateFiles(projectRoot, templateName, source, pagePrefix);
|
|
714
791
|
if (stale.length > 0) {
|
|
715
792
|
log("yellow", "\nLeftovers from a previously applied template (nothing imports them):");
|
|
716
793
|
for (const dest of stale) log("dim", ` ${dest}`);
|
|
@@ -718,7 +795,13 @@ async function addTemplate(templateName, options) {
|
|
|
718
795
|
}
|
|
719
796
|
}
|
|
720
797
|
if (options.ai) {
|
|
721
|
-
const result = await customizeWithAI(
|
|
798
|
+
const result = await customizeWithAI(
|
|
799
|
+
projectRoot,
|
|
800
|
+
manifest,
|
|
801
|
+
templateDir,
|
|
802
|
+
options.ai,
|
|
803
|
+
pagePrefix
|
|
804
|
+
);
|
|
722
805
|
if (options.ai.json) {
|
|
723
806
|
console.log(
|
|
724
807
|
JSON.stringify(
|
|
@@ -777,12 +860,12 @@ Run "git diff" to review the AI changes.`);
|
|
|
777
860
|
}
|
|
778
861
|
|
|
779
862
|
// src/commands/chart.ts
|
|
780
|
-
import { existsSync as
|
|
781
|
-
import {
|
|
863
|
+
import { existsSync as existsSync6 } from "fs";
|
|
864
|
+
import { relative as relative2 } from "path";
|
|
782
865
|
|
|
783
866
|
// src/chart-presets.ts
|
|
784
|
-
import { existsSync as
|
|
785
|
-
import { dirname as dirname3, join as
|
|
867
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
868
|
+
import { dirname as dirname3, join as join6, relative } from "path";
|
|
786
869
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
787
870
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
788
871
|
var PRESET_DESCRIPTIONS = {
|
|
@@ -796,34 +879,34 @@ var PRESET_DESCRIPTIONS = {
|
|
|
796
879
|
var BEGIN_MARKER = "/* vantage-template:chart-preset \u2014 regenerated by `chart`; edits here are lost */";
|
|
797
880
|
var END_MARKER = "/* vantage-template:chart-preset:end */";
|
|
798
881
|
function getChartPresetsDir() {
|
|
799
|
-
return
|
|
882
|
+
return join6(__dirname2, "..", "chart-presets");
|
|
800
883
|
}
|
|
801
884
|
function listChartPresetNames() {
|
|
802
885
|
const dir = getChartPresetsDir();
|
|
803
|
-
if (!
|
|
886
|
+
if (!existsSync5(dir)) return [];
|
|
804
887
|
return readdirSync2(dir).filter((f) => f.endsWith(".css")).map((f) => f.replace(/\.css$/, "")).sort();
|
|
805
888
|
}
|
|
806
889
|
function getChartPresetDescription(name) {
|
|
807
890
|
return PRESET_DESCRIPTIONS[name] ?? "";
|
|
808
891
|
}
|
|
809
892
|
function getChartPresetPath(name) {
|
|
810
|
-
return
|
|
893
|
+
return join6(getChartPresetsDir(), `${name}.css`);
|
|
811
894
|
}
|
|
812
895
|
function applyChartPreset(projectRoot, presetName, options = {}) {
|
|
813
896
|
const presetPath = getChartPresetPath(presetName);
|
|
814
|
-
if (!
|
|
897
|
+
if (!existsSync5(presetPath)) {
|
|
815
898
|
throw new Error(`Chart preset "${presetName}" not found.`);
|
|
816
899
|
}
|
|
817
|
-
const targetPath =
|
|
818
|
-
if (!
|
|
819
|
-
throw new Error(
|
|
900
|
+
const targetPath = resolveStylesPath(projectRoot);
|
|
901
|
+
if (!existsSync5(targetPath)) {
|
|
902
|
+
throw new Error(`${relative(projectRoot, targetPath)} not found in project.`);
|
|
820
903
|
}
|
|
821
|
-
const preset =
|
|
904
|
+
const preset = readFileSync5(presetPath, "utf-8").trimEnd();
|
|
822
905
|
const block = `${BEGIN_MARKER}
|
|
823
906
|
${preset}
|
|
824
907
|
${END_MARKER}
|
|
825
908
|
`;
|
|
826
|
-
const current =
|
|
909
|
+
const current = readFileSync5(targetPath, "utf-8");
|
|
827
910
|
const begin = current.indexOf(BEGIN_MARKER);
|
|
828
911
|
const end = current.indexOf(END_MARKER);
|
|
829
912
|
let content;
|
|
@@ -835,7 +918,7 @@ ${END_MARKER}
|
|
|
835
918
|
${block}`;
|
|
836
919
|
}
|
|
837
920
|
if (!options.dryRun) {
|
|
838
|
-
|
|
921
|
+
writeFileSync3(targetPath, content, "utf-8");
|
|
839
922
|
}
|
|
840
923
|
return { targetPath, content };
|
|
841
924
|
}
|
|
@@ -843,9 +926,10 @@ ${block}`;
|
|
|
843
926
|
// src/commands/chart.ts
|
|
844
927
|
function setChartPreset(presetName, options) {
|
|
845
928
|
const projectRoot = process.cwd();
|
|
846
|
-
const stylesPath =
|
|
847
|
-
|
|
848
|
-
|
|
929
|
+
const stylesPath = resolveStylesPath(projectRoot);
|
|
930
|
+
const stylesLabel = relative2(projectRoot, stylesPath);
|
|
931
|
+
if (!existsSync6(stylesPath)) {
|
|
932
|
+
log("red", `${stylesLabel} not found. Are you in a Squadbase Vantage project?`);
|
|
849
933
|
process.exit(1);
|
|
850
934
|
}
|
|
851
935
|
const available = listChartPresetNames();
|
|
@@ -864,18 +948,19 @@ function setChartPreset(presetName, options) {
|
|
|
864
948
|
if (options.dryRun) {
|
|
865
949
|
log("yellow", "(dry run \u2014 no files will be changed)");
|
|
866
950
|
const { content } = applyChartPreset(projectRoot, presetName, { dryRun: true });
|
|
867
|
-
log("dim",
|
|
951
|
+
log("dim", `
|
|
952
|
+
--- ${stylesLabel} (preview) ---`);
|
|
868
953
|
console.log(content);
|
|
869
954
|
return;
|
|
870
955
|
}
|
|
871
956
|
applyChartPreset(projectRoot, presetName);
|
|
872
|
-
log("cyan",
|
|
957
|
+
log("cyan", ` updated: ${stylesLabel}`);
|
|
873
958
|
log("green", "\nChart preset applied successfully!");
|
|
874
959
|
}
|
|
875
960
|
|
|
876
961
|
// src/commands/init.ts
|
|
877
962
|
import { spawnSync } from "child_process";
|
|
878
|
-
import { cpSync, existsSync as
|
|
963
|
+
import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync as readdirSync3 } from "fs";
|
|
879
964
|
import { dirname as dirname4, join as join7 } from "path";
|
|
880
965
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
881
966
|
var __dirname3 = dirname4(fileURLToPath3(import.meta.url));
|
|
@@ -888,7 +973,7 @@ function getBaseTemplateDir() {
|
|
|
888
973
|
function initProject(options) {
|
|
889
974
|
const targetDir = process.cwd();
|
|
890
975
|
const baseTemplateDir = getBaseTemplateDir();
|
|
891
|
-
if (!
|
|
976
|
+
if (!existsSync7(baseTemplateDir)) {
|
|
892
977
|
log("red", "Base template not found. The package may be corrupted.");
|
|
893
978
|
process.exit(1);
|
|
894
979
|
}
|
|
@@ -902,13 +987,16 @@ function initProject(options) {
|
|
|
902
987
|
process.exit(1);
|
|
903
988
|
}
|
|
904
989
|
}
|
|
905
|
-
if (!options.force &&
|
|
990
|
+
if (!options.force && existsSync7(join7(targetDir, "squadbase.yml"))) {
|
|
906
991
|
log("red", "This directory already contains a Squadbase project (squadbase.yml exists).");
|
|
907
992
|
log("yellow", "Use --force to overwrite.");
|
|
908
993
|
process.exit(1);
|
|
909
994
|
}
|
|
910
|
-
|
|
911
|
-
|
|
995
|
+
const existingEntry = ["src/index.tsx", "index.tsx"].find(
|
|
996
|
+
(rel) => existsSync7(join7(targetDir, rel))
|
|
997
|
+
);
|
|
998
|
+
if (!options.force && existingEntry) {
|
|
999
|
+
log("red", `This directory already contains a ${existingEntry}.`);
|
|
912
1000
|
log("yellow", "Use --force to overwrite.");
|
|
913
1001
|
process.exit(1);
|
|
914
1002
|
}
|
|
@@ -922,7 +1010,7 @@ function initProject(options) {
|
|
|
922
1010
|
mkdirSync2(dest, { recursive: true });
|
|
923
1011
|
cpSync(src, dest, { recursive: true, force: options.force });
|
|
924
1012
|
} else {
|
|
925
|
-
if (!options.force &&
|
|
1013
|
+
if (!options.force && existsSync7(dest)) {
|
|
926
1014
|
log("yellow", ` skipped (exists): ${destName}`);
|
|
927
1015
|
continue;
|
|
928
1016
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squadbase/vantage-template",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "CLI tool for initializing and customizing Squadbase Vantage projects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"squadbase",
|
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"sync-base": "rsync -a --delete --exclude='node_modules' --exclude='dist' --exclude='.vantage' --exclude='package-lock.json' --exclude='*.tsbuildinfo' --exclude='*.log' ../vantage/ base-template/ && mv base-template/.gitignore base-template/_gitignore",
|
|
40
40
|
"build": "npm run sync-base && tsup",
|
|
41
|
-
"prepublishOnly": "npm run build",
|
|
42
|
-
"release": "npm publish --@squadbase:registry=https://registry.npmjs.org"
|
|
41
|
+
"prepublishOnly": "npm --prefix .. run agents:check && npm run build",
|
|
42
|
+
"release": "npm publish --@squadbase:registry=https://registry.npmjs.org",
|
|
43
|
+
"dev": "node scripts/dev.mjs",
|
|
44
|
+
"screenshot": "node scripts/screenshot.mjs"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@types/node": "^22.15.0",
|
|
48
|
+
"playwright": "^1.49.0",
|
|
46
49
|
"tsup": "^8.4.0",
|
|
47
50
|
"typescript": "~5.7.0"
|
|
48
51
|
},
|
package/ui-templates/CLAUDE.md
CHANGED
|
@@ -6,7 +6,7 @@ UIパターン別テンプレート(KPI重視・チャート格子・テーブ
|
|
|
6
6
|
|
|
7
7
|
このファイルは `vantage-template/ui-templates/` 配下のファイル構成ルールを定義する。デザイン要件・AI relabel・EN/JA・CLI の挙動は親 [`../CLAUDE.md`](../CLAUDE.md) に従う。
|
|
8
8
|
|
|
9
|
-
> **Vite 版との差分**: 適用先が Vantage アプリ(`@squadbase/vantage`)になったため、(1) エントリは `src/pages/home.tsx` ではなく
|
|
9
|
+
> **Vite 版との差分**: 適用先が Vantage アプリ(`@squadbase/vantage`)になったため、(1) エントリは `src/pages/home.tsx` ではなく **`src/index.tsx`**、(2) テンプレ固有ファイルは `src/templates/<slug>/` ではなく **`src/components/<slug>/`**、(3) 全ての building block を `@squadbase/vantage/*` から import する(ローカルコピーは持たない)。
|
|
10
10
|
|
|
11
11
|
## 設計目標
|
|
12
12
|
|
|
@@ -20,21 +20,25 @@ UIパターン別テンプレート(KPI重視・チャート格子・テーブ
|
|
|
20
20
|
|
|
21
21
|
### 1. 1テンプレ = 1ディレクトリ(コロケーション)
|
|
22
22
|
|
|
23
|
-
適用先では、`index.tsx`(エントリ)以外のテンプレ固有ファイルを **`components/<slug>/` 1ディレクトリに集約** する。
|
|
23
|
+
適用先では、`src/index.tsx`(エントリ)以外のテンプレ固有ファイルを **`src/components/<slug>/` 1ディレクトリに集約** する。
|
|
24
24
|
|
|
25
25
|
```
|
|
26
26
|
# 適用先(dest)レイアウト
|
|
27
|
-
index.tsx
|
|
28
|
-
components/<slug>/
|
|
27
|
+
src/index.tsx # エントリ(ルート "/")
|
|
28
|
+
src/components/<slug>/
|
|
29
29
|
mock-data.ts # データ層(relabel:false / 差し替え対象)
|
|
30
30
|
types.ts # 型(index と data で共有)
|
|
31
31
|
...(分割した場合のみ追加ファイル)
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
- **探索**: `components/<slug>/` を見ればテンプレ由来が一覧完結
|
|
35
|
-
- **削除**: `rm -rf components/<slug>/` で一掃
|
|
34
|
+
- **探索**: `src/components/<slug>/` を見ればテンプレ由来が一覧完結
|
|
35
|
+
- **削除**: `rm -rf src/components/<slug>/` で一掃
|
|
36
36
|
|
|
37
|
-
**`
|
|
37
|
+
**`src/` を使う理由(v0.3.0 以降)**: ページ探索ルートは `src/` の有無だけで決まる。ベーステンプレートは `src/` を持つので、ページとそれが import するものは**すべて `src/` の中**に置く。`src/` があるのにルート直下に `.tsx` や `styles.css` を残すと `vantage check` が `SRC_DIR_SPLIT` エラーにする。**例外は `server/` と `public/` で、これらは常にプロジェクトルート直下**(`src/server/` はスキャンされない)。
|
|
38
|
+
|
|
39
|
+
**ただし manifest の `dest` に `src/` は書かない。** `dest` は**ページ探索ルート相対**(`index.tsx` / `components/<slug>/x.tsx`)で、`src/` を付けるかどうかは適用先のレイアウトを見て CLI が決める(ルート直下にページを置くプロジェクトにも適用できる)。`server/` `public/` だけ `"scope": "root"` を付けてプロジェクトルート固定にする。オーサリング側のファイルは**常に `src/` レイアウト前提**で書き、`src/` を跨ぐ相対 import は適用時に CLI が張り替える。
|
|
40
|
+
|
|
41
|
+
**`components/` を使う理由(重要)**: Vantage は**ファイルベースルーティング**で、`.tsx` ファイルは原則すべてページ扱いになる。`components/` / `hooks/` / `lib/` / `server/` / `public/` だけがルート走査から除外される(`NON_ROUTE_DIRS`)ため、`src/templates/<slug>/` のようなディレクトリに `.tsx` を置くと **`/templates/<slug>/...` という意図しないルートが生え、`vantage check` が `MISSING_DEFAULT_EXPORT` で落ちる**(`SUSPICIOUS_ROUTE_DIR` 警告も出る)。データファイル(`.ts`)も同じディレクトリに置いてコロケーションを保つ。
|
|
38
42
|
|
|
39
43
|
### 2. インライン優先 — 薄いラッパ component を作らない
|
|
40
44
|
|
|
@@ -55,20 +59,21 @@ function areaOption(data: TimePoint[]): EChartsOption {
|
|
|
55
59
|
|
|
56
60
|
### 3. データ層(mock-data / types)は分離する
|
|
57
61
|
|
|
58
|
-
`mock-data.ts` と `types.ts` は `index.tsx` に inline せず、`components/<slug>/` の別ファイルに保つ。表示ラベル(KPI名・軸名・stage名など)は描画側(`index.tsx`)の literal/const に置き、データ配列には値・id・generic な sample text のみ置く。
|
|
62
|
+
`mock-data.ts` と `types.ts` は `src/index.tsx` に inline せず、`src/components/<slug>/` の別ファイルに保つ。表示ラベル(KPI名・軸名・stage名など)は描画側(`index.tsx`)の literal/const に置き、データ配列には値・id・generic な sample text のみ置く。
|
|
59
63
|
|
|
60
64
|
### 3-b. API 版テンプレ(`server/api` から配信する場合)
|
|
61
65
|
|
|
62
|
-
`kpi-chart-simple` はモック配列を直読みせず、**`server/api` から `useApiQuery` で取る**構成になっている。実データ接続までの導線を1
|
|
66
|
+
`kpi-chart-simple` はモック配列を直読みせず、**`server/api` から `useApiQuery` で取る**構成になっている。実データ接続までの導線を1本コードで示すためのもので、この構成を採るテンプレは置き場が変わる(以下は `src/` レイアウトに適用したときの実配置。manifest の `dest` は `src/` を除いた形で書く):
|
|
63
67
|
|
|
64
68
|
```
|
|
65
|
-
index.tsx
|
|
66
|
-
components/<slug>/*.tsx
|
|
67
|
-
lib/<slug>/types.ts
|
|
68
|
-
server/api/<endpoint>.ts # ★ サンプルデータ + GET ハンドラ(relabel:false
|
|
69
|
+
src/index.tsx # useApiQuery + useSearchParam
|
|
70
|
+
src/components/<slug>/*.tsx # 表示コンポーネント
|
|
71
|
+
src/lib/<slug>/types.ts # ★ client と server が共有する型(relabel:false)
|
|
72
|
+
server/api/<endpoint>.ts # ★ サンプルデータ + GET ハンドラ(relabel:false)※ルート直下
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
- **共有型は `components/` ではなく `lib/`** に置く。クライアントは `server/` を import できない(`CLIENT_IMPORTS_SERVER`)ので、両者が参照できる中立な場所が要る。`lib/` もルート走査対象外なので安全。
|
|
75
|
+
- **共有型は `components/` ではなく `src/lib/`** に置く。クライアントは `server/` を import できない(`CLIENT_IMPORTS_SERVER`)ので、両者が参照できる中立な場所が要る。`lib/` もルート走査対象外なので安全。
|
|
76
|
+
- **`server/` は `src/` の外**なので、ハンドラからの相対 import は `src/` を跨ぐ: `server/api/<endpoint>.ts` からは `../../src/lib/<slug>/types`。`src/components/<slug>/*.tsx` からは `../../lib/<slug>/types`(どちらも `src/` の中なので `src/` は出てこない)。
|
|
72
77
|
- **サンプルデータはサーバー側に置く。** 「実装者が差し替える場所」がハンドラ1箇所に集約され、`KpiSummary` のような payload 型さえ保てばページを触らずに実データへ移行できる。
|
|
73
78
|
- **絞り込みは `useSearchParam` + `useApiQuery` の `search`** で組む。`search` はクエリ文字列とキャッシュキーの両方に入るので、URL を変えれば再取得される。
|
|
74
79
|
- クライアントに見せたいエラーは `HttpError(status, msg)` を throw する。`useApiQuery` の `error` は `ApiError` なのでキャスト不要で `.message` が読める。
|
|
@@ -81,7 +86,7 @@ server/api/<endpoint>.ts # ★ サンプルデータ + GET ハ
|
|
|
81
86
|
|
|
82
87
|
### 分割してよい例外
|
|
83
88
|
|
|
84
|
-
**再利用される** / **内部 state・interaction を持つ** / **`index.tsx` が 400〜500 行を超える** のいずれかに当てはまる UI のみ、`components/<slug>/` 配下に切り出してよい。迷ったら inline。
|
|
89
|
+
**再利用される** / **内部 state・interaction を持つ** / **`index.tsx` が 400〜500 行を超える** のいずれかに当てはまる UI のみ、`src/components/<slug>/` 配下に切り出してよい。迷ったら inline。
|
|
85
90
|
|
|
86
91
|
## import 規約(Vantage)
|
|
87
92
|
|
|
@@ -101,11 +106,14 @@ server/api/<endpoint>.ts # ★ サンプルデータ + GET ハ
|
|
|
101
106
|
**相対 import は拡張子なしで書く**(`@squadbase/vantage` v0.2.0 で `.js` 指定子の規約は廃止。既存の `.js` 付き import も動くが、新規テンプレでは付けない)。
|
|
102
107
|
|
|
103
108
|
```tsx
|
|
104
|
-
// index.tsx から
|
|
109
|
+
// src/index.tsx から
|
|
105
110
|
import { MOCK_SERIES } from "./components/<slug>/mock-data"
|
|
106
111
|
|
|
107
|
-
// components/<slug>/detail-table.tsx から
|
|
112
|
+
// src/components/<slug>/detail-table.tsx から
|
|
108
113
|
import type { Row } from "./types"
|
|
114
|
+
|
|
115
|
+
// server/api/<endpoint>.ts から(src/ の外なので src/ を跨ぐ)
|
|
116
|
+
import type { Payload } from "../../src/lib/<slug>/types"
|
|
109
117
|
```
|
|
110
118
|
|
|
111
119
|
`Placeholder` / `Sparkline` はローカルコピーではなく `@squadbase/vantage/components` から取る(v0.1.1 でフレームワークに入った)。
|
|
@@ -123,7 +131,7 @@ Vantage の UI キットは Radix ではなく **Base UI** ベース。移植時
|
|
|
123
131
|
|
|
124
132
|
## エントリの必須要件
|
|
125
133
|
|
|
126
|
-
`index.tsx` は **default export の React component** が必須(無いと `vantage check` が `MISSING_DEFAULT_EXPORT`)。加えてタイトル/説明を `definePage` で宣言する:
|
|
134
|
+
`src/index.tsx` は **default export の React component** が必須(無いと `vantage check` が `MISSING_DEFAULT_EXPORT`)。加えてタイトル/説明を `definePage` で宣言する:
|
|
127
135
|
|
|
128
136
|
```tsx
|
|
129
137
|
import { definePage } from "@squadbase/vantage"
|
|
@@ -150,9 +158,11 @@ ui-templates/<slug>/
|
|
|
150
158
|
lib/mock-data.ts → dest: components/<slug>/mock-data.ts (add, relabel:false)
|
|
151
159
|
lib/types.ts → dest: components/<slug>/types.ts (add, relabel:false)
|
|
152
160
|
components/*.tsx → dest: components/<slug>/*.tsx (add) ※分割した場合のみ
|
|
153
|
-
server/*.ts → dest: server/api/*.ts
|
|
161
|
+
server/*.ts → dest: server/api/*.ts, scope: "root" (add, relabel:false) ※API 版のみ
|
|
154
162
|
```
|
|
155
163
|
|
|
164
|
+
**オーサリング側のディレクトリ名(`pages/` `lib/` `components/` `server/`)は適用先の dest と一致しない。** dest だけが真実で、`manifest.json` に書く。**dest はページ探索ルート相対なので `src/` は付けない**(`scope: "root"` のものだけプロジェクトルート相対)。
|
|
165
|
+
|
|
156
166
|
API 版(`kpi-chart-simple`)では `lib/types.ts` の dest が `lib/<slug>/types.ts` になり、`lib/mock-data.ts` は無く、代わりに `server/*.ts` を持つ。上の「3-b」を参照。
|
|
157
167
|
|
|
158
168
|
## manifest.json
|
|
@@ -172,6 +182,7 @@ API 版(`kpi-chart-simple`)では `lib/types.ts` の dest が `lib/<slug>/ty
|
|
|
172
182
|
|
|
173
183
|
- `files[]` が manifest の全て(`nav[]` は無い)。ベーステンプレートのナビは `useRoutes()` からルート一覧を組むので、ページファイルをコピーすればナビにも載る
|
|
174
184
|
- データファイルには `"relabel": false` を必ず付ける
|
|
185
|
+
- `dest` に `src/` を書かない。`server/` `public/` にだけ `"scope": "root"` を付ける
|
|
175
186
|
|
|
176
187
|
## 見本市の例外ルール
|
|
177
188
|
|
|
@@ -189,7 +200,7 @@ API 版(`kpi-chart-simple`)では `lib/types.ts` の dest が `lib/<slug>/ty
|
|
|
189
200
|
|
|
190
201
|
## チェックリスト(テンプレ追加・改修時)
|
|
191
202
|
|
|
192
|
-
1. テンプレ固有ファイルの `dest` は `components/<slug>/` 配下か(`templates/` に `.tsx`
|
|
203
|
+
1. テンプレ固有ファイルの `dest` は `components/<slug>/` 配下か(`templates/` に `.tsx` を置いていないか)。`dest` に `src/` を書いていないか。`server/*` だけ `dest: server/api/*` + `"scope": "root"` か
|
|
193
204
|
2. `@/` エイリアスや `echarts` / `@tanstack/*` の直接 import、`Placeholder` / `Sparkline` のローカル import が残っていないか
|
|
194
205
|
3. 相対 import に `.js` 拡張子が残っていないか(v0.2.0 で規約廃止)
|
|
195
206
|
4. 薄いチャートラッパ component を作っていないか。チャートに色をハードコードしていないか(`option.color` / `itemStyle.color` / `theme`)
|
|
@@ -197,4 +208,4 @@ API 版(`kpi-chart-simple`)では `lib/types.ts` の dest が `lib/<slug>/ty
|
|
|
197
208
|
6. 未使用の関数・型・import が残っていないか
|
|
198
209
|
7. 適用先で `npx vantage check` と `npx tsc --noEmit` が通るか
|
|
199
210
|
8. EN/JA 両方を同じ構成に揃えたか
|
|
200
|
-
9. レイアウトを変えたなら `preview-wide.png` (1600x900) / `preview-square.png` (1200x1200) を撮り直したか
|
|
211
|
+
9. レイアウトを変えたなら `preview-wide.png` (1600x900) / `preview-square.png` (1200x1200) を撮り直したか — `cd vantage-template && npm run screenshot <slug> <slug>-ja`(全部撮り直すなら引数なし)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
{ "src": "pages/index.tsx", "dest": "index.tsx", "action": "replace" },
|
|
7
7
|
{ "src": "components/top-items-table.tsx", "dest": "components/kpi-chart-simple/top-items-table.tsx", "action": "add" },
|
|
8
8
|
{ "src": "lib/types.ts", "dest": "lib/kpi-chart-simple/types.ts", "action": "add", "relabel": false },
|
|
9
|
-
{ "src": "server/kpi-summary.ts", "dest": "server/api/kpi-summary.ts", "action": "add", "relabel": false }
|
|
9
|
+
{ "src": "server/kpi-summary.ts", "dest": "server/api/kpi-summary.ts", "scope": "root", "action": "add", "relabel": false }
|
|
10
10
|
]
|
|
11
11
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
{ "src": "pages/index.tsx", "dest": "index.tsx", "action": "replace" },
|
|
7
7
|
{ "src": "components/top-items-table.tsx", "dest": "components/kpi-chart-simple/top-items-table.tsx", "action": "add" },
|
|
8
8
|
{ "src": "lib/types.ts", "dest": "lib/kpi-chart-simple/types.ts", "action": "add", "relabel": false },
|
|
9
|
-
{ "src": "server/kpi-summary.ts", "dest": "server/api/kpi-summary.ts", "action": "add", "relabel": false }
|
|
9
|
+
{ "src": "server/kpi-summary.ts", "dest": "server/api/kpi-summary.ts", "scope": "root", "action": "add", "relabel": false }
|
|
10
10
|
]
|
|
11
11
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|