@setzkasten-cms/astro-admin 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-routes/_auth-guard.d.ts +47 -0
- package/dist/api-routes/_auth-guard.js +18 -0
- package/dist/api-routes/_commit-trailers.d.ts +8 -0
- package/dist/api-routes/_commit-trailers.js +8 -0
- package/dist/api-routes/_feature-gate.d.ts +23 -0
- package/dist/api-routes/_feature-gate.js +7 -0
- package/dist/api-routes/_github-cache.d.ts +4 -0
- package/dist/api-routes/_github-cache.js +8 -0
- package/dist/api-routes/_github-token.d.ts +27 -0
- package/dist/api-routes/_github-token.js +8 -0
- package/dist/api-routes/_license-tier.d.ts +22 -0
- package/dist/api-routes/_license-tier.js +6 -0
- package/dist/api-routes/_pages-meta-store.d.ts +32 -0
- package/dist/api-routes/_pages-meta-store.js +9 -0
- package/dist/api-routes/_role-resolver.d.ts +15 -0
- package/dist/api-routes/_role-resolver.js +13 -0
- package/dist/api-routes/_session-cookie.d.ts +18 -0
- package/dist/api-routes/_session-cookie.js +6 -0
- package/dist/api-routes/_storage-config.d.ts +60 -0
- package/dist/api-routes/_storage-config.js +10 -0
- package/dist/api-routes/_vercel-origin.d.ts +16 -0
- package/dist/api-routes/_vercel-origin.js +6 -0
- package/dist/api-routes/_webhook-dispatcher.d.ts +13 -0
- package/dist/api-routes/_webhook-dispatcher.js +97 -0
- package/dist/api-routes/_webhook-signing.d.ts +11 -0
- package/dist/api-routes/_webhook-signing.js +6 -0
- package/dist/api-routes/_webhook-status-store.d.ts +19 -0
- package/dist/api-routes/_webhook-status-store.js +10 -0
- package/dist/api-routes/_website-resolver.d.ts +49 -0
- package/dist/api-routes/_website-resolver.js +14 -0
- package/dist/api-routes/_websites-store.d.ts +30 -0
- package/dist/api-routes/_websites-store.js +11 -0
- package/dist/api-routes/asset-proxy.d.ts +12 -0
- package/dist/api-routes/asset-proxy.js +67 -0
- package/dist/api-routes/auth-callback.d.ts +9 -0
- package/dist/api-routes/auth-callback.js +68 -0
- package/dist/api-routes/auth-login.d.ts +11 -0
- package/dist/api-routes/auth-login.js +27 -0
- package/dist/api-routes/auth-logout.d.ts +10 -0
- package/dist/api-routes/auth-logout.js +13 -0
- package/dist/api-routes/auth-session.d.ts +9 -0
- package/dist/api-routes/auth-session.js +31 -0
- package/dist/api-routes/auth-setzkasten-login.d.ts +18 -0
- package/dist/api-routes/auth-setzkasten-login.js +74 -0
- package/dist/api-routes/catalog-add.d.ts +14 -0
- package/dist/api-routes/catalog-add.js +153 -0
- package/dist/api-routes/catalog-export.d.ts +13 -0
- package/dist/api-routes/catalog-export.js +71 -0
- package/dist/api-routes/catalog-helpers.d.ts +41 -0
- package/dist/api-routes/catalog-helpers.js +11 -0
- package/dist/api-routes/catalog-list.d.ts +11 -0
- package/dist/api-routes/catalog-list.js +12 -0
- package/dist/api-routes/config.d.ts +12 -0
- package/dist/api-routes/config.js +43 -0
- package/dist/api-routes/deploy-hook.d.ts +14 -0
- package/dist/api-routes/deploy-hook.js +52 -0
- package/dist/api-routes/editors.d.ts +29 -0
- package/dist/api-routes/editors.js +18 -0
- package/dist/api-routes/github-proxy.d.ts +12 -0
- package/dist/api-routes/github-proxy.js +82 -0
- package/dist/api-routes/global-config.d.ts +20 -0
- package/dist/api-routes/global-config.js +19 -0
- package/dist/api-routes/history-rollback.d.ts +22 -0
- package/dist/api-routes/history-rollback.js +111 -0
- package/dist/api-routes/history-version.d.ts +11 -0
- package/dist/api-routes/history-version.js +57 -0
- package/dist/api-routes/history.d.ts +13 -0
- package/dist/api-routes/history.js +85 -0
- package/dist/api-routes/icons-local.d.ts +28 -0
- package/dist/api-routes/icons-local.js +115 -0
- package/dist/api-routes/init-add-section.d.ts +23 -0
- package/dist/api-routes/init-add-section.js +396 -0
- package/dist/api-routes/init-apply.d.ts +11 -0
- package/dist/api-routes/init-apply.js +266 -0
- package/dist/api-routes/init-migrate.d.ts +16 -0
- package/dist/api-routes/init-migrate.js +205 -0
- package/dist/api-routes/init-scan-page.d.ts +39 -0
- package/dist/api-routes/init-scan-page.js +260 -0
- package/dist/api-routes/init-scan.d.ts +11 -0
- package/dist/api-routes/init-scan.js +128 -0
- package/dist/api-routes/migrate-to-multi.d.ts +26 -0
- package/dist/api-routes/migrate-to-multi.js +188 -0
- package/dist/api-routes/pages.d.ts +39 -0
- package/dist/api-routes/pages.js +88 -0
- package/dist/api-routes/section-add.d.ts +18 -0
- package/dist/api-routes/section-add.js +173 -0
- package/dist/api-routes/section-commit-pending.d.ts +18 -0
- package/dist/api-routes/section-commit-pending.js +207 -0
- package/dist/api-routes/section-delete.d.ts +15 -0
- package/dist/api-routes/section-delete.js +149 -0
- package/dist/api-routes/section-duplicate.d.ts +15 -0
- package/dist/api-routes/section-duplicate.js +143 -0
- package/dist/api-routes/section-management.d.ts +41 -0
- package/dist/api-routes/section-management.js +14 -0
- package/dist/api-routes/section-prepare-copy.d.ts +25 -0
- package/dist/api-routes/section-prepare-copy.js +69 -0
- package/dist/api-routes/section-prepare.d.ts +18 -0
- package/dist/api-routes/section-prepare.js +104 -0
- package/dist/api-routes/setup-github-app-bounce.d.ts +13 -0
- package/dist/api-routes/setup-github-app-bounce.js +45 -0
- package/dist/api-routes/setup-github-app-branches.d.ts +14 -0
- package/dist/api-routes/setup-github-app-branches.js +58 -0
- package/dist/api-routes/setup-github-app-callback.d.ts +15 -0
- package/dist/api-routes/setup-github-app-callback.js +45 -0
- package/dist/api-routes/setup-github-app-installed.d.ts +15 -0
- package/dist/api-routes/setup-github-app-installed.js +33 -0
- package/dist/api-routes/setup-github-app-repos.d.ts +17 -0
- package/dist/api-routes/setup-github-app-repos.js +41 -0
- package/dist/api-routes/setup-github-app.d.ts +15 -0
- package/dist/api-routes/setup-github-app.js +41 -0
- package/dist/api-routes/updater-check.d.ts +10 -0
- package/dist/api-routes/updater-check.js +37 -0
- package/dist/api-routes/updater-register.d.ts +14 -0
- package/dist/api-routes/updater-register.js +71 -0
- package/dist/api-routes/updater-transfer.d.ts +11 -0
- package/dist/api-routes/updater-transfer.js +37 -0
- package/dist/api-routes/updater-unbind.d.ts +17 -0
- package/dist/api-routes/updater-unbind.js +35 -0
- package/dist/api-routes/webhooks-status.d.ts +12 -0
- package/dist/api-routes/webhooks-status.js +22 -0
- package/dist/api-routes/webhooks-test.d.ts +13 -0
- package/dist/api-routes/webhooks-test.js +124 -0
- package/dist/api-routes/webhooks.d.ts +6 -0
- package/dist/api-routes/webhooks.js +148 -0
- package/dist/api-routes/websites-add.d.ts +15 -0
- package/dist/api-routes/websites-add.js +92 -0
- package/dist/api-routes/websites-list.d.ts +12 -0
- package/dist/api-routes/websites-list.js +35 -0
- package/dist/api-routes/websites-remove.d.ts +15 -0
- package/dist/api-routes/websites-remove.js +69 -0
- package/dist/chunk-35S35OIV.js +80 -0
- package/dist/chunk-45ARVNT3.js +25 -0
- package/dist/chunk-5PIMDP4N.js +25 -0
- package/dist/chunk-5ZFTG4BW.js +10 -0
- package/dist/chunk-6UIKVKED.js +51 -0
- package/dist/chunk-737TIZRU.js +9 -0
- package/dist/chunk-AM4DZXXM.js +120 -0
- package/dist/chunk-FXNOTESI.js +87 -0
- package/dist/chunk-GHNK2GFE.js +48 -0
- package/dist/chunk-GRG3LNKH.js +37 -0
- package/dist/chunk-INIWFKQ3.js +236 -0
- package/dist/chunk-JHY6XTLL.js +24 -0
- package/dist/chunk-K22A4ZBS.js +1574 -0
- package/dist/chunk-KH22FJO5.js +17 -0
- package/dist/chunk-NKDATSPA.js +43 -0
- package/dist/chunk-RHJONMLK.js +1267 -0
- package/dist/chunk-TJNJKPUL.js +11 -0
- package/dist/chunk-V6IMPVF3.js +120 -0
- package/dist/chunk-W3QHY5GW.js +19 -0
- package/dist/chunk-ZQDGGWJP.js +43 -0
- package/package.json +249 -53
- package/src/api-routes/__tests__/route-registry.test.ts +7 -1
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeAstroSection,
|
|
3
|
+
extractLayoutImport,
|
|
4
|
+
extractSectionImports
|
|
5
|
+
} from "../chunk-K22A4ZBS.js";
|
|
6
|
+
import {
|
|
7
|
+
prefixPath,
|
|
8
|
+
resolveStorageConfigForRequest
|
|
9
|
+
} from "../chunk-6UIKVKED.js";
|
|
10
|
+
import {
|
|
11
|
+
resolveGitHubTokenForRequest
|
|
12
|
+
} from "../chunk-NKDATSPA.js";
|
|
13
|
+
|
|
14
|
+
// src/api-routes/init-scan-page.ts
|
|
15
|
+
function resolveFullConfig() {
|
|
16
|
+
const buildConfig = typeof __SETZKASTEN_FULL_CONFIG__ !== "undefined" ? __SETZKASTEN_FULL_CONFIG__ : null;
|
|
17
|
+
return buildConfig ?? globalThis.__SETZKASTEN_FULL_CONFIG__;
|
|
18
|
+
}
|
|
19
|
+
var POST = async ({ request, cookies }) => {
|
|
20
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
21
|
+
if (!session) {
|
|
22
|
+
return new Response("Unauthorized", { status: 401 });
|
|
23
|
+
}
|
|
24
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
25
|
+
if (!tokenResult.ok) {
|
|
26
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
27
|
+
}
|
|
28
|
+
const githubToken = tokenResult.value;
|
|
29
|
+
try {
|
|
30
|
+
const body = await request.json();
|
|
31
|
+
const storage = await resolveStorageConfigForRequest(request, body);
|
|
32
|
+
if (!storage) {
|
|
33
|
+
return Response.json({ error: "Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars." }, { status: 400 });
|
|
34
|
+
}
|
|
35
|
+
const { owner, repo, branch, projectPrefix } = storage;
|
|
36
|
+
const { pagePath } = body;
|
|
37
|
+
if (!pagePath) {
|
|
38
|
+
return Response.json({ error: "pagePath is required" }, { status: 400 });
|
|
39
|
+
}
|
|
40
|
+
const config = resolveFullConfig();
|
|
41
|
+
const managedSections = /* @__PURE__ */ new Map();
|
|
42
|
+
if (config) {
|
|
43
|
+
for (const product of Object.values(config.products)) {
|
|
44
|
+
for (const [key, section] of Object.entries(product.sections)) {
|
|
45
|
+
managedSections.set(key, new Set(Object.keys(section.fields)));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const treeResponse = await githubFetch(
|
|
50
|
+
`https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`,
|
|
51
|
+
githubToken
|
|
52
|
+
);
|
|
53
|
+
if (!treeResponse.ok) {
|
|
54
|
+
return Response.json(
|
|
55
|
+
{ error: `Failed to fetch repo tree: ${treeResponse.status}` },
|
|
56
|
+
{ status: treeResponse.status }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const treeData = await treeResponse.json();
|
|
60
|
+
const files = treeData.tree.map((item) => ({
|
|
61
|
+
path: item.path,
|
|
62
|
+
type: item.type
|
|
63
|
+
}));
|
|
64
|
+
let fullPagePath = prefixPath(pagePath, projectPrefix);
|
|
65
|
+
let pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken);
|
|
66
|
+
if (!pageSource && pagePath.endsWith(".astro")) {
|
|
67
|
+
const indexPath = pagePath.replace(/\.astro$/, "/index.astro");
|
|
68
|
+
fullPagePath = prefixPath(indexPath, projectPrefix);
|
|
69
|
+
pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken);
|
|
70
|
+
}
|
|
71
|
+
if (!pageSource) {
|
|
72
|
+
return Response.json({ error: `Could not read page source: ${fullPagePath}` }, { status: 404 });
|
|
73
|
+
}
|
|
74
|
+
const imports = extractSectionImports(pageSource, fullPagePath, files, projectPrefix);
|
|
75
|
+
const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
76
|
+
const contentPath = serverConfig?.storage?.contentPath || "content";
|
|
77
|
+
const unmanagedSections = [];
|
|
78
|
+
const managedUpdates = [];
|
|
79
|
+
for (const imp of imports) {
|
|
80
|
+
if (!imp.resolvedPath) continue;
|
|
81
|
+
if (!managedSections.has(imp.sectionKey)) {
|
|
82
|
+
const sectionSource = await fetchFileContent(owner, repo, branch, imp.resolvedPath, githubToken);
|
|
83
|
+
if (!sectionSource) continue;
|
|
84
|
+
const section = await analyzeAstroSection(
|
|
85
|
+
sectionSource,
|
|
86
|
+
imp.sectionKey,
|
|
87
|
+
imp.componentName,
|
|
88
|
+
imp.resolvedPath
|
|
89
|
+
);
|
|
90
|
+
unmanagedSections.push(section);
|
|
91
|
+
} else {
|
|
92
|
+
const existingFieldKeys = managedSections.get(imp.sectionKey);
|
|
93
|
+
const sectionSource = await fetchFileContent(owner, repo, branch, imp.resolvedPath, githubToken);
|
|
94
|
+
if (!sectionSource) continue;
|
|
95
|
+
const inferred = await analyzeAstroSection(
|
|
96
|
+
sectionSource,
|
|
97
|
+
imp.sectionKey,
|
|
98
|
+
imp.componentName,
|
|
99
|
+
imp.resolvedPath
|
|
100
|
+
);
|
|
101
|
+
const sectionJsonPath = `${contentPath}/_sections/${imp.sectionKey}.json`;
|
|
102
|
+
const sectionJson = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
|
|
103
|
+
const existingValues = /* @__PURE__ */ new Set();
|
|
104
|
+
if (sectionJson) {
|
|
105
|
+
try {
|
|
106
|
+
const data = JSON.parse(sectionJson);
|
|
107
|
+
for (const val of Object.values(data)) {
|
|
108
|
+
if (typeof val === "string" && val.length >= 2) existingValues.add(val);
|
|
109
|
+
}
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const missingFields = inferred.fields.filter((f) => {
|
|
114
|
+
if (existingFieldKeys.has(f.key)) return false;
|
|
115
|
+
if (typeof f.defaultValue === "string" && existingValues.has(f.defaultValue)) return false;
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
if (missingFields.length > 0) {
|
|
119
|
+
managedUpdates.push({
|
|
120
|
+
key: imp.sectionKey,
|
|
121
|
+
componentName: imp.componentName,
|
|
122
|
+
componentPath: imp.resolvedPath,
|
|
123
|
+
missingFields,
|
|
124
|
+
allFields: inferred.fields
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
{
|
|
130
|
+
const pageKeyNorm = pagePath.replace(/^src\/pages\//, "").replace(/\/(index)?\.astro$/, "").replace(/\.astro$/, "") || "index";
|
|
131
|
+
const sectionKey = "_page_" + pageKeyNorm.replace(/\//g, "_");
|
|
132
|
+
const pageSection = await analyzeAstroSection(
|
|
133
|
+
pageSource,
|
|
134
|
+
sectionKey,
|
|
135
|
+
pageKeyNorm.replace(/\//g, "_") + "Page",
|
|
136
|
+
fullPagePath,
|
|
137
|
+
{ mode: "page" }
|
|
138
|
+
);
|
|
139
|
+
pageSection.isPageLevel = true;
|
|
140
|
+
if (!managedSections.has(sectionKey)) {
|
|
141
|
+
if (pageSection.fields.length > 0) {
|
|
142
|
+
unmanagedSections.push(pageSection);
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const existingFieldKeys = managedSections.get(sectionKey);
|
|
146
|
+
const sectionJsonPath = `${contentPath}/_sections/${sectionKey}.json`;
|
|
147
|
+
const sectionJson = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
|
|
148
|
+
const existingValues = /* @__PURE__ */ new Set();
|
|
149
|
+
if (sectionJson) {
|
|
150
|
+
try {
|
|
151
|
+
const data = JSON.parse(sectionJson);
|
|
152
|
+
for (const val of Object.values(data)) {
|
|
153
|
+
if (typeof val === "string" && val.length >= 2) existingValues.add(val);
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const missingFields = pageSection.fields.filter((f) => {
|
|
159
|
+
if (existingFieldKeys.has(f.key)) return false;
|
|
160
|
+
if (typeof f.defaultValue === "string" && existingValues.has(f.defaultValue)) return false;
|
|
161
|
+
return true;
|
|
162
|
+
});
|
|
163
|
+
if (missingFields.length > 0) {
|
|
164
|
+
managedUpdates.push({
|
|
165
|
+
key: sectionKey,
|
|
166
|
+
componentName: pageKeyNorm.replace(/\//g, "_") + "Page",
|
|
167
|
+
componentPath: fullPagePath,
|
|
168
|
+
missingFields,
|
|
169
|
+
allFields: pageSection.fields
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
{
|
|
175
|
+
const layoutImport = extractLayoutImport(pageSource, fullPagePath, files, projectPrefix);
|
|
176
|
+
if (layoutImport?.resolvedPath) {
|
|
177
|
+
const layoutSource = await fetchFileContent(owner, repo, branch, layoutImport.resolvedPath, githubToken);
|
|
178
|
+
if (layoutSource) {
|
|
179
|
+
for (const region of extractLayoutRegions(layoutSource)) {
|
|
180
|
+
const sectionKey = `_layout_${region.name}`;
|
|
181
|
+
if (managedSections.has(sectionKey)) continue;
|
|
182
|
+
const regionSource = `---
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
${region.html}`;
|
|
186
|
+
const section = await analyzeAstroSection(
|
|
187
|
+
regionSource,
|
|
188
|
+
sectionKey,
|
|
189
|
+
region.name,
|
|
190
|
+
layoutImport.resolvedPath,
|
|
191
|
+
{ mode: "page" }
|
|
192
|
+
);
|
|
193
|
+
section.isPageLevel = true;
|
|
194
|
+
section.isLayoutRegion = true;
|
|
195
|
+
section.layoutPath = layoutImport.resolvedPath;
|
|
196
|
+
section.regionTag = region.tag;
|
|
197
|
+
if (section.fields.length > 0) {
|
|
198
|
+
unmanagedSections.push(section);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return Response.json({ unmanagedSections, managedUpdates });
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error("[setzkasten] scan-page error:", error);
|
|
207
|
+
return Response.json(
|
|
208
|
+
{ error: error instanceof Error ? error.message : "Scan failed" },
|
|
209
|
+
{ status: 500 }
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
async function githubFetch(url, token) {
|
|
214
|
+
return fetch(url, {
|
|
215
|
+
headers: {
|
|
216
|
+
Authorization: `Bearer ${token}`,
|
|
217
|
+
Accept: "application/vnd.github+json",
|
|
218
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function extractLayoutRegions(layoutSource) {
|
|
223
|
+
let template = layoutSource;
|
|
224
|
+
if (layoutSource.startsWith("---")) {
|
|
225
|
+
const endIdx = layoutSource.indexOf("\n---", 3);
|
|
226
|
+
if (endIdx !== -1) {
|
|
227
|
+
template = layoutSource.slice(endIdx + 4);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const regions = [];
|
|
231
|
+
for (const tag of ["header", "footer"]) {
|
|
232
|
+
const openTag = new RegExp(`<${tag}(\\s[^>]*)?>`, "i");
|
|
233
|
+
const openMatch = openTag.exec(template);
|
|
234
|
+
if (!openMatch) continue;
|
|
235
|
+
const closeTag = `</${tag}>`;
|
|
236
|
+
const closeIdx = template.indexOf(closeTag, openMatch.index);
|
|
237
|
+
if (closeIdx === -1) continue;
|
|
238
|
+
const html = template.slice(openMatch.index, closeIdx + closeTag.length);
|
|
239
|
+
regions.push({ name: tag, tag, html });
|
|
240
|
+
}
|
|
241
|
+
return regions;
|
|
242
|
+
}
|
|
243
|
+
async function fetchFileContent(owner, repo, branch, path, token) {
|
|
244
|
+
try {
|
|
245
|
+
const response = await githubFetch(
|
|
246
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
247
|
+
token
|
|
248
|
+
);
|
|
249
|
+
if (!response.ok) return null;
|
|
250
|
+
const data = await response.json();
|
|
251
|
+
return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
|
|
252
|
+
} catch {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export {
|
|
257
|
+
POST,
|
|
258
|
+
extractLayoutRegions,
|
|
259
|
+
resolveFullConfig
|
|
260
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/setzkasten/init/scan
|
|
5
|
+
*
|
|
6
|
+
* Scans a GitHub repo and returns project analysis + detected sections.
|
|
7
|
+
* Body: { owner: string, repo: string, branch?: string }
|
|
8
|
+
*/
|
|
9
|
+
declare const POST: APIRoute;
|
|
10
|
+
|
|
11
|
+
export { POST };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeAstroSection,
|
|
3
|
+
extractSectionImports,
|
|
4
|
+
findAstroPages
|
|
5
|
+
} from "../chunk-K22A4ZBS.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveGitHubTokenForRequest
|
|
8
|
+
} from "../chunk-NKDATSPA.js";
|
|
9
|
+
|
|
10
|
+
// src/api-routes/init-scan.ts
|
|
11
|
+
import { analyzeProject } from "@setzkasten-cms/core/init";
|
|
12
|
+
var POST = async ({ request, cookies }) => {
|
|
13
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
14
|
+
if (!session) {
|
|
15
|
+
return new Response("Unauthorized", { status: 401 });
|
|
16
|
+
}
|
|
17
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
18
|
+
if (!tokenResult.ok) {
|
|
19
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
20
|
+
}
|
|
21
|
+
const githubToken = tokenResult.value;
|
|
22
|
+
try {
|
|
23
|
+
const body = await request.json();
|
|
24
|
+
const { owner, repo, branch = "main" } = body;
|
|
25
|
+
if (!owner || !repo) {
|
|
26
|
+
return Response.json({ error: "owner and repo are required" }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
const treeResponse = await githubFetch(
|
|
29
|
+
`https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`,
|
|
30
|
+
githubToken
|
|
31
|
+
);
|
|
32
|
+
if (!treeResponse.ok) {
|
|
33
|
+
return Response.json(
|
|
34
|
+
{ error: `Failed to fetch repo tree: ${treeResponse.status}` },
|
|
35
|
+
{ status: treeResponse.status }
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
const treeData = await treeResponse.json();
|
|
39
|
+
const files = treeData.tree.map((item) => ({
|
|
40
|
+
path: item.path,
|
|
41
|
+
type: item.type
|
|
42
|
+
}));
|
|
43
|
+
const analysis = analyzeProject(files);
|
|
44
|
+
const astroRoot = analysis.projectRoots.find((r) => r.framework === "astro");
|
|
45
|
+
if (!astroRoot) {
|
|
46
|
+
return Response.json({
|
|
47
|
+
analysis,
|
|
48
|
+
pages: [],
|
|
49
|
+
sections: [],
|
|
50
|
+
astroConfigPath: null,
|
|
51
|
+
message: "Kein Astro-Projekt gefunden"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const pages = findAstroPages(files, astroRoot.path);
|
|
55
|
+
const allSections = /* @__PURE__ */ new Map();
|
|
56
|
+
const pageSections = {};
|
|
57
|
+
for (const page of pages) {
|
|
58
|
+
const pageSource = await fetchFileContent(owner, repo, branch, page.filePath, githubToken);
|
|
59
|
+
if (!pageSource) continue;
|
|
60
|
+
const imports = extractSectionImports(pageSource, page.filePath, files, astroRoot.path);
|
|
61
|
+
pageSections[page.pageKey] = imports.map((i) => i.sectionKey);
|
|
62
|
+
for (const imp of imports) {
|
|
63
|
+
if (allSections.has(imp.sectionKey)) continue;
|
|
64
|
+
if (!imp.resolvedPath) continue;
|
|
65
|
+
const sectionSource = await fetchFileContent(owner, repo, branch, imp.resolvedPath, githubToken);
|
|
66
|
+
if (!sectionSource) continue;
|
|
67
|
+
const section = await analyzeAstroSection(
|
|
68
|
+
sectionSource,
|
|
69
|
+
imp.sectionKey,
|
|
70
|
+
imp.componentName,
|
|
71
|
+
imp.resolvedPath
|
|
72
|
+
);
|
|
73
|
+
allSections.set(imp.sectionKey, section);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const astroConfigCandidates = ["astro.config.mjs", "astro.config.ts", "astro.config.js"];
|
|
77
|
+
let astroConfigPath = null;
|
|
78
|
+
for (const candidate of astroConfigCandidates) {
|
|
79
|
+
const path = astroRoot.path ? `${astroRoot.path}/${candidate}` : candidate;
|
|
80
|
+
if (files.some((f) => f.path === path)) {
|
|
81
|
+
astroConfigPath = path;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Response.json({
|
|
86
|
+
analysis,
|
|
87
|
+
projectRoot: astroRoot.path,
|
|
88
|
+
pages,
|
|
89
|
+
pageSections,
|
|
90
|
+
sections: Object.fromEntries(allSections),
|
|
91
|
+
astroConfigPath
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("[setzkasten] Init scan error:", error);
|
|
95
|
+
return Response.json(
|
|
96
|
+
{ error: error instanceof Error ? error.message : "Scan failed" },
|
|
97
|
+
{ status: 500 }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
async function githubFetch(url, token) {
|
|
102
|
+
return fetch(url, {
|
|
103
|
+
headers: {
|
|
104
|
+
Authorization: `Bearer ${token}`,
|
|
105
|
+
Accept: "application/vnd.github+json",
|
|
106
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function fetchFileContent(owner, repo, branch, path, token) {
|
|
111
|
+
try {
|
|
112
|
+
const response = await githubFetch(
|
|
113
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
114
|
+
token
|
|
115
|
+
);
|
|
116
|
+
if (!response.ok) return null;
|
|
117
|
+
const data = await response.json();
|
|
118
|
+
if (data.encoding === "base64") {
|
|
119
|
+
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
120
|
+
}
|
|
121
|
+
return data.content;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
POST
|
|
128
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/setzkasten/migrate/to-multi
|
|
5
|
+
*
|
|
6
|
+
* Body: { configRepo: 'owner/repo', configInstallationId: string,
|
|
7
|
+
* previewOrigin?: string }
|
|
8
|
+
*
|
|
9
|
+
* Admin-only. Expects the deployer to have already (1) created the
|
|
10
|
+
* config repo and (2) installed the GitHub App on it. The endpoint then:
|
|
11
|
+
*
|
|
12
|
+
* 1. Reads `_editors.json` and `_global_config.json` from the current
|
|
13
|
+
* single-mode website repo.
|
|
14
|
+
* 2. Writes both files into `<config-repo>/content/`.
|
|
15
|
+
* 3. Initialises `<config-repo>/websites.json` with a single entry that
|
|
16
|
+
* snapshots the current single-mode setup (repo, branch, preview
|
|
17
|
+
* origin, App-Installation-ID).
|
|
18
|
+
*
|
|
19
|
+
* After a 200 response the deployer still has to update
|
|
20
|
+
* `setzkasten.config.ts` (kind: 'single' → 'multi') and the ENV
|
|
21
|
+
* variables and redeploy. The wizard surfaces a copy-ready diff for
|
|
22
|
+
* those manual steps.
|
|
23
|
+
*/
|
|
24
|
+
declare const POST: APIRoute;
|
|
25
|
+
|
|
26
|
+
export { POST };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSession
|
|
3
|
+
} from "../chunk-INIWFKQ3.js";
|
|
4
|
+
import {
|
|
5
|
+
resolveStorageConfig
|
|
6
|
+
} from "../chunk-6UIKVKED.js";
|
|
7
|
+
import "../chunk-5PIMDP4N.js";
|
|
8
|
+
import "../chunk-45ARVNT3.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveConfigRepoToken
|
|
11
|
+
} from "../chunk-NKDATSPA.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveLicenseTier
|
|
14
|
+
} from "../chunk-TJNJKPUL.js";
|
|
15
|
+
import {
|
|
16
|
+
withTrailers
|
|
17
|
+
} from "../chunk-KH22FJO5.js";
|
|
18
|
+
|
|
19
|
+
// src/api-routes/migrate-to-multi.ts
|
|
20
|
+
import { canAddWebsite, isMultiModeAvailable } from "@setzkasten-cms/core";
|
|
21
|
+
var POST = async ({ request, cookies }) => {
|
|
22
|
+
const session = parseSession(cookies.get("setzkasten_session")?.value);
|
|
23
|
+
if (!session) return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
|
|
24
|
+
if (session.user.role !== "admin")
|
|
25
|
+
return new Response(JSON.stringify({ error: "Forbidden" }), { status: 403 });
|
|
26
|
+
const tier = resolveLicenseTier();
|
|
27
|
+
if (!isMultiModeAvailable(tier)) {
|
|
28
|
+
return new Response(
|
|
29
|
+
JSON.stringify({
|
|
30
|
+
error: "Multi-Mode ist nur mit Pro- oder Enterprise-Lizenz verf\xFCgbar.",
|
|
31
|
+
tier
|
|
32
|
+
}),
|
|
33
|
+
{ status: 402, headers: { "Content-Type": "application/json" } }
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
let body = {};
|
|
37
|
+
try {
|
|
38
|
+
body = await request.json();
|
|
39
|
+
} catch {
|
|
40
|
+
return new Response(JSON.stringify({ error: "Invalid JSON body" }), { status: 400 });
|
|
41
|
+
}
|
|
42
|
+
if (!body.configRepo || typeof body.configRepo !== "string" || !body.configRepo.includes("/")) {
|
|
43
|
+
return new Response(JSON.stringify({ error: "configRepo (owner/repo) ist erforderlich" }), {
|
|
44
|
+
status: 400
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (!body.configInstallationId || typeof body.configInstallationId !== "string") {
|
|
48
|
+
return new Response(JSON.stringify({ error: "configInstallationId ist erforderlich" }), {
|
|
49
|
+
status: 400
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
|
|
53
|
+
if (fullConfig?.storage?.kind && fullConfig.storage.kind !== "single" && fullConfig.storage.kind !== "github-app") {
|
|
54
|
+
return new Response(
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
error: `Migration nur aus dem Single-Mode m\xF6glich. Aktueller storage.kind: ${fullConfig.storage.kind ?? "unbekannt"}`
|
|
57
|
+
}),
|
|
58
|
+
{ status: 400 }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const allowed = canAddWebsite(tier, 0);
|
|
62
|
+
if (!allowed.ok) {
|
|
63
|
+
return new Response(JSON.stringify({ error: allowed.reason }), { status: 402 });
|
|
64
|
+
}
|
|
65
|
+
const sourceStorage = resolveStorageConfig();
|
|
66
|
+
if (!sourceStorage) {
|
|
67
|
+
return new Response(
|
|
68
|
+
JSON.stringify({ error: "Single-Mode Storage konnte nicht aufgel\xF6st werden" }),
|
|
69
|
+
{ status: 500 }
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const tokenResult = await resolveConfigRepoToken();
|
|
73
|
+
if (!tokenResult.ok) {
|
|
74
|
+
return new Response(JSON.stringify({ error: tokenResult.error.message }), { status: 500 });
|
|
75
|
+
}
|
|
76
|
+
const token = tokenResult.value;
|
|
77
|
+
const [configOwner, configRepo] = body.configRepo.split("/");
|
|
78
|
+
if (!configOwner || !configRepo) {
|
|
79
|
+
return new Response(JSON.stringify({ error: 'configRepo muss "owner/repo" sein' }), {
|
|
80
|
+
status: 400
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const sourceContentPath = globalThis.__SETZKASTEN_CONFIG__?.storage?.contentPath ?? "content";
|
|
84
|
+
const headers = {
|
|
85
|
+
Authorization: `Bearer ${token}`,
|
|
86
|
+
Accept: "application/vnd.github+json",
|
|
87
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
88
|
+
"Content-Type": "application/json"
|
|
89
|
+
};
|
|
90
|
+
const ghBase = (owner, repo, path) => `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
|
|
91
|
+
const sourceEditors = await readOptional(
|
|
92
|
+
ghBase(sourceStorage.owner, sourceStorage.repo, `${sourceContentPath}/_editors.json`),
|
|
93
|
+
`?ref=${sourceStorage.branch}`,
|
|
94
|
+
headers
|
|
95
|
+
);
|
|
96
|
+
const sourceGlobal = await readOptional(
|
|
97
|
+
ghBase(sourceStorage.owner, sourceStorage.repo, `${sourceContentPath}/_global_config.json`),
|
|
98
|
+
`?ref=${sourceStorage.branch}`,
|
|
99
|
+
headers
|
|
100
|
+
);
|
|
101
|
+
const configBranch = "main";
|
|
102
|
+
const editorsCommit = sourceEditors ? await putFile(
|
|
103
|
+
ghBase(configOwner, configRepo, "content/_editors.json"),
|
|
104
|
+
sourceEditors,
|
|
105
|
+
configBranch,
|
|
106
|
+
"chore(migrate): copy editors from website repo",
|
|
107
|
+
headers
|
|
108
|
+
) : true;
|
|
109
|
+
const globalCommit = sourceGlobal ? await putFile(
|
|
110
|
+
ghBase(configOwner, configRepo, "content/_global_config.json"),
|
|
111
|
+
sourceGlobal,
|
|
112
|
+
configBranch,
|
|
113
|
+
"chore(migrate): copy global config from website repo",
|
|
114
|
+
headers
|
|
115
|
+
) : true;
|
|
116
|
+
const previewOrigin = body.previewOrigin ?? process.env.PUBLIC_SITE_URL ?? "http://localhost:4321";
|
|
117
|
+
const initialEntry = {
|
|
118
|
+
id: "main",
|
|
119
|
+
name: sourceStorage.repo,
|
|
120
|
+
repo: `${sourceStorage.owner}/${sourceStorage.repo}`,
|
|
121
|
+
branch: sourceStorage.branch,
|
|
122
|
+
previewOrigin,
|
|
123
|
+
githubApp: {
|
|
124
|
+
appId: process.env.GITHUB_APP_ID ?? "",
|
|
125
|
+
installationId: process.env.GITHUB_APP_INSTALLATION_ID ?? ""
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const websitesContent = JSON.stringify({ websites: [initialEntry] }, null, 2);
|
|
129
|
+
const websitesCommit = await putFile(
|
|
130
|
+
ghBase(configOwner, configRepo, "websites.json"),
|
|
131
|
+
websitesContent,
|
|
132
|
+
configBranch,
|
|
133
|
+
"feat(migrate): initialise websites.json with current single-mode setup",
|
|
134
|
+
headers
|
|
135
|
+
);
|
|
136
|
+
return new Response(
|
|
137
|
+
JSON.stringify({
|
|
138
|
+
ok: true,
|
|
139
|
+
committed: {
|
|
140
|
+
editors: editorsCommit,
|
|
141
|
+
globalConfig: globalCommit,
|
|
142
|
+
websites: websitesCommit
|
|
143
|
+
},
|
|
144
|
+
// Echo back the values the user still has to set themselves.
|
|
145
|
+
manual: {
|
|
146
|
+
configRepo: body.configRepo,
|
|
147
|
+
configInstallationId: body.configInstallationId,
|
|
148
|
+
configBranch,
|
|
149
|
+
envChanges: {
|
|
150
|
+
add: {
|
|
151
|
+
SETZKASTEN_CONFIG_REPO: body.configRepo,
|
|
152
|
+
SETZKASTEN_CONFIG_BRANCH: configBranch,
|
|
153
|
+
GITHUB_APP_CONFIG_INSTALLATION_ID: body.configInstallationId
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}),
|
|
158
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
159
|
+
);
|
|
160
|
+
};
|
|
161
|
+
async function readOptional(url, qs, headers) {
|
|
162
|
+
const res = await fetch(url + qs, { headers });
|
|
163
|
+
if (!res.ok) return null;
|
|
164
|
+
const data = await res.json();
|
|
165
|
+
return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
|
|
166
|
+
}
|
|
167
|
+
async function putFile(url, content, branch, message, headers) {
|
|
168
|
+
let sha;
|
|
169
|
+
try {
|
|
170
|
+
const existing = await fetch(`${url}?ref=${branch}`, { headers });
|
|
171
|
+
if (existing.ok) {
|
|
172
|
+
const data = await existing.json();
|
|
173
|
+
sha = data.sha;
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
const body = {
|
|
178
|
+
message: withTrailers(message),
|
|
179
|
+
content: Buffer.from(content).toString("base64"),
|
|
180
|
+
branch
|
|
181
|
+
};
|
|
182
|
+
if (sha) body.sha = sha;
|
|
183
|
+
const res = await fetch(url, { method: "PUT", headers, body: JSON.stringify(body) });
|
|
184
|
+
return res.ok;
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
POST
|
|
188
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
interface PageInfo {
|
|
4
|
+
path: string;
|
|
5
|
+
pageKey: string;
|
|
6
|
+
label: string;
|
|
7
|
+
hasConfig: boolean;
|
|
8
|
+
/** Unix-ms timestamp of the page's last Setzkasten-driven commit, when
|
|
9
|
+
* `_pages-meta.json` knows about the page. */
|
|
10
|
+
lastModified?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Returns the list of pages scanned at build time.
|
|
14
|
+
* Reads the Vite build-time constant first; falls back to globalThis for
|
|
15
|
+
* local dev / test environments where the define is not applied.
|
|
16
|
+
*
|
|
17
|
+
* Only valid in single-mode where the admin and the website share the same
|
|
18
|
+
* Astro project. Multi-mode has to fetch the page list per website at
|
|
19
|
+
* runtime (see {@link fetchPagesFromGitHub}).
|
|
20
|
+
*/
|
|
21
|
+
declare function resolvePages(): PageInfo[];
|
|
22
|
+
/**
|
|
23
|
+
* GET /api/setzkasten/pages
|
|
24
|
+
*
|
|
25
|
+
* Single-mode: returns the build-time scan from the admin's own Astro
|
|
26
|
+
* project, enriched with `_pages-meta.json` timestamps from the
|
|
27
|
+
* (single) repo.
|
|
28
|
+
*
|
|
29
|
+
* Multi-mode: the X-SK-Website header selects one of the registered
|
|
30
|
+
* websites. The admin doesn't have build-time access to that website's
|
|
31
|
+
* `src/pages/` directory, so we fetch it via the GitHub Contents API
|
|
32
|
+
* (cached for 5 min), then enrich with the per-website
|
|
33
|
+
* `_pages-meta.json`. Without this branch, every website in Multi-Mode
|
|
34
|
+
* would see the admin's own page list — typically a single "index"
|
|
35
|
+
* stub — instead of its real pages.
|
|
36
|
+
*/
|
|
37
|
+
declare const GET: APIRoute;
|
|
38
|
+
|
|
39
|
+
export { GET, resolvePages };
|