@c-time/frelio-cli 1.4.8 → 1.4.10

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.
Files changed (41) hide show
  1. package/README.md +179 -3
  2. package/dist/commands/doctor.d.ts +11 -0
  3. package/dist/commands/doctor.js +71 -0
  4. package/dist/commands/fleet.d.ts +16 -0
  5. package/dist/commands/fleet.js +110 -0
  6. package/dist/commands/init.js +1 -0
  7. package/dist/commands/set-mode.d.ts +10 -0
  8. package/dist/commands/set-mode.js +41 -0
  9. package/dist/commands/update.d.ts +7 -2
  10. package/dist/commands/update.js +72 -19
  11. package/dist/core/config.js +1 -0
  12. package/dist/core/doctor.d.ts +36 -0
  13. package/dist/core/doctor.js +86 -0
  14. package/dist/core/file-generators.d.ts +3 -0
  15. package/dist/core/file-generators.js +13 -3
  16. package/dist/core/fleet.d.ts +62 -0
  17. package/dist/core/fleet.js +172 -0
  18. package/dist/core/migrations/index.d.ts +3 -0
  19. package/dist/core/migrations/index.js +2 -0
  20. package/dist/core/migrations/node20-to-node24.d.ts +15 -0
  21. package/dist/core/migrations/node20-to-node24.js +35 -0
  22. package/dist/core/migrations/registry.d.ts +11 -0
  23. package/dist/core/migrations/registry.js +18 -0
  24. package/dist/core/migrations/types.d.ts +55 -0
  25. package/dist/core/migrations/types.js +11 -0
  26. package/dist/core/site-mode.d.ts +23 -0
  27. package/dist/core/site-mode.js +37 -0
  28. package/dist/core/template-refresh.d.ts +41 -0
  29. package/dist/core/template-refresh.js +148 -0
  30. package/dist/core/template-scaffold.d.ts +11 -0
  31. package/dist/core/template-scaffold.js +12 -2
  32. package/dist/core/types.d.ts +2 -0
  33. package/dist/core/version-check.d.ts +31 -0
  34. package/dist/core/version-check.js +114 -0
  35. package/dist/index.js +41 -3
  36. package/dist/lib/npm-registry.d.ts +12 -0
  37. package/dist/lib/npm-registry.js +30 -0
  38. package/dist/lib/template-renderer.js +6 -1
  39. package/dist/lib/templates.d.ts +36 -0
  40. package/dist/lib/templates.js +171 -0
  41. package/package.json +1 -1
