@setzkasten-cms/astro-admin 1.4.0 → 1.4.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/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,28 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GET /api/setzkasten/icons/local
|
|
5
|
+
*
|
|
6
|
+
* Lists `.svg` files from the website's local icons folder(s) so the admin
|
|
7
|
+
* picker can render them as a "Lokal" tab.
|
|
8
|
+
*
|
|
9
|
+
* Path resolution:
|
|
10
|
+
* 1. If `icons.localPath` is set in the website config, scan exactly the
|
|
11
|
+
* listed folders (string or string[]). Whatever the user wrote wins.
|
|
12
|
+
* 2. Otherwise, scan `LOCAL_ICONS_DISCOVERY_PATHS` until a folder yields
|
|
13
|
+
* at least one SVG. Lets projects with `public/icons/` or
|
|
14
|
+
* `src/assets/svg/` work without touching their config — and surfaces
|
|
15
|
+
* the discovered path so the admin can copy it into setzkasten.config.ts.
|
|
16
|
+
*
|
|
17
|
+
* Response:
|
|
18
|
+
* {
|
|
19
|
+
* icons: Array<{ name, svg, source }>,
|
|
20
|
+
* paths: string[], // paths that contributed icons
|
|
21
|
+
* discovered: boolean, // true when discovery fallback ran
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Each `svg` value is server-side sanitized.
|
|
25
|
+
*/
|
|
26
|
+
declare const GET: APIRoute;
|
|
27
|
+
|
|
28
|
+
export { GET };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
prefixPath,
|
|
3
|
+
resolveStorageConfigForRequest
|
|
4
|
+
} from "../chunk-6UIKVKED.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveGitHubTokenForRequest
|
|
7
|
+
} from "../chunk-NKDATSPA.js";
|
|
8
|
+
|
|
9
|
+
// src/api-routes/icons-local.ts
|
|
10
|
+
import {
|
|
11
|
+
LOCAL_ICONS_DISCOVERY_PATHS,
|
|
12
|
+
resolveLocalIconsPaths,
|
|
13
|
+
sanitizeSvg
|
|
14
|
+
} from "@setzkasten-cms/core";
|
|
15
|
+
var GET = async ({ request, cookies }) => {
|
|
16
|
+
if (!cookies.get("setzkasten_session")?.value) {
|
|
17
|
+
return new Response("Unauthorized", { status: 401 });
|
|
18
|
+
}
|
|
19
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
20
|
+
if (!tokenResult.ok) {
|
|
21
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
22
|
+
}
|
|
23
|
+
const token = tokenResult.value;
|
|
24
|
+
const storage = await resolveStorageConfigForRequest(request);
|
|
25
|
+
if (!storage) {
|
|
26
|
+
return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
|
|
27
|
+
}
|
|
28
|
+
const { owner, repo, branch, projectPrefix } = storage;
|
|
29
|
+
const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
|
|
30
|
+
const configured = resolveLocalIconsPaths(fullConfig?.icons ?? null);
|
|
31
|
+
const discovered = configured === null;
|
|
32
|
+
const candidatePaths = configured ?? LOCAL_ICONS_DISCOVERY_PATHS;
|
|
33
|
+
const headers = {
|
|
34
|
+
Authorization: `Bearer ${token}`,
|
|
35
|
+
Accept: "application/vnd.github+json",
|
|
36
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
37
|
+
};
|
|
38
|
+
const MAX_ICONS = 200;
|
|
39
|
+
const yieldedPaths = [];
|
|
40
|
+
const allEntries = [];
|
|
41
|
+
for (const relativePath of candidatePaths) {
|
|
42
|
+
if (allEntries.length >= MAX_ICONS) break;
|
|
43
|
+
const path = prefixPath(relativePath, projectPrefix);
|
|
44
|
+
const listRes = await fetch(
|
|
45
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
46
|
+
{ headers }
|
|
47
|
+
);
|
|
48
|
+
if (listRes.status === 404) continue;
|
|
49
|
+
if (!listRes.ok) continue;
|
|
50
|
+
const json = await listRes.json().catch(() => null);
|
|
51
|
+
if (!Array.isArray(json)) continue;
|
|
52
|
+
const svgs = json.filter(
|
|
53
|
+
(e) => e.type === "file" && e.name.toLowerCase().endsWith(".svg")
|
|
54
|
+
);
|
|
55
|
+
if (svgs.length === 0) continue;
|
|
56
|
+
yieldedPaths.push(relativePath);
|
|
57
|
+
const remaining = MAX_ICONS - allEntries.length;
|
|
58
|
+
for (const entry of svgs.slice(0, remaining)) {
|
|
59
|
+
allEntries.push({ path: relativePath, entry });
|
|
60
|
+
}
|
|
61
|
+
if (discovered) break;
|
|
62
|
+
}
|
|
63
|
+
const PARALLELISM = 8;
|
|
64
|
+
const results = new Array(allEntries.length).fill(null);
|
|
65
|
+
let cursor = 0;
|
|
66
|
+
async function worker() {
|
|
67
|
+
while (true) {
|
|
68
|
+
const idx = cursor++;
|
|
69
|
+
if (idx >= allEntries.length) return;
|
|
70
|
+
const { path, entry } = allEntries[idx];
|
|
71
|
+
results[idx] = await fetchAndSanitize(path, entry, token);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
await Promise.all(Array.from({ length: Math.min(PARALLELISM, allEntries.length) }, worker));
|
|
75
|
+
const seen = /* @__PURE__ */ new Set();
|
|
76
|
+
const icons = [];
|
|
77
|
+
for (const r of results) {
|
|
78
|
+
if (!r) continue;
|
|
79
|
+
if (seen.has(r.id)) continue;
|
|
80
|
+
seen.add(r.id);
|
|
81
|
+
icons.push(r);
|
|
82
|
+
}
|
|
83
|
+
return Response.json({
|
|
84
|
+
icons,
|
|
85
|
+
paths: yieldedPaths,
|
|
86
|
+
discovered
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
async function fetchAndSanitize(path, entry, token) {
|
|
90
|
+
const baseName = entry.name.replace(/\.svg$/i, "");
|
|
91
|
+
const url = entry.download_url;
|
|
92
|
+
if (!url) return null;
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = new URL(url);
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const host = parsed.hostname;
|
|
100
|
+
const githubHost = host === "raw.githubusercontent.com" || host.endsWith(".githubusercontent.com");
|
|
101
|
+
if (!githubHost) return null;
|
|
102
|
+
try {
|
|
103
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
104
|
+
if (!res.ok) return null;
|
|
105
|
+
const raw = await res.text();
|
|
106
|
+
const svg = sanitizeSvg(raw);
|
|
107
|
+
if (!svg) return null;
|
|
108
|
+
return { id: `${path}/${baseName}`, name: baseName, svg, source: path };
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
GET
|
|
115
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { APIRoute } from 'astro';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/setzkasten/init/add-section
|
|
5
|
+
*
|
|
6
|
+
* Adds a single section to an already-initialized Setzkasten project.
|
|
7
|
+
* Updates setzkasten.config.ts, creates the content JSON, and updates the page config.
|
|
8
|
+
* All changes are committed as a single batch via Git Trees API.
|
|
9
|
+
*
|
|
10
|
+
* Body: { owner, repo, branch?, projectRoot, section, pageKey, contentPath? }
|
|
11
|
+
*/
|
|
12
|
+
declare const POST: APIRoute;
|
|
13
|
+
/**
|
|
14
|
+
* Add import statement and SECTION_COMPONENTS registry entry to a page file.
|
|
15
|
+
* Returns the patched source, or null if no changes needed.
|
|
16
|
+
*/
|
|
17
|
+
declare function patchPageFile(source: string, sectionKey: string, componentName: string, componentPath: string, pagePath: string): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Calculate a relative import path from a page directory to a component file.
|
|
20
|
+
*/
|
|
21
|
+
declare function calculateRelativePath(fromDir: string, toPath: string): string;
|
|
22
|
+
|
|
23
|
+
export { POST, calculateRelativePath, patchPageFile };
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import {
|
|
2
|
+
patchTemplateForFields,
|
|
3
|
+
stripTemplateFallbacks
|
|
4
|
+
} from "../chunk-RHJONMLK.js";
|
|
5
|
+
import {
|
|
6
|
+
prefixPath,
|
|
7
|
+
resolveStorageConfigForRequest
|
|
8
|
+
} from "../chunk-6UIKVKED.js";
|
|
9
|
+
import {
|
|
10
|
+
resolveGitHubTokenForRequest
|
|
11
|
+
} from "../chunk-NKDATSPA.js";
|
|
12
|
+
import {
|
|
13
|
+
withTrailers
|
|
14
|
+
} from "../chunk-KH22FJO5.js";
|
|
15
|
+
|
|
16
|
+
// src/api-routes/init-add-section.ts
|
|
17
|
+
import { addSectionToConfig } from "@setzkasten-cms/core/init";
|
|
18
|
+
var POST = async ({ request, cookies }) => {
|
|
19
|
+
const session = cookies.get("setzkasten_session")?.value;
|
|
20
|
+
if (!session) {
|
|
21
|
+
return new Response("Unauthorized", { status: 401 });
|
|
22
|
+
}
|
|
23
|
+
const tokenResult = await resolveGitHubTokenForRequest(request);
|
|
24
|
+
if (!tokenResult.ok) {
|
|
25
|
+
return new Response(tokenResult.error.message, { status: 500 });
|
|
26
|
+
}
|
|
27
|
+
const githubToken = tokenResult.value;
|
|
28
|
+
try {
|
|
29
|
+
const body = await request.json();
|
|
30
|
+
const storage = await resolveStorageConfigForRequest(request, body);
|
|
31
|
+
if (!storage) {
|
|
32
|
+
return Response.json({ error: "Could not resolve owner/repo. Set SETZKASTEN_OWNER and SETZKASTEN_REPO env vars." }, { status: 400 });
|
|
33
|
+
}
|
|
34
|
+
const { owner, repo, branch, projectPrefix } = storage;
|
|
35
|
+
const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
|
|
36
|
+
const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
|
|
37
|
+
const { section, pageKey } = body;
|
|
38
|
+
if (!section || !pageKey) {
|
|
39
|
+
return Response.json({ error: "section and pageKey are required" }, { status: 400 });
|
|
40
|
+
}
|
|
41
|
+
const headers = {
|
|
42
|
+
Authorization: `Bearer ${githubToken}`,
|
|
43
|
+
Accept: "application/vnd.github+json",
|
|
44
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
45
|
+
"Content-Type": "application/json"
|
|
46
|
+
};
|
|
47
|
+
const filesToCommit = [];
|
|
48
|
+
const configPath = prefixPath("setzkasten.config.ts", projectPrefix);
|
|
49
|
+
const existingConfig = await fetchFileContent(owner, repo, branch, configPath, githubToken);
|
|
50
|
+
if (existingConfig) {
|
|
51
|
+
const updatedConfig = addSectionToConfig(existingConfig, section.key, section, section.allFields);
|
|
52
|
+
if (updatedConfig) {
|
|
53
|
+
filesToCommit.push({ path: configPath, content: updatedConfig });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const sectionJsonPath = `${contentPath}/_sections/${section.key}.json`;
|
|
57
|
+
const existingSectionJson = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
|
|
58
|
+
let sectionData = {};
|
|
59
|
+
if (existingSectionJson) {
|
|
60
|
+
try {
|
|
61
|
+
sectionData = JSON.parse(existingSectionJson);
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const field of section.fields) {
|
|
66
|
+
if (!(field.key in sectionData)) {
|
|
67
|
+
let value = field.defaultValue ?? getDefaultValue(field.type);
|
|
68
|
+
if (Array.isArray(value)) value = value.filter((item) => item != null);
|
|
69
|
+
sectionData[field.key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const orderedKeys = (section.allFields ?? section.fields).map((f) => f.key);
|
|
73
|
+
const orderedData = {};
|
|
74
|
+
for (const key of orderedKeys) {
|
|
75
|
+
if (key in sectionData) {
|
|
76
|
+
orderedData[key] = sectionData[key];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const key of Object.keys(sectionData)) {
|
|
80
|
+
if (!(key in orderedData)) {
|
|
81
|
+
orderedData[key] = sectionData[key];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
filesToCommit.push({
|
|
85
|
+
path: sectionJsonPath,
|
|
86
|
+
content: JSON.stringify(orderedData, null, 2)
|
|
87
|
+
});
|
|
88
|
+
const configKey = "_" + pageKey.replace(/\//g, "_");
|
|
89
|
+
const pageConfigPath = `${contentPath}/pages/${configKey}.json`;
|
|
90
|
+
const existingPageConfig = await fetchFileContent(owner, repo, branch, pageConfigPath, githubToken);
|
|
91
|
+
let pageConfig;
|
|
92
|
+
if (existingPageConfig) {
|
|
93
|
+
pageConfig = JSON.parse(existingPageConfig);
|
|
94
|
+
const normalizeKey = (k) => k.replace(/[-_]/g, "").toLowerCase();
|
|
95
|
+
const normalizedNewKey = normalizeKey(section.key);
|
|
96
|
+
if (!pageConfig.sections.some((s) => normalizeKey(s.key) === normalizedNewKey)) {
|
|
97
|
+
pageConfig.sections.push({ key: section.key, enabled: true });
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
pageConfig = {
|
|
101
|
+
sections: [{ key: section.key, enabled: true }]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
filesToCommit.push({
|
|
105
|
+
path: pageConfigPath,
|
|
106
|
+
content: JSON.stringify(pageConfig, null, 2)
|
|
107
|
+
});
|
|
108
|
+
if (section.isPageLevel && body.pagePath) {
|
|
109
|
+
let fullPagePath = prefixPath(body.pagePath, projectPrefix);
|
|
110
|
+
let pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken);
|
|
111
|
+
let resolvedPagePath = body.pagePath;
|
|
112
|
+
if (!pageSource && body.pagePath.endsWith(".astro")) {
|
|
113
|
+
const indexPath = body.pagePath.replace(/\.astro$/, "/index.astro");
|
|
114
|
+
fullPagePath = prefixPath(indexPath, projectPrefix);
|
|
115
|
+
pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken);
|
|
116
|
+
if (pageSource) resolvedPagePath = indexPath;
|
|
117
|
+
}
|
|
118
|
+
if (pageSource) {
|
|
119
|
+
const repeatedGroups = section._analyzerResult?.repeatedGroups ?? [];
|
|
120
|
+
let patchedSource = await patchTemplateForFields(
|
|
121
|
+
pageSource,
|
|
122
|
+
section.key,
|
|
123
|
+
section.allFields ?? section.fields,
|
|
124
|
+
repeatedGroups,
|
|
125
|
+
{ mode: "page" }
|
|
126
|
+
);
|
|
127
|
+
const { source: strippedSource, fallbacks } = stripTemplateFallbacks(patchedSource);
|
|
128
|
+
if (strippedSource !== patchedSource) {
|
|
129
|
+
patchedSource = strippedSource;
|
|
130
|
+
let jsonUpdated = false;
|
|
131
|
+
for (const [key, value] of Object.entries(fallbacks)) {
|
|
132
|
+
const existing = sectionData[key];
|
|
133
|
+
const existingIsPlain = typeof existing === "string" && !/<[a-z]/.test(existing);
|
|
134
|
+
const newIsHtml = typeof value === "string" && /<[a-z]/.test(value);
|
|
135
|
+
if (!(key in sectionData) || existing === "" || existing === null || existingIsPlain && newIsHtml) {
|
|
136
|
+
sectionData[key] = value;
|
|
137
|
+
jsonUpdated = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (jsonUpdated) {
|
|
141
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath);
|
|
142
|
+
if (jsonIdx !== -1) {
|
|
143
|
+
filesToCommit[jsonIdx].content = JSON.stringify(sectionData, null, 2);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (patchedSource !== pageSource) {
|
|
148
|
+
filesToCommit.push({ path: fullPagePath, content: patchedSource });
|
|
149
|
+
const previewCopySource = patchedSource.replace(/\bexport\s+const\s+prerender\s*=\s*true\s*;?\s*\n?/, "").replace(/(from\s+')(\.\.\/)/g, "$1../$2").replace(/(from\s+")(\.\.\/)/g, "$1../$2");
|
|
150
|
+
const relativePage = resolvedPagePath.replace(/^src\/pages\//, "");
|
|
151
|
+
const previewCopyPath = prefixPath(`src/pages/sk-preview/${relativePage}`, projectPrefix);
|
|
152
|
+
filesToCommit.push({ path: previewCopyPath, content: previewCopySource });
|
|
153
|
+
}
|
|
154
|
+
const fields = section.allFields ?? section.fields;
|
|
155
|
+
for (const g of repeatedGroups) {
|
|
156
|
+
const topField = fields.find((f) => f.key === g.fieldKey);
|
|
157
|
+
if (!topField || !Array.isArray(topField.defaultValue)) continue;
|
|
158
|
+
sectionData[g.fieldKey] = topField.defaultValue.filter((item) => item != null);
|
|
159
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath);
|
|
160
|
+
if (jsonIdx !== -1) {
|
|
161
|
+
filesToCommit[jsonIdx].content = JSON.stringify(sectionData, null, 2);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else if (section.componentPath) {
|
|
166
|
+
const componentSource = await fetchFileContent(owner, repo, branch, section.componentPath, githubToken);
|
|
167
|
+
if (componentSource) {
|
|
168
|
+
const repeatedGroups = section._analyzerResult?.repeatedGroups ?? [];
|
|
169
|
+
const patchedSource = await patchTemplateForFields(componentSource, section.key, section.allFields ?? section.fields, repeatedGroups);
|
|
170
|
+
if (patchedSource !== componentSource) {
|
|
171
|
+
filesToCommit.push({ path: section.componentPath, content: patchedSource });
|
|
172
|
+
}
|
|
173
|
+
const fields = section.allFields ?? section.fields;
|
|
174
|
+
for (const g of repeatedGroups) {
|
|
175
|
+
const topField = fields.find((f) => f.key === g.fieldKey);
|
|
176
|
+
if (!topField || !Array.isArray(topField.defaultValue)) continue;
|
|
177
|
+
const items = topField.defaultValue.filter((item) => item != null);
|
|
178
|
+
sectionData[g.fieldKey] = items;
|
|
179
|
+
const jsonIdx = filesToCommit.findIndex((f) => f.path === sectionJsonPath);
|
|
180
|
+
if (jsonIdx !== -1) {
|
|
181
|
+
filesToCommit[jsonIdx].content = JSON.stringify(sectionData, null, 2);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (body.pagePath && section.componentName) {
|
|
186
|
+
const fullPagePath = prefixPath(body.pagePath, projectPrefix);
|
|
187
|
+
const pageSource = await fetchFileContent(owner, repo, branch, fullPagePath, githubToken);
|
|
188
|
+
if (pageSource) {
|
|
189
|
+
const patched = patchPageFile(pageSource, section.key, section.componentName, section.componentPath, body.pagePath);
|
|
190
|
+
if (patched && patched !== pageSource) {
|
|
191
|
+
filesToCommit.push({ path: fullPagePath, content: patched });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const pageDir = body.pagePath.replace(/\/[^/]+$/, "");
|
|
195
|
+
const previewPath = prefixPath(`${pageDir}/sk-preview/[...page].astro`, projectPrefix);
|
|
196
|
+
const previewSource = await fetchFileContent(owner, repo, branch, previewPath, githubToken);
|
|
197
|
+
if (previewSource) {
|
|
198
|
+
const patchedPreview = patchPageFile(previewSource, section.key, section.componentName, section.componentPath, `${pageDir}/sk-preview/[...page].astro`);
|
|
199
|
+
if (patchedPreview && patchedPreview !== previewSource) {
|
|
200
|
+
filesToCommit.push({ path: previewPath, content: patchedPreview });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (filesToCommit.length === 0) {
|
|
206
|
+
return Response.json({ error: "Nothing to commit" }, { status: 400 });
|
|
207
|
+
}
|
|
208
|
+
const commitResult = await batchCommit(
|
|
209
|
+
owner,
|
|
210
|
+
repo,
|
|
211
|
+
branch,
|
|
212
|
+
filesToCommit,
|
|
213
|
+
withTrailers(existingSectionJson ? `content: update ${section.key} section \u2014 add new fields` : `content: add ${section.key} section to Setzkasten`),
|
|
214
|
+
headers
|
|
215
|
+
);
|
|
216
|
+
if (!commitResult.ok) {
|
|
217
|
+
return Response.json({ error: commitResult.error }, { status: 500 });
|
|
218
|
+
}
|
|
219
|
+
const { recordPageEdit } = await import("./_pages-meta-store.js");
|
|
220
|
+
await recordPageEdit(
|
|
221
|
+
{ owner, repo, branch, contentPath, token: tokenResult.value },
|
|
222
|
+
pageKey
|
|
223
|
+
).catch(() => {
|
|
224
|
+
});
|
|
225
|
+
return Response.json({
|
|
226
|
+
success: true,
|
|
227
|
+
commitSha: commitResult.sha,
|
|
228
|
+
filesWritten: filesToCommit.map((f) => f.path)
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.error("[setzkasten] add-section error:", error);
|
|
232
|
+
return Response.json(
|
|
233
|
+
{ error: error instanceof Error ? error.message : "Add section failed" },
|
|
234
|
+
{ status: 500 }
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
function getDefaultValue(fieldType) {
|
|
239
|
+
switch (fieldType) {
|
|
240
|
+
case "text":
|
|
241
|
+
return "";
|
|
242
|
+
case "number":
|
|
243
|
+
return 0;
|
|
244
|
+
case "boolean":
|
|
245
|
+
return false;
|
|
246
|
+
case "image":
|
|
247
|
+
return { path: "", alt: "" };
|
|
248
|
+
case "array":
|
|
249
|
+
return [];
|
|
250
|
+
case "color":
|
|
251
|
+
return "#000000";
|
|
252
|
+
case "date":
|
|
253
|
+
return "";
|
|
254
|
+
case "icon":
|
|
255
|
+
return "";
|
|
256
|
+
default:
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function patchPageFile(source, sectionKey, componentName, componentPath, pagePath) {
|
|
261
|
+
const normalize = (k) => k.replace(/[-_]/g, "").toLowerCase();
|
|
262
|
+
if (source.includes(`import ${componentName}`) || source.includes(`'${componentName}'`)) {
|
|
263
|
+
if (source.includes(normalize(sectionKey))) return null;
|
|
264
|
+
}
|
|
265
|
+
const pageDir = pagePath.replace(/\/[^/]+$/, "");
|
|
266
|
+
const relPath = calculateRelativePath(pageDir, componentPath);
|
|
267
|
+
let patched = source;
|
|
268
|
+
const lastImportMatch = patched.match(/import\s+\w+\s+from\s+['"][^'"]*Section\.astro['"];?\s*\n/);
|
|
269
|
+
if (lastImportMatch) {
|
|
270
|
+
let lastIdx = 0;
|
|
271
|
+
const importRegex = /import\s+\w+\s+from\s+['"][^'"]*Section\.astro['"];?\s*\n/g;
|
|
272
|
+
let m;
|
|
273
|
+
while ((m = importRegex.exec(patched)) !== null) {
|
|
274
|
+
lastIdx = m.index + m[0].length;
|
|
275
|
+
}
|
|
276
|
+
const importLine = `import ${componentName} from '${relPath}';
|
|
277
|
+
`;
|
|
278
|
+
if (!patched.includes(`import ${componentName}`)) {
|
|
279
|
+
patched = patched.slice(0, lastIdx) + importLine + patched.slice(lastIdx);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const registryPattern = /SECTION_COMPONENTS[^{]*\{([^}]*)\}/s;
|
|
283
|
+
const registryMatch = patched.match(registryPattern);
|
|
284
|
+
if (registryMatch) {
|
|
285
|
+
const registryContent = registryMatch[1];
|
|
286
|
+
if (!registryContent.includes(normalize(sectionKey))) {
|
|
287
|
+
const lastEntryMatch = registryContent.match(/.*\w+Section,?\s*$/m);
|
|
288
|
+
if (lastEntryMatch && lastEntryMatch.index !== void 0) {
|
|
289
|
+
const insertPos = registryMatch.index + registryMatch[0].indexOf(registryContent) + lastEntryMatch.index + lastEntryMatch[0].length;
|
|
290
|
+
const newEntry = `
|
|
291
|
+
[normalize('${sectionKey}')]: ${componentName},`;
|
|
292
|
+
patched = patched.slice(0, insertPos) + newEntry + patched.slice(insertPos);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const hardcodedPattern = new RegExp(`\\s*<${componentName}\\s*/?>\\s*\\n?`, "g");
|
|
297
|
+
patched = patched.replace(hardcodedPattern, "\n");
|
|
298
|
+
return patched !== source ? patched : null;
|
|
299
|
+
}
|
|
300
|
+
function calculateRelativePath(fromDir, toPath) {
|
|
301
|
+
const fromParts = fromDir.split("/");
|
|
302
|
+
const toParts = toPath.split("/");
|
|
303
|
+
let common = 0;
|
|
304
|
+
while (common < fromParts.length && common < toParts.length && fromParts[common] === toParts[common]) {
|
|
305
|
+
common++;
|
|
306
|
+
}
|
|
307
|
+
const ups = fromParts.length - common;
|
|
308
|
+
const remaining = toParts.slice(common).join("/");
|
|
309
|
+
if (ups === 0) return "./" + remaining;
|
|
310
|
+
return "../".repeat(ups) + remaining;
|
|
311
|
+
}
|
|
312
|
+
async function fetchFileContent(owner, repo, branch, path, token) {
|
|
313
|
+
try {
|
|
314
|
+
const response = await fetch(
|
|
315
|
+
`https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
|
|
316
|
+
{
|
|
317
|
+
headers: {
|
|
318
|
+
Authorization: `Bearer ${token}`,
|
|
319
|
+
Accept: "application/vnd.github+json",
|
|
320
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
if (!response.ok) return null;
|
|
325
|
+
const data = await response.json();
|
|
326
|
+
return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function batchCommit(owner, repo, branch, files, message, headers) {
|
|
332
|
+
try {
|
|
333
|
+
const refRes = await fetch(
|
|
334
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
335
|
+
{ headers }
|
|
336
|
+
);
|
|
337
|
+
if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
|
|
338
|
+
const refData = await refRes.json();
|
|
339
|
+
const headSha = refData.object.sha;
|
|
340
|
+
const commitRes = await fetch(
|
|
341
|
+
`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`,
|
|
342
|
+
{ headers }
|
|
343
|
+
);
|
|
344
|
+
if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
|
|
345
|
+
const commitData = await commitRes.json();
|
|
346
|
+
const treeRes = await fetch(
|
|
347
|
+
`https://api.github.com/repos/${owner}/${repo}/git/trees`,
|
|
348
|
+
{
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers,
|
|
351
|
+
body: JSON.stringify({
|
|
352
|
+
base_tree: commitData.tree.sha,
|
|
353
|
+
tree: files.map((f) => ({
|
|
354
|
+
path: f.path,
|
|
355
|
+
mode: "100644",
|
|
356
|
+
type: "blob",
|
|
357
|
+
content: f.content
|
|
358
|
+
}))
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
|
|
363
|
+
const treeData = await treeRes.json();
|
|
364
|
+
const newCommitRes = await fetch(
|
|
365
|
+
`https://api.github.com/repos/${owner}/${repo}/git/commits`,
|
|
366
|
+
{
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers,
|
|
369
|
+
body: JSON.stringify({
|
|
370
|
+
tree: treeData.sha,
|
|
371
|
+
parents: [headSha],
|
|
372
|
+
message
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
|
|
377
|
+
const newCommitData = await newCommitRes.json();
|
|
378
|
+
const updateRes = await fetch(
|
|
379
|
+
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
|
|
380
|
+
{
|
|
381
|
+
method: "PATCH",
|
|
382
|
+
headers,
|
|
383
|
+
body: JSON.stringify({ sha: newCommitData.sha })
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
|
|
387
|
+
return { ok: true, sha: newCommitData.sha };
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return { ok: false, error: error instanceof Error ? error.message : "Commit failed" };
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
export {
|
|
393
|
+
POST,
|
|
394
|
+
calculateRelativePath,
|
|
395
|
+
patchPageFile
|
|
396
|
+
};
|