@@ -0,0 +1,86 @@
1
+ /**
2
+ * core/doctor — 生成物の自己診断(#85)
3
+ *
4
+ * #84 のマイグレーション定義を読み、対象リポジトリをスキャンして
5
+ * 未適用(期限付き廃止が残っている等)を検出する。
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { ok } from './types.js';
10
+ import { MIGRATIONS } from './migrations/registry.js';
11
+ /**
12
+ * グロブを展開する。`*` は basename(ファイル名部分)に 1 つだけ対応する。
13
+ * 例: `.github/workflows/*.yml`。マッチしたファイルの絶対パスを返す。
14
+ */
15
+ export function expandGlob(projectDir, globRel) {
16
+ const dir = path.dirname(globRel);
17
+ const base = path.basename(globRel);
18
+ const dirPath = path.join(projectDir, dir);
19
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
20
+ return [];
21
+ }
22
+ if (!base.includes('*')) {
23
+ const full = path.join(dirPath, base);
24
+ return fs.existsSync(full) && fs.statSync(full).isFile() ? [full] : [];
25
+ }
26
+ const [prefix, suffix] = base.split('*');
27
+ return fs
28
+ .readdirSync(dirPath, { withFileTypes: true })
29
+ .filter((e) => e.isFile())
30
+ .map((e) => e.name)
31
+ .filter((name) => name.length >= prefix.length + suffix.length &&
32
+ name.startsWith(prefix) &&
33
+ name.endsWith(suffix))
34
+ .map((name) => path.join(dirPath, name));
35
+ }
36
+ export function compilePatterns(m) {
37
+ return m.detection.patterns.map((p) => new RegExp(p, m.detection.flags ?? 'm'));
38
+ }
39
+ /**
40
+ * プロジェクトを診断し、未適用のマイグレーションを検出する。
41
+ * 欠損ファイルでは例外を投げない(best-effort)。
42
+ */
43
+ export function runDoctor(projectDir) {
44
+ const findings = [];
45
+ const scanned = new Set();
46
+ for (const migration of MIGRATIONS) {
47
+ const regexes = compilePatterns(migration);
48
+ const matchedFiles = [];
49
+ // 対象ファイルを重複なく集める
50
+ const candidates = new Set();
51
+ for (const glob of migration.detection.files) {
52
+ for (const f of expandGlob(projectDir, glob))
53
+ candidates.add(f);
54
+ }
55
+ for (const file of candidates) {
56
+ scanned.add(file);
57
+ let content;
58
+ try {
59
+ content = fs.readFileSync(file, 'utf-8');
60
+ }
61
+ catch {
62
+ continue;
63
+ }
64
+ if (regexes.some((re) => re.test(content))) {
65
+ // 表示・比較を安定させるため常に / 区切りに正規化する
66
+ matchedFiles.push(path.relative(projectDir, file).split(path.sep).join('/'));
67
+ }
68
+ }
69
+ if (matchedFiles.length > 0) {
70
+ matchedFiles.sort();
71
+ findings.push({ migration, matchedFiles });
72
+ }
73
+ }
74
+ const actionable = findings.some((f) => f.migration.severity !== 'info');
75
+ return ok({ findings, scannedFileCount: scanned.size, actionable });
76
+ }
77
+ /**
78
+ * 期限までの残日数を求める。`now` はテスト用に注入可能。
79
+ * 期限なし → null。負値 = 超過。
80
+ */
81
+ export function daysUntil(deadline, now = new Date()) {
82
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
83
+ const end = Date.parse(`${deadline}T00:00:00Z`);
84
+ const today = Date.parse(`${now.toISOString().slice(0, 10)}T00:00:00Z`);
85
+ return Math.round((end - today) / MS_PER_DAY);
86
+ }
@@ -20,6 +20,9 @@ export declare function generateAndWriteRoutesJson(projectDir: string): Operatio
20
20
  export declare function generateAndWritePublicRoutesJson(projectDir: string): OperationResult<{
21
21
  path: string;
22
22
  }>;
23
+ export declare function generateAndWriteGateMiddleware(projectDir: string): OperationResult<{
24
+ path: string;
25
+ }>;
23
26
  export declare function generateAndWriteWorkerWranglerToml(projectDir: string, config: ProjectConfig): OperationResult<{
24
27
  path: string;
25
28
  }>;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import path from 'node:path';
8
8
  import { ok, fail } from './types.js';
9
- import { generateConfigJson, generateWranglerToml, generateWorkerWranglerToml, generateRedirects, generateRoutesJson, generateStorageFunction, writeFile, } from '../lib/templates.js';
9
+ import { generateConfigJson, generateWranglerToml, generateWorkerWranglerToml, generateRedirects, generateRoutesJson, generatePublicRoutesJson, generateStorageFunction, generateGateMiddleware, writeFile, } from '../lib/templates.js';
10
10
  // ---------------------------------------------------------------------------
11
11
  // Individual file generators
12
12
  // ---------------------------------------------------------------------------
@@ -53,14 +53,23 @@ export function generateAndWriteRoutesJson(projectDir) {
53
53
  export function generateAndWritePublicRoutesJson(projectDir) {
54
54
  try {
55
55
  const filePath = path.join(projectDir, 'public', '_routes.json');
56
- const content = JSON.stringify({ version: 1, include: ['/storage/*'], exclude: [] }, null, 2);
57
- writeFile(filePath, content);
56
+ writeFile(filePath, generatePublicRoutesJson());
58
57
  return ok({ path: filePath });
59
58
  }
60
59
  catch (e) {
61
60
  return fail(`public/_routes.json 生成失敗: ${e.message}`, 'EXEC_FAILED');
62
61
  }
63
62
  }
63
+ export function generateAndWriteGateMiddleware(projectDir) {
64
+ try {
65
+ const filePath = path.join(projectDir, 'functions', '_middleware.ts');
66
+ writeFile(filePath, generateGateMiddleware());
67
+ return ok({ path: filePath });
68
+ }
69
+ catch (e) {
70
+ return fail(`functions/_middleware.ts 生成失敗: ${e.message}`, 'EXEC_FAILED');
71
+ }
72
+ }
64
73
  export function generateAndWriteWorkerWranglerToml(projectDir, config) {
65
74
  try {
66
75
  const filePath = path.join(projectDir, 'workers', 'file-upload', 'wrangler.toml');
@@ -93,6 +102,7 @@ export function regenerateAllConfigFiles(projectDir, config) {
93
102
  () => generateAndWriteRedirects(projectDir),
94
103
  () => generateAndWriteRoutesJson(projectDir),
95
104
  () => generateAndWritePublicRoutesJson(projectDir),
105
+ () => generateAndWriteGateMiddleware(projectDir),
96
106
  ];
97
107
  for (const gen of generators) {
98
108
  const result = gen();
@@ -0,0 +1,62 @@
1
+ /**
2
+ * core/fleet — フリート在庫/可視化(#89)
3
+ *
4
+ * operator が管理(アクセス権のある)コンテンツリポジトリを横断スキャンし、
5
+ * 各リポの frelio バンドルバージョン(version.json)と未対応マイグレーション
6
+ * (#84 の MIGRATIONS を #85 doctor と同じ検出ロジックで判定)を集計する。
7
+ *
8
+ * GitHub アクセスは GhClient で抽象化し、本番は gh CLI、テストは fake を注入する。
9
+ */
10
+ import { type OperationResult } from './types.js';
11
+ import type { Migration, Severity } from './migrations/types.js';
12
+ import { type Finding } from './doctor.js';
13
+ export type GhClient = {
14
+ /** org / user 配下のリポジトリ一覧(owner/repo 形式) */
15
+ listOrgRepos(org: string): string[];
16
+ /** リポジトリ内のファイル生内容。存在しなければ null */
17
+ readFile(repo: string, pathRel: string): string | null;
18
+ /** リポジトリ内ディレクトリのファイル名一覧。存在しなければ null */
19
+ listDir(repo: string, dirRel: string): string[] | null;
20
+ };
21
+ /** gh CLI を使った本番 GhClient */
22
+ export declare function createGhClient(): GhClient;
23
+ /** グロブ(`*` は basename に 1 つ)とファイルパスの一致判定(posix) */
24
+ export declare function globMatches(globRel: string, filePath: string): boolean;
25
+ /**
26
+ * 取得済みファイル(path → 内容)に対してマイグレーション検出を行う。
27
+ * doctor.runDoctor のローカル fs 版と同じ判定を、リモート取得済み内容に適用する純関数。
28
+ */
29
+ export declare function detectMigrations(files: Record<string, string>, migrations?: Migration[]): Finding[];
30
+ export type RepoInventory = {
31
+ repo: string;
32
+ /** version.json が存在する = frelio リポと判定 */
33
+ isFrelio: boolean;
34
+ version: string | null;
35
+ findings: Finding[];
36
+ /** スキャン中の致命的エラー(取得失敗等) */
37
+ error?: string;
38
+ };
39
+ /** 1 リポジトリをスキャンして在庫情報を返す。 */
40
+ export declare function scanRepo(gh: GhClient, repo: string, migrations?: Migration[]): RepoInventory;
41
+ export type ResolveReposOptions = {
42
+ /** GitHub org / user。配下を列挙し version.json を持つリポのみ対象にする */
43
+ org?: string;
44
+ /** 明示指定の owner/repo 一覧 */
45
+ repos?: string[];
46
+ };
47
+ /** スキャン対象リポジトリを解決する(重複排除)。 */
48
+ export declare function resolveRepos(gh: GhClient, opts: ResolveReposOptions): OperationResult<string[]>;
49
+ export type FleetTotals = {
50
+ scanned: number;
51
+ frelio: number;
52
+ withPending: number;
53
+ bySeverity: Record<Severity, number>;
54
+ };
55
+ export type FleetReport = {
56
+ repos: RepoInventory[];
57
+ totals: FleetTotals;
58
+ };
59
+ /** 解決済みリポ一覧をスキャンして集計する。 */
60
+ export declare function runFleetInventory(gh: GhClient, repos: string[], migrations?: Migration[]): FleetReport;
61
+ /** actionable(critical/warning)な未対応がフリート内に 1 件でもあるか。 */
62
+ export declare function fleetHasActionable(report: FleetReport): boolean;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * core/fleet — フリート在庫/可視化(#89)
3
+ *
4
+ * operator が管理(アクセス権のある)コンテンツリポジトリを横断スキャンし、
5
+ * 各リポの frelio バンドルバージョン(version.json)と未対応マイグレーション
6
+ * (#84 の MIGRATIONS を #85 doctor と同じ検出ロジックで判定)を集計する。
7
+ *
8
+ * GitHub アクセスは GhClient で抽象化し、本番は gh CLI、テストは fake を注入する。
9
+ */
10
+ import path from 'node:path';
11
+ import { exec } from '../lib/shell.js';
12
+ import { ok, fail } from './types.js';
13
+ import { MIGRATIONS } from './migrations/registry.js';
14
+ import { compilePatterns } from './doctor.js';
15
+ /** gh CLI を使った本番 GhClient */
16
+ export function createGhClient() {
17
+ return {
18
+ listOrgRepos(org) {
19
+ const out = exec(`gh repo list ${org} --no-archived --limit 1000 --json nameWithOwner -q ".[].nameWithOwner"`, { silent: true });
20
+ return out.split('\n').map((s) => s.trim()).filter(Boolean);
21
+ },
22
+ readFile(repo, pathRel) {
23
+ try {
24
+ return exec(`gh api repos/${repo}/contents/${pathRel} -H "Accept: application/vnd.github.raw"`, { silent: true });
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ },
30
+ listDir(repo, dirRel) {
31
+ try {
32
+ const out = exec(`gh api repos/${repo}/contents/${dirRel} -q ".[].name"`, {
33
+ silent: true,
34
+ });
35
+ return out.split('\n').map((s) => s.trim()).filter(Boolean);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ },
41
+ };
42
+ }
43
+ // ---------------------------------------------------------------------------
44
+ // 検出(doctor のリモート版)
45
+ // ---------------------------------------------------------------------------
46
+ /** グロブ(`*` は basename に 1 つ)とファイルパスの一致判定(posix) */
47
+ export function globMatches(globRel, filePath) {
48
+ if (path.posix.dirname(globRel) !== path.posix.dirname(filePath))
49
+ return false;
50
+ const gBase = path.posix.basename(globRel);
51
+ const fBase = path.posix.basename(filePath);
52
+ if (!gBase.includes('*'))
53
+ return gBase === fBase;
54
+ const [prefix, suffix] = gBase.split('*');
55
+ return (fBase.length >= prefix.length + suffix.length &&
56
+ fBase.startsWith(prefix) &&
57
+ fBase.endsWith(suffix));
58
+ }
59
+ /**
60
+ * 取得済みファイル(path → 内容)に対してマイグレーション検出を行う。
61
+ * doctor.runDoctor のローカル fs 版と同じ判定を、リモート取得済み内容に適用する純関数。
62
+ */
63
+ export function detectMigrations(files, migrations = MIGRATIONS) {
64
+ const findings = [];
65
+ for (const migration of migrations) {
66
+ const regexes = compilePatterns(migration);
67
+ const matchedFiles = [];
68
+ for (const [filePath, content] of Object.entries(files)) {
69
+ const inScope = migration.detection.files.some((g) => globMatches(g, filePath));
70
+ if (!inScope)
71
+ continue;
72
+ if (regexes.some((re) => re.test(content)))
73
+ matchedFiles.push(filePath);
74
+ }
75
+ if (matchedFiles.length > 0) {
76
+ matchedFiles.sort();
77
+ findings.push({ migration, matchedFiles });
78
+ }
79
+ }
80
+ return findings;
81
+ }
82
+ /** マイグレーション検出に必要なファイルをリモートから集める */
83
+ function collectFiles(gh, repo, migrations) {
84
+ const globs = migrations.flatMap((m) => m.detection.files);
85
+ const dirs = new Set(globs.map((g) => path.posix.dirname(g)));
86
+ const files = {};
87
+ for (const dir of dirs) {
88
+ const names = gh.listDir(repo, dir);
89
+ if (!names)
90
+ continue;
91
+ for (const name of names) {
92
+ const full = dir === '.' ? name : `${dir}/${name}`;
93
+ if (!globs.some((g) => globMatches(g, full)))
94
+ continue;
95
+ const content = gh.readFile(repo, full);
96
+ if (content != null)
97
+ files[full] = content;
98
+ }
99
+ }
100
+ return files;
101
+ }
102
+ /** 1 リポジトリをスキャンして在庫情報を返す。 */
103
+ export function scanRepo(gh, repo, migrations = MIGRATIONS) {
104
+ try {
105
+ const vraw = gh.readFile(repo, 'version.json');
106
+ const isFrelio = vraw != null;
107
+ let version = null;
108
+ if (vraw != null) {
109
+ try {
110
+ version = JSON.parse(vraw).version ?? null;
111
+ }
112
+ catch {
113
+ version = null;
114
+ }
115
+ }
116
+ const files = collectFiles(gh, repo, migrations);
117
+ const findings = detectMigrations(files, migrations);
118
+ return { repo, isFrelio, version, findings };
119
+ }
120
+ catch (e) {
121
+ return { repo, isFrelio: false, version: null, findings: [], error: e.message };
122
+ }
123
+ }
124
+ /** スキャン対象リポジトリを解決する(重複排除)。 */
125
+ export function resolveRepos(gh, opts) {
126
+ const set = new Set();
127
+ for (const r of opts.repos ?? []) {
128
+ const t = r.trim();
129
+ if (t)
130
+ set.add(t);
131
+ }
132
+ if (opts.org) {
133
+ let orgRepos;
134
+ try {
135
+ orgRepos = gh.listOrgRepos(opts.org);
136
+ }
137
+ catch (e) {
138
+ return fail(`リポジトリ一覧の取得に失敗しました: ${e.message}`, 'EXEC_FAILED');
139
+ }
140
+ // org 配下は version.json を持つ frelio リポのみに絞る
141
+ for (const r of orgRepos) {
142
+ if (gh.readFile(r, 'version.json') != null)
143
+ set.add(r);
144
+ }
145
+ }
146
+ if (set.size === 0) {
147
+ return fail('スキャン対象リポジトリがありません。--org または --repos を指定してください。', 'NOT_FOUND');
148
+ }
149
+ return ok([...set]);
150
+ }
151
+ /** 解決済みリポ一覧をスキャンして集計する。 */
152
+ export function runFleetInventory(gh, repos, migrations = MIGRATIONS) {
153
+ const inv = repos.map((r) => scanRepo(gh, r, migrations));
154
+ const bySeverity = { critical: 0, warning: 0, info: 0 };
155
+ for (const r of inv) {
156
+ for (const f of r.findings)
157
+ bySeverity[f.migration.severity]++;
158
+ }
159
+ return {
160
+ repos: inv,
161
+ totals: {
162
+ scanned: inv.length,
163
+ frelio: inv.filter((r) => r.isFrelio).length,
164
+ withPending: inv.filter((r) => r.findings.length > 0).length,
165
+ bySeverity,
166
+ },
167
+ };
168
+ }
169
+ /** actionable(critical/warning)な未対応がフリート内に 1 件でもあるか。 */
170
+ export function fleetHasActionable(report) {
171
+ return report.repos.some((r) => r.findings.some((f) => f.migration.severity !== 'info'));
172
+ }
@@ -0,0 +1,3 @@
1
+ export type { Migration, DetectionPattern, Severity, TemplateManifest } from './types.js';
2
+ export { MIGRATIONS, TEMPLATE_MANIFEST } from './registry.js';
3
+ export { node20ToNode24 } from './node20-to-node24.js';
@@ -0,0 +1,2 @@
1
+ export { MIGRATIONS, TEMPLATE_MANIFEST } from './registry.js';
2
+ export { node20ToNode24 } from './node20-to-node24.js';
@@ -0,0 +1,15 @@
1
+ import type { Migration } from './types.js';
2
+ /**
3
+ * #81 — GitHub Actions の Node 20 廃止(2026-06-16 に Node 24 へ強制移行)。
4
+ *
5
+ * 検出: `.github/workflows/*` のうち
6
+ * - `runs.using: node20`(カスタムアクション宣言)
7
+ * - `actions/<name>@v4`(Node 20 ランタイムの旧 major)
8
+ * - `node-version: 20`(setup-node の Node 指定)
9
+ *
10
+ * 注意: 検出は `actions/` 配下のアクションに限定する。`cloudflare/wrangler-action@v4`
11
+ * のような他 org のアクションを誤検出しないため(更新後テンプレで使用している)。
12
+ *
13
+ * 参考: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
14
+ */
15
+ export declare const node20ToNode24: Migration;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * #81 — GitHub Actions の Node 20 廃止(2026-06-16 に Node 24 へ強制移行)。
3
+ *
4
+ * 検出: `.github/workflows/*` のうち
5
+ * - `runs.using: node20`(カスタムアクション宣言)
6
+ * - `actions/<name>@v4`(Node 20 ランタイムの旧 major)
7
+ * - `node-version: 20`(setup-node の Node 指定)
8
+ *
9
+ * 注意: 検出は `actions/` 配下のアクションに限定する。`cloudflare/wrangler-action@v4`
10
+ * のような他 org のアクションを誤検出しないため(更新後テンプレで使用している)。
11
+ *
12
+ * 参考: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
13
+ */
14
+ export const node20ToNode24 = {
15
+ id: 'node20-to-node24',
16
+ title: 'GitHub Actions ワークフローを Node 24 対応へ更新',
17
+ description: 'GitHub が Node 20 ベースの Actions ランタイムを 2026-06-16 に Node 24 へ強制移行します。' +
18
+ '生成済みワークフローが Node 20 のアクション(actions/*@v4 等)や node-version: 20 を' +
19
+ '使っている場合、期限後に動作不能となる恐れがあります。',
20
+ severity: 'critical',
21
+ deadline: '2026-06-16',
22
+ detection: {
23
+ files: ['.github/workflows/*.yml', '.github/workflows/*.yaml'],
24
+ patterns: [
25
+ "\\busing:\\s*['\"]?node20['\"]?",
26
+ 'actions/[\\w-]+@v4\\b',
27
+ "node-version:\\s*['\"]?20\\b",
28
+ ],
29
+ flags: 'm',
30
+ },
31
+ remediation: '`npx @c-time/frelio-cli update --templates-only` を実行して .github/workflows/ を' +
32
+ '最新テンプレート(Node 24 対応アクション)で再生成してください。',
33
+ command: 'npx @c-time/frelio-cli update --templates-only',
34
+ referenceUrl: 'https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/',
35
+ };
@@ -0,0 +1,11 @@
1
+ import type { Migration, TemplateManifest } from './types.js';
2
+ /**
3
+ * 既知のマイグレーション一覧(実装順 = 登録順)。
4
+ * frelio doctor がこの一覧を走査して未適用を検出する。
5
+ */
6
+ export declare const MIGRATIONS: Migration[];
7
+ /**
8
+ * frelio が期待する最新テンプレート世代。
9
+ * バージョンは「世代」を表す単純な整数文字列で、破壊的なテンプレ変更ごとに上げる。
10
+ */
11
+ export declare const TEMPLATE_MANIFEST: TemplateManifest;
@@ -0,0 +1,18 @@
1
+ import { node20ToNode24 } from './node20-to-node24.js';
2
+ /**
3
+ * 既知のマイグレーション一覧(実装順 = 登録順)。
4
+ * frelio doctor がこの一覧を走査して未適用を検出する。
5
+ */
6
+ export const MIGRATIONS = [node20ToNode24];
7
+ /**
8
+ * frelio が期待する最新テンプレート世代。
9
+ * バージョンは「世代」を表す単純な整数文字列で、破壊的なテンプレ変更ごとに上げる。
10
+ */
11
+ export const TEMPLATE_MANIFEST = {
12
+ manifestVersion: 1,
13
+ expected: {
14
+ workflows: '2', // Node 24 対応(#81)
15
+ functions: '1',
16
+ wrangler: '1',
17
+ },
18
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * core/migrations — マシン可読なマイグレーション定義
3
+ *
4
+ * 「いま正しいテンプレ/ワークフロー/functions の期待バージョンは何か」
5
+ * 「どの期限付き破壊的変更にどう対処するか」を機械可読に持つ。
6
+ * frelio doctor(#85)と frelio update(#86)が共通参照する土台。
7
+ *
8
+ * 定義はコードに同梱する(.ts)。CLI の npm 公開物は dist/(tsc 出力)のみで、
9
+ * src/templates/ 配下の非 .ts ファイルは同梱されないため。
10
+ */
11
+ /** 検出した問題の重大度 */
12
+ export type Severity = 'critical' | 'warning' | 'info';
13
+ /**
14
+ * 検出パターン。対象ファイル(グロブ)の中身に対し、いずれかの正規表現が
15
+ * ヒットしたらそのマイグレーションが「未適用」と判定する。
16
+ */
17
+ export type DetectionPattern = {
18
+ /** 対象ファイルのグロブ(プロジェクトルートからの相対、`*` 1段のみ対応) */
19
+ files: string[];
20
+ /** ファイル内容に対して評価する正規表現ソース(OR 判定) */
21
+ patterns: string[];
22
+ /** 各正規表現に付与するフラグ(既定 'm') */
23
+ flags?: string;
24
+ };
25
+ /** 1件のマイグレーション(破壊的変更への追従単位) */
26
+ export type Migration = {
27
+ /** 一意な識別子(CHANGELOG / Releases の「移行」節と対応づける) */
28
+ id: string;
29
+ /** 1行タイトル(日本語) */
30
+ title: string;
31
+ /** 概要説明(日本語) */
32
+ description: string;
33
+ severity: Severity;
34
+ /** 強制適用/廃止の期限(ISO 8601 の日付 'YYYY-MM-DD'、任意) */
35
+ deadline?: string;
36
+ detection: DetectionPattern;
37
+ /** 直し方の説明(日本語) */
38
+ remediation: string;
39
+ /** 直すために実行するコマンド(任意、doctor → update の導線に使う) */
40
+ command?: string;
41
+ /** 参考 URL(任意) */
42
+ referenceUrl?: string;
43
+ };
44
+ /**
45
+ * テンプレート版マニフェスト。生成物に対し、frelio が期待する
46
+ * ワークフロー/functions/wrangler テンプレートの世代を表す。
47
+ */
48
+ export type TemplateManifest = {
49
+ manifestVersion: 1;
50
+ expected: {
51
+ workflows: string;
52
+ functions: string;
53
+ wrangler: string;
54
+ };
55
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * core/migrations — マシン可読なマイグレーション定義
3
+ *
4
+ * 「いま正しいテンプレ/ワークフロー/functions の期待バージョンは何か」
5
+ * 「どの期限付き破壊的変更にどう対処するか」を機械可読に持つ。
6
+ * frelio doctor(#85)と frelio update(#86)が共通参照する土台。
7
+ *
8
+ * 定義はコードに同梱する(.ts)。CLI の npm 公開物は dist/(tsc 出力)のみで、
9
+ * src/templates/ 配下の非 .ts ファイルは同梱されないため。
10
+ */
11
+ export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * core/site-mode — 本番ゲートのサイト公開状態変更(Issue #93)
3
+ *
4
+ * config.json を正本として siteMode を更新し、派生ファイル
5
+ * (config.json / wrangler.toml [vars] / functions/_middleware.ts /
6
+ * public/_routes.json 等)を一括再生成する。
7
+ *
8
+ * エッジ(functions/_middleware.ts)が読むのは wrangler.toml [vars] の SITE_MODE。
9
+ * そのため変更を反映するにはコンテンツ Pages の再デプロイが必要。
10
+ */
11
+ import { type ProjectConfig, type OperationResult } from './types.js';
12
+ import { SITE_MODES, type SiteMode } from '../lib/templates.js';
13
+ export { SITE_MODES };
14
+ export type { SiteMode };
15
+ export declare function isValidSiteMode(v: string): v is SiteMode;
16
+ /**
17
+ * siteMode を変更し、派生ファイルを一括再生成する。
18
+ */
19
+ export declare function changeSiteMode(projectDir: string, mode: string): OperationResult<{
20
+ config: ProjectConfig;
21
+ mode: SiteMode;
22
+ regeneratedPaths: string[];
23
+ }>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * core/site-mode — 本番ゲートのサイト公開状態変更(Issue #93)
3
+ *
4
+ * config.json を正本として siteMode を更新し、派生ファイル
5
+ * (config.json / wrangler.toml [vars] / functions/_middleware.ts /
6
+ * public/_routes.json 等)を一括再生成する。
7
+ *
8
+ * エッジ(functions/_middleware.ts)が読むのは wrangler.toml [vars] の SITE_MODE。
9
+ * そのため変更を反映するにはコンテンツ Pages の再デプロイが必要。
10
+ */
11
+ import { ok, fail } from './types.js';
12
+ import { SITE_MODES } from '../lib/templates.js';
13
+ import { readConfig } from './config.js';
14
+ import { regenerateAllConfigFiles } from './file-generators.js';
15
+ export { SITE_MODES };
16
+ export function isValidSiteMode(v) {
17
+ return SITE_MODES.includes(v);
18
+ }
19
+ /**
20
+ * siteMode を変更し、派生ファイルを一括再生成する。
21
+ */
22
+ export function changeSiteMode(projectDir, mode) {
23
+ if (!isValidSiteMode(mode)) {
24
+ return fail(`不正なモード: "${mode}"(指定可能: ${SITE_MODES.join(' | ')})`, 'VALIDATION_ERROR');
25
+ }
26
+ const read = readConfig(projectDir);
27
+ if (!read.success)
28
+ return read;
29
+ if (!read.data.config) {
30
+ return fail('admin/config.json が見つかりません。Frelio プロジェクトのルートで実行してください。', 'NOT_FOUND');
31
+ }
32
+ const merged = { ...read.data.config, siteMode: mode };
33
+ const regen = regenerateAllConfigFiles(projectDir, merged);
34
+ if (!regen.success)
35
+ return regen;
36
+ return ok({ config: merged, mode, regeneratedPaths: regen.data.paths });
37
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * core/template-refresh — 既存プロジェクトのテンプレ再生成(#86)
3
+ *
4
+ * frelio doctor(#85)が「古い」と指摘した workflow / functions / wrangler を、
5
+ * 最新テンプレートで再生成する(Pull の受け皿)。
6
+ *
7
+ * - ワークフロー(.github/)は GitHub のテンプレートリポジトリから取得して .hbs を描画する。
8
+ * (init と同じ取得経路。`fetchTemplate` を共有)
9
+ * - wrangler.toml / functions/storage / _redirects / _routes.json は config から決定的に生成する
10
+ * (lib/templates.ts の生成関数を共有。set-domain と同一の正本)
11
+ *
12
+ * ユーザー改変を保護するため、既存ファイルと差分がある場合は競合モードに従う
13
+ * (既定 backup: <file>.frelio-bak に退避してから上書き)。
14
+ *
15
+ * frelio-data/・public/・admin/config.json・functions/api/・workers/file-upload/src は触らない。
16
+ */
17
+ import { type ProjectConfig, type OperationResult } from './types.js';
18
+ /** 再生成対象のカテゴリ */
19
+ export type RefreshTarget = 'workflows' | 'wrangler' | 'functions' | 'routing';
20
+ /** 競合(既存ファイルと差分あり)時の挙動 */
21
+ export type ConflictMode = 'backup' | 'overwrite' | 'skip';
22
+ export type RefreshResult = {
23
+ /** 新規作成または上書きしたファイル(プロジェクト相対・/ 区切り) */
24
+ written: string[];
25
+ /** 既存と同一でスキップ */
26
+ unchanged: string[];
27
+ /** 差分ありだが mode=skip で据え置き */
28
+ skipped: string[];
29
+ /** .frelio-bak を作成したファイル */
30
+ backedUp: string[];
31
+ };
32
+ export type RefreshOptions = {
33
+ targets?: RefreshTarget[];
34
+ mode?: ConflictMode;
35
+ /** テスト/DI 用。指定時は GitHub からの取得をスキップしこのディレクトリを使う */
36
+ templateDir?: string;
37
+ };
38
+ /**
39
+ * テンプレートを再生成して既存プロジェクトに適用する。
40
+ */
41
+ export declare function refreshTemplates(projectDir: string, config: ProjectConfig, opts?: RefreshOptions): Promise<OperationResult<RefreshResult>>;