@glw907/cairn-cms 0.5.0 → 0.6.0-rc.0
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/auth/crypto.d.ts +13 -0
- package/dist/auth/crypto.d.ts.map +1 -0
- package/dist/auth/crypto.js +31 -0
- package/dist/auth/store.d.ts +41 -0
- package/dist/auth/store.d.ts.map +1 -0
- package/dist/auth/store.js +115 -0
- package/dist/auth/types.d.ts +25 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +1 -0
- package/dist/components/AdminLayout.svelte +58 -108
- package/dist/components/AdminLayout.svelte.d.ts +14 -9
- package/dist/components/AdminLayout.svelte.d.ts.map +1 -1
- package/dist/components/ComponentPalette.svelte +50 -0
- package/dist/components/ComponentPalette.svelte.d.ts +16 -0
- package/dist/components/ComponentPalette.svelte.d.ts.map +1 -0
- package/dist/components/ConceptList.svelte +81 -0
- package/dist/components/ConceptList.svelte.d.ts +13 -0
- package/dist/components/ConceptList.svelte.d.ts.map +1 -0
- package/dist/components/ConfirmPage.svelte +23 -20
- package/dist/components/ConfirmPage.svelte.d.ts +6 -0
- package/dist/components/ConfirmPage.svelte.d.ts.map +1 -1
- package/dist/components/EditPage.svelte +160 -103
- package/dist/components/EditPage.svelte.d.ts +17 -7
- package/dist/components/EditPage.svelte.d.ts.map +1 -1
- package/dist/components/LoginPage.svelte +42 -52
- package/dist/components/LoginPage.svelte.d.ts +12 -0
- package/dist/components/LoginPage.svelte.d.ts.map +1 -1
- package/dist/components/ManageEditors.svelte +81 -0
- package/dist/components/ManageEditors.svelte.d.ts +24 -0
- package/dist/components/ManageEditors.svelte.d.ts.map +1 -0
- package/dist/components/MarkdownEditor.svelte +81 -0
- package/dist/components/MarkdownEditor.svelte.d.ts +20 -0
- package/dist/components/MarkdownEditor.svelte.d.ts.map +1 -0
- package/dist/components/NavTree.svelte +138 -0
- package/dist/components/NavTree.svelte.d.ts +17 -0
- package/dist/components/NavTree.svelte.d.ts.map +1 -0
- package/dist/components/cairn-admin.css +42 -0
- package/dist/components/index.d.ts +5 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +7 -4
- package/dist/content/compose.d.ts +7 -0
- package/dist/content/compose.d.ts.map +1 -0
- package/dist/content/compose.js +32 -0
- package/dist/content/concepts.d.ts +17 -0
- package/dist/content/concepts.d.ts.map +1 -0
- package/dist/content/concepts.js +41 -0
- package/dist/content/frontmatter.d.ts +18 -0
- package/dist/content/frontmatter.d.ts.map +1 -0
- package/dist/content/frontmatter.js +58 -0
- package/dist/content/ids.d.ts +17 -0
- package/dist/content/ids.d.ts.map +1 -0
- package/dist/content/ids.js +33 -0
- package/dist/content/types.d.ts +210 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +1 -0
- package/dist/content/validate.d.ts +13 -0
- package/dist/content/validate.d.ts.map +1 -0
- package/dist/content/validate.js +45 -0
- package/dist/email.d.ts +25 -12
- package/dist/email.d.ts.map +1 -1
- package/dist/email.js +24 -24
- package/dist/env.d.ts +24 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +29 -0
- package/dist/github/credentials.d.ts +12 -0
- package/dist/github/credentials.d.ts.map +1 -0
- package/dist/github/credentials.js +11 -0
- package/dist/github/repo.d.ts +49 -0
- package/dist/github/repo.d.ts.map +1 -0
- package/dist/github/repo.js +123 -0
- package/dist/github/signing.d.ts +17 -0
- package/dist/github/signing.d.ts.map +1 -0
- package/dist/github/signing.js +79 -0
- package/dist/github/types.d.ts +35 -0
- package/dist/github/types.d.ts.map +1 -0
- package/dist/github/types.js +19 -0
- package/dist/index.d.ts +27 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -8
- package/dist/nav/site-config.d.ts +50 -0
- package/dist/nav/site-config.d.ts.map +1 -0
- package/dist/nav/site-config.js +100 -0
- package/dist/render/glyph.d.ts +1 -1
- package/dist/render/glyph.d.ts.map +1 -1
- package/dist/render/index.d.ts +5 -5
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +6 -6
- package/dist/render/pipeline.d.ts +3 -3
- package/dist/render/pipeline.d.ts.map +1 -1
- package/dist/render/pipeline.js +4 -4
- package/dist/render/registry.d.ts +6 -4
- package/dist/render/registry.d.ts.map +1 -1
- package/dist/render/registry.js +8 -6
- package/dist/render/rehype-dispatch.d.ts +1 -1
- package/dist/render/rehype-dispatch.d.ts.map +1 -1
- package/dist/render/remark-directives.d.ts +1 -1
- package/dist/render/remark-directives.d.ts.map +1 -1
- package/dist/render/sanitize.d.ts +8 -0
- package/dist/render/sanitize.d.ts.map +1 -0
- package/dist/render/sanitize.js +26 -0
- package/dist/sveltekit/auth-routes.d.ts +23 -0
- package/dist/sveltekit/auth-routes.d.ts.map +1 -0
- package/dist/sveltekit/auth-routes.js +85 -0
- package/dist/sveltekit/content-routes.d.ts +80 -0
- package/dist/sveltekit/content-routes.d.ts.map +1 -0
- package/dist/sveltekit/content-routes.js +183 -0
- package/dist/sveltekit/editors-routes.d.ts +24 -0
- package/dist/sveltekit/editors-routes.d.ts.map +1 -0
- package/dist/sveltekit/editors-routes.js +73 -0
- package/dist/sveltekit/guard.d.ts +9 -0
- package/dist/sveltekit/guard.d.ts.map +1 -0
- package/dist/sveltekit/guard.js +43 -0
- package/dist/sveltekit/health.d.ts +19 -0
- package/dist/sveltekit/health.d.ts.map +1 -0
- package/dist/sveltekit/health.js +12 -0
- package/dist/sveltekit/index.d.ts +9 -83
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/index.js +8 -149
- package/dist/sveltekit/nav-routes.d.ts +30 -0
- package/dist/sveltekit/nav-routes.d.ts.map +1 -0
- package/dist/sveltekit/nav-routes.js +103 -0
- package/dist/sveltekit/types.d.ts +32 -0
- package/dist/sveltekit/types.d.ts.map +1 -0
- package/dist/sveltekit/types.js +1 -0
- package/package.json +38 -58
- package/src/lib/auth/crypto.ts +37 -0
- package/src/lib/auth/store.ts +158 -0
- package/src/lib/auth/types.ts +27 -0
- package/src/lib/components/AdminLayout.svelte +58 -108
- package/src/lib/components/ComponentPalette.svelte +50 -0
- package/src/lib/components/ConceptList.svelte +81 -0
- package/src/lib/components/ConfirmPage.svelte +23 -20
- package/src/lib/components/EditPage.svelte +160 -103
- package/src/lib/components/LoginPage.svelte +42 -52
- package/src/lib/components/ManageEditors.svelte +81 -0
- package/src/lib/components/MarkdownEditor.svelte +81 -0
- package/src/lib/components/NavTree.svelte +138 -0
- package/src/lib/components/cairn-admin.css +42 -0
- package/src/lib/components/index.ts +7 -4
- package/src/lib/content/compose.ts +39 -0
- package/src/lib/content/concepts.ts +57 -0
- package/src/lib/content/frontmatter.ts +71 -0
- package/src/lib/content/ids.ts +38 -0
- package/src/lib/content/types.ts +235 -0
- package/src/lib/content/validate.ts +51 -0
- package/src/lib/email.ts +52 -38
- package/src/lib/env.ts +32 -0
- package/src/lib/github/credentials.ts +27 -0
- package/src/lib/github/repo.ts +138 -0
- package/src/lib/github/signing.ts +97 -0
- package/src/lib/github/types.ts +46 -0
- package/src/lib/index.ts +86 -8
- package/src/lib/nav/site-config.ts +124 -0
- package/src/lib/render/glyph.ts +6 -6
- package/src/lib/render/index.ts +6 -6
- package/src/lib/render/pipeline.ts +22 -22
- package/src/lib/render/registry.ts +33 -26
- package/src/lib/render/rehype-dispatch.ts +47 -47
- package/src/lib/render/remark-directives.ts +46 -46
- package/src/lib/render/sanitize.ts +27 -0
- package/src/lib/sveltekit/auth-routes.ts +107 -0
- package/src/lib/sveltekit/content-routes.ts +261 -0
- package/src/lib/sveltekit/editors-routes.ts +82 -0
- package/src/lib/sveltekit/guard.ts +47 -0
- package/src/lib/sveltekit/health.ts +24 -0
- package/src/lib/sveltekit/index.ts +19 -235
- package/src/lib/sveltekit/nav-routes.ts +139 -0
- package/src/lib/sveltekit/types.ts +33 -0
- package/dist/adapter.d.ts +0 -69
- package/dist/adapter.d.ts.map +0 -1
- package/dist/adapter.js +0 -30
- package/dist/auth/admins.d.ts +0 -33
- package/dist/auth/admins.d.ts.map +0 -1
- package/dist/auth/admins.js +0 -90
- package/dist/auth/config.d.ts +0 -2097
- package/dist/auth/config.d.ts.map +0 -1
- package/dist/auth/config.js +0 -78
- package/dist/auth/guard.d.ts +0 -34
- package/dist/auth/guard.d.ts.map +0 -1
- package/dist/auth/guard.js +0 -47
- package/dist/auth/index.d.ts +0 -4
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -6
- package/dist/auth/schema.d.ts +0 -750
- package/dist/auth/schema.d.ts.map +0 -1
- package/dist/auth/schema.js +0 -93
- package/dist/carta.d.ts +0 -39
- package/dist/carta.d.ts.map +0 -1
- package/dist/carta.js +0 -30
- package/dist/components/AdminList.svelte +0 -33
- package/dist/components/AdminList.svelte.d.ts +0 -10
- package/dist/components/AdminList.svelte.d.ts.map +0 -1
- package/dist/components/ManageAdmins.svelte +0 -84
- package/dist/components/ManageAdmins.svelte.d.ts +0 -10
- package/dist/components/ManageAdmins.svelte.d.ts.map +0 -1
- package/dist/content.d.ts +0 -3
- package/dist/content.d.ts.map +0 -1
- package/dist/content.js +0 -10
- package/dist/github.d.ts +0 -72
- package/dist/github.d.ts.map +0 -1
- package/dist/github.js +0 -171
- package/dist/utils.d.ts +0 -3
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -11
- package/src/lib/adapter.ts +0 -119
- package/src/lib/auth/admins.ts +0 -106
- package/src/lib/auth/config.ts +0 -108
- package/src/lib/auth/guard.ts +0 -60
- package/src/lib/auth/index.ts +0 -6
- package/src/lib/auth/schema.ts +0 -112
- package/src/lib/carta.ts +0 -59
- package/src/lib/components/AdminList.svelte +0 -33
- package/src/lib/components/ManageAdmins.svelte +0 -84
- package/src/lib/content.ts +0 -11
- package/src/lib/github.ts +0 -220
- package/src/lib/utils.ts +0 -12
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/lib/auth/schema.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBf,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBnB,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BnB,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBxB,CAAC;AAEF,eAAO,MAAM,aAAa;;;EAGvB,CAAC;AAEJ,eAAO,MAAM,gBAAgB;;EAK1B,CAAC;AAEJ,eAAO,MAAM,gBAAgB;;EAK1B,CAAC"}
|
package/dist/auth/schema.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { relations, sql } from "drizzle-orm";
|
|
2
|
-
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
|
|
3
|
-
export const user = sqliteTable("user", {
|
|
4
|
-
id: text("id").primaryKey(),
|
|
5
|
-
name: text("name").notNull(),
|
|
6
|
-
email: text("email").notNull().unique(),
|
|
7
|
-
emailVerified: integer("email_verified", { mode: "boolean" })
|
|
8
|
-
.default(false)
|
|
9
|
-
.notNull(),
|
|
10
|
-
image: text("image"),
|
|
11
|
-
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
12
|
-
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
13
|
-
.notNull(),
|
|
14
|
-
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
15
|
-
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
16
|
-
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
17
|
-
.notNull(),
|
|
18
|
-
role: text("role"),
|
|
19
|
-
banned: integer("banned", { mode: "boolean" }).default(false),
|
|
20
|
-
banReason: text("ban_reason"),
|
|
21
|
-
banExpires: integer("ban_expires", { mode: "timestamp_ms" }),
|
|
22
|
-
});
|
|
23
|
-
export const session = sqliteTable("session", {
|
|
24
|
-
id: text("id").primaryKey(),
|
|
25
|
-
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
26
|
-
token: text("token").notNull().unique(),
|
|
27
|
-
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
28
|
-
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
29
|
-
.notNull(),
|
|
30
|
-
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
31
|
-
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
32
|
-
.notNull(),
|
|
33
|
-
ipAddress: text("ip_address"),
|
|
34
|
-
userAgent: text("user_agent"),
|
|
35
|
-
userId: text("user_id")
|
|
36
|
-
.notNull()
|
|
37
|
-
.references(() => user.id, { onDelete: "cascade" }),
|
|
38
|
-
impersonatedBy: text("impersonated_by"),
|
|
39
|
-
}, (table) => [index("session_userId_idx").on(table.userId)]);
|
|
40
|
-
export const account = sqliteTable("account", {
|
|
41
|
-
id: text("id").primaryKey(),
|
|
42
|
-
accountId: text("account_id").notNull(),
|
|
43
|
-
providerId: text("provider_id").notNull(),
|
|
44
|
-
userId: text("user_id")
|
|
45
|
-
.notNull()
|
|
46
|
-
.references(() => user.id, { onDelete: "cascade" }),
|
|
47
|
-
accessToken: text("access_token"),
|
|
48
|
-
refreshToken: text("refresh_token"),
|
|
49
|
-
idToken: text("id_token"),
|
|
50
|
-
accessTokenExpiresAt: integer("access_token_expires_at", {
|
|
51
|
-
mode: "timestamp_ms",
|
|
52
|
-
}),
|
|
53
|
-
refreshTokenExpiresAt: integer("refresh_token_expires_at", {
|
|
54
|
-
mode: "timestamp_ms",
|
|
55
|
-
}),
|
|
56
|
-
scope: text("scope"),
|
|
57
|
-
password: text("password"),
|
|
58
|
-
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
59
|
-
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
60
|
-
.notNull(),
|
|
61
|
-
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
62
|
-
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
63
|
-
.notNull(),
|
|
64
|
-
}, (table) => [index("account_userId_idx").on(table.userId)]);
|
|
65
|
-
export const verification = sqliteTable("verification", {
|
|
66
|
-
id: text("id").primaryKey(),
|
|
67
|
-
identifier: text("identifier").notNull(),
|
|
68
|
-
value: text("value").notNull(),
|
|
69
|
-
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
|
|
70
|
-
createdAt: integer("created_at", { mode: "timestamp_ms" })
|
|
71
|
-
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
72
|
-
.notNull(),
|
|
73
|
-
updatedAt: integer("updated_at", { mode: "timestamp_ms" })
|
|
74
|
-
.default(sql `(cast(unixepoch('subsecond') * 1000 as integer))`)
|
|
75
|
-
.$onUpdate(() => /* @__PURE__ */ new Date())
|
|
76
|
-
.notNull(),
|
|
77
|
-
}, (table) => [index("verification_identifier_idx").on(table.identifier)]);
|
|
78
|
-
export const userRelations = relations(user, ({ many }) => ({
|
|
79
|
-
sessions: many(session),
|
|
80
|
-
accounts: many(account),
|
|
81
|
-
}));
|
|
82
|
-
export const sessionRelations = relations(session, ({ one }) => ({
|
|
83
|
-
user: one(user, {
|
|
84
|
-
fields: [session.userId],
|
|
85
|
-
references: [user.id],
|
|
86
|
-
}),
|
|
87
|
-
}));
|
|
88
|
-
export const accountRelations = relations(account, ({ one }) => ({
|
|
89
|
-
user: one(user, {
|
|
90
|
-
fields: [account.userId],
|
|
91
|
-
references: [user.id],
|
|
92
|
-
}),
|
|
93
|
-
}));
|
package/dist/carta.d.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { Pluggable, Processor } from 'unified';
|
|
2
|
-
export interface PreviewPlugins {
|
|
3
|
-
/** remark plugins, injected after gfm and before remark-rehype. */
|
|
4
|
-
remarkPlugins: Pluggable[];
|
|
5
|
-
/** rehype plugins, injected after remark-rehype. */
|
|
6
|
-
rehypePlugins: Pluggable[];
|
|
7
|
-
}
|
|
8
|
-
interface PreviewTransformer {
|
|
9
|
-
execution: 'sync';
|
|
10
|
-
type: 'remark' | 'rehype';
|
|
11
|
-
transform: (ctx: {
|
|
12
|
-
processor: Processor;
|
|
13
|
-
}) => void;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Map the site's plugin set to Carta sync transformers, remark phase before rehype.
|
|
17
|
-
* Carta's processor is remarkParse → gfm → [remark] → remark-rehype → [rehype] → stringify,
|
|
18
|
-
* so this ordering reproduces render.ts exactly. Pure (no Carta) so it is unit-testable.
|
|
19
|
-
*/
|
|
20
|
-
export declare function previewTransformers({ remarkPlugins, rehypePlugins }: PreviewPlugins): PreviewTransformer[];
|
|
21
|
-
/** Minimal Options subset we populate (avoids importing carta-md, which re-exports Svelte components). */
|
|
22
|
-
interface PreviewCartaOptions {
|
|
23
|
-
sanitizer: false;
|
|
24
|
-
rehypeOptions: {
|
|
25
|
-
allowDangerousHtml: boolean;
|
|
26
|
-
};
|
|
27
|
-
extensions: Array<{
|
|
28
|
-
transformers: PreviewTransformer[];
|
|
29
|
-
}>;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Carta options for a render-only preview: site plugins wired in, raw HTML allowed, no
|
|
33
|
-
* sanitizer. Authors are trusted and the directive pipeline emits intentional raw HTML
|
|
34
|
-
* (render.ts uses allowDangerousHtml + rehype-raw); sanitizing here would strip EC
|
|
35
|
-
* primitives. The Svelte component passes this to `new Carta(...)`.
|
|
36
|
-
*/
|
|
37
|
-
export declare function previewCartaOptions(plugins: PreviewPlugins): PreviewCartaOptions;
|
|
38
|
-
export {};
|
|
39
|
-
//# sourceMappingURL=carta.d.ts.map
|
package/dist/carta.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"carta.d.ts","sourceRoot":"","sources":["../src/lib/carta.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,oDAAoD;IACpD,aAAa,EAAE,SAAS,EAAE,CAAC;CAC5B;AAED,UAAU,kBAAkB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC1B,SAAS,EAAE,CAAC,GAAG,EAAE;QAAE,SAAS,EAAE,SAAS,CAAA;KAAE,KAAK,IAAI,CAAC;CACpD;AAYD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,EAAE,cAAc,GAAG,kBAAkB,EAAE,CAE1G;AAED,0GAA0G;AAC1G,UAAU,mBAAmB;IAC3B,SAAS,EAAE,KAAK,CAAC;IACjB,aAAa,EAAE;QAAE,kBAAkB,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,UAAU,EAAE,KAAK,CAAC;QAAE,YAAY,EAAE,kBAAkB,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,CAMhF"}
|
package/dist/carta.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
function phase(plugins, type) {
|
|
2
|
-
return plugins.map((plugin) => ({
|
|
3
|
-
execution: 'sync',
|
|
4
|
-
type,
|
|
5
|
-
transform: ({ processor }) => {
|
|
6
|
-
processor.use([plugin]);
|
|
7
|
-
},
|
|
8
|
-
}));
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Map the site's plugin set to Carta sync transformers, remark phase before rehype.
|
|
12
|
-
* Carta's processor is remarkParse → gfm → [remark] → remark-rehype → [rehype] → stringify,
|
|
13
|
-
* so this ordering reproduces render.ts exactly. Pure (no Carta) so it is unit-testable.
|
|
14
|
-
*/
|
|
15
|
-
export function previewTransformers({ remarkPlugins, rehypePlugins }) {
|
|
16
|
-
return [...phase(remarkPlugins, 'remark'), ...phase(rehypePlugins, 'rehype')];
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Carta options for a render-only preview: site plugins wired in, raw HTML allowed, no
|
|
20
|
-
* sanitizer. Authors are trusted and the directive pipeline emits intentional raw HTML
|
|
21
|
-
* (render.ts uses allowDangerousHtml + rehype-raw); sanitizing here would strip EC
|
|
22
|
-
* primitives. The Svelte component passes this to `new Carta(...)`.
|
|
23
|
-
*/
|
|
24
|
-
export function previewCartaOptions(plugins) {
|
|
25
|
-
return {
|
|
26
|
-
sanitizer: false,
|
|
27
|
-
rehypeOptions: { allowDangerousHtml: true },
|
|
28
|
-
extensions: [{ transformers: previewTransformers(plugins) }],
|
|
29
|
-
};
|
|
30
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
// The /admin content list: every collection's files, linking into the editor. Data comes
|
|
3
|
-
// from `adminListLoad` (collections) merged with `adminLayoutLoad` (siteName). The shell
|
|
4
|
-
// (AdminLayout) owns the chrome (site title, signed-in identity, nav, sign out), so this
|
|
5
|
-
// page renders only the content body.
|
|
6
|
-
import type { AdminCollectionList } from '../sveltekit';
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
data: { collections: AdminCollectionList[] };
|
|
10
|
-
}
|
|
11
|
-
let { data }: Props = $props();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<h1 class="text-2xl font-bold">Content</h1>
|
|
15
|
-
|
|
16
|
-
{#each data.collections as collection (collection.type)}
|
|
17
|
-
<section class="mt-8">
|
|
18
|
-
<h2 class="mb-3 text-lg font-semibold">{collection.label}</h2>
|
|
19
|
-
{#if collection.error}
|
|
20
|
-
<div class="alert alert-warning">Couldn't load {collection.label.toLowerCase()}: {collection.error}</div>
|
|
21
|
-
{:else if collection.files.length === 0}
|
|
22
|
-
<p class="opacity-60">No content yet.</p>
|
|
23
|
-
{:else}
|
|
24
|
-
<ul class="menu rounded-box border border-base-300 bg-base-100 p-2">
|
|
25
|
-
{#each collection.files as file (file.path)}
|
|
26
|
-
<li>
|
|
27
|
-
<a href="/admin/edit/{collection.type}/{file.id}">{file.id}</a>
|
|
28
|
-
</li>
|
|
29
|
-
{/each}
|
|
30
|
-
</ul>
|
|
31
|
-
{/if}
|
|
32
|
-
</section>
|
|
33
|
-
{/each}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { AdminCollectionList } from '../sveltekit';
|
|
2
|
-
interface Props {
|
|
3
|
-
data: {
|
|
4
|
-
collections: AdminCollectionList[];
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
declare const AdminList: import("svelte").Component<Props, {}, "">;
|
|
8
|
-
type AdminList = ReturnType<typeof AdminList>;
|
|
9
|
-
export default AdminList;
|
|
10
|
-
//# sourceMappingURL=AdminList.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AdminList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AdminList.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGtD,UAAU,KAAK;IACb,IAAI,EAAE;QAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;KAAE,CAAC;CAC9C;AAkCH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
// Owner-gated editor management: list the allowlist, change roles, remove editors, add new
|
|
3
|
-
// ones. Reuses the same neutral DaisyUI chrome as the rest of the admin (panels, alerts,
|
|
4
|
-
// table, buttons). Data comes from `adminsLoad` merged with `adminLayoutLoad` (siteName);
|
|
5
|
-
// mutations post to the page's named form actions (`?/add`, `?/remove`, `?/setRole`).
|
|
6
|
-
import type { AdminsData } from '../auth';
|
|
7
|
-
|
|
8
|
-
interface Props {
|
|
9
|
-
data: AdminsData & { siteName: string };
|
|
10
|
-
}
|
|
11
|
-
let { data }: Props = $props();
|
|
12
|
-
</script>
|
|
13
|
-
|
|
14
|
-
<svelte:head>
|
|
15
|
-
<title>Editors · {data.siteName} CMS</title>
|
|
16
|
-
</svelte:head>
|
|
17
|
-
|
|
18
|
-
<div>
|
|
19
|
-
<h1 class="text-2xl font-bold">Editors</h1>
|
|
20
|
-
<p class="text-sm opacity-60">Who can sign in to {data.siteName} CMS.</p>
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
{#if data.saved}
|
|
24
|
-
<div class="alert alert-success mt-6"><span>Allowlist updated.</span></div>
|
|
25
|
-
{:else if data.error}
|
|
26
|
-
<div class="alert alert-error mt-6"><span>{data.error}</span></div>
|
|
27
|
-
{/if}
|
|
28
|
-
|
|
29
|
-
<div class="mt-6 overflow-x-auto rounded-box border border-base-300 bg-base-100">
|
|
30
|
-
<table class="table">
|
|
31
|
-
<thead>
|
|
32
|
-
<tr><th>Name</th><th>Email</th><th>Role</th><th class="text-right">Actions</th></tr>
|
|
33
|
-
</thead>
|
|
34
|
-
<tbody>
|
|
35
|
-
{#each data.admins as admin (admin.email)}
|
|
36
|
-
{@const isSelf = admin.email === data.self}
|
|
37
|
-
<tr>
|
|
38
|
-
<td class="font-medium">{admin.name}</td>
|
|
39
|
-
<td class="opacity-70">{admin.email}{#if isSelf}<span class="ml-1 opacity-50">(you)</span>{/if}</td>
|
|
40
|
-
<td>
|
|
41
|
-
<span class="badge {admin.role === 'owner' ? 'badge-primary' : 'badge-ghost'}">{admin.role}</span>
|
|
42
|
-
</td>
|
|
43
|
-
<td>
|
|
44
|
-
<div class="flex justify-end gap-2">
|
|
45
|
-
<!-- Flip role. Disabled for yourself so you can't demote the last owner out. -->
|
|
46
|
-
<form method="POST" action="?/setRole">
|
|
47
|
-
<input type="hidden" name="email" value={admin.email} />
|
|
48
|
-
<input type="hidden" name="role" value={admin.role === 'owner' ? 'editor' : 'owner'} />
|
|
49
|
-
<button type="submit" class="btn btn-ghost btn-xs" disabled={isSelf}>
|
|
50
|
-
Make {admin.role === 'owner' ? 'editor' : 'owner'}
|
|
51
|
-
</button>
|
|
52
|
-
</form>
|
|
53
|
-
<form method="POST" action="?/remove">
|
|
54
|
-
<input type="hidden" name="email" value={admin.email} />
|
|
55
|
-
<button type="submit" class="btn btn-ghost btn-xs text-error" disabled={isSelf}>Remove</button>
|
|
56
|
-
</form>
|
|
57
|
-
</div>
|
|
58
|
-
</td>
|
|
59
|
-
</tr>
|
|
60
|
-
{/each}
|
|
61
|
-
</tbody>
|
|
62
|
-
</table>
|
|
63
|
-
</div>
|
|
64
|
-
|
|
65
|
-
<form method="POST" action="?/add"
|
|
66
|
-
class="mt-8 grid gap-4 rounded-box border border-base-300 bg-base-100 p-6 sm:grid-cols-[1fr_1fr_auto_auto] sm:items-end">
|
|
67
|
-
<label class="flex flex-col gap-1">
|
|
68
|
-
<span class="text-sm font-medium">Email</span>
|
|
69
|
-
<input type="email" name="email" required autocomplete="off" placeholder="you@example.com"
|
|
70
|
-
class="input w-full" />
|
|
71
|
-
</label>
|
|
72
|
-
<label class="flex flex-col gap-1">
|
|
73
|
-
<span class="text-sm font-medium">Name</span>
|
|
74
|
-
<input type="text" name="name" required placeholder="Display name" class="input w-full" />
|
|
75
|
-
</label>
|
|
76
|
-
<label class="flex flex-col gap-1">
|
|
77
|
-
<span class="text-sm font-medium">Role</span>
|
|
78
|
-
<select name="role" class="select">
|
|
79
|
-
<option value="editor">editor</option>
|
|
80
|
-
<option value="owner">owner</option>
|
|
81
|
-
</select>
|
|
82
|
-
</label>
|
|
83
|
-
<button type="submit" class="btn btn-primary">Add editor</button>
|
|
84
|
-
</form>
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { AdminsData } from '../auth';
|
|
2
|
-
interface Props {
|
|
3
|
-
data: AdminsData & {
|
|
4
|
-
siteName: string;
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
declare const ManageAdmins: import("svelte").Component<Props, {}, "">;
|
|
8
|
-
type ManageAdmins = ReturnType<typeof ManageAdmins>;
|
|
9
|
-
export default ManageAdmins;
|
|
10
|
-
//# sourceMappingURL=ManageAdmins.svelte.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ManageAdmins.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ManageAdmins.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxC,UAAU,KAAK;IACb,IAAI,EAAE,UAAU,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAmFH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
package/dist/content.d.ts
DELETED
package/dist/content.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/lib/content.ts"],"names":[],"mappings":"AAOA,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E"}
|
package/dist/content.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// cairn-core: reassemble a markdown file from frontmatter + body for committing.
|
|
2
|
-
//
|
|
3
|
-
// The inverse of the gray-matter parse the edit loader does on read. Kept as its own seam
|
|
4
|
-
// so a site adapter can own the on-disk serialization contract (quoting, key order)
|
|
5
|
-
// without the save endpoint reaching for gray-matter directly.
|
|
6
|
-
import matter from 'gray-matter';
|
|
7
|
-
/** Serialize frontmatter data + markdown body back into a file string. */
|
|
8
|
-
export function serializeMarkdown(frontmatter, body) {
|
|
9
|
-
return matter.stringify(body, frontmatter);
|
|
10
|
-
}
|
package/dist/github.d.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export interface RepoRef {
|
|
2
|
-
owner: string;
|
|
3
|
-
repo: string;
|
|
4
|
-
branch: string;
|
|
5
|
-
}
|
|
6
|
-
/** A markdown file in a collection directory. `id` is the slug (filename without `.md`). */
|
|
7
|
-
export interface RepoFile {
|
|
8
|
-
id: string;
|
|
9
|
-
name: string;
|
|
10
|
-
path: string;
|
|
11
|
-
}
|
|
12
|
-
/** Build the contents-API URL for a repo path, pinned to the configured branch. */
|
|
13
|
-
export declare function contentsUrl(repo: RepoRef, path: string): string;
|
|
14
|
-
interface ContentsEntry {
|
|
15
|
-
name: string;
|
|
16
|
-
path: string;
|
|
17
|
-
type: string;
|
|
18
|
-
}
|
|
19
|
-
/** Keep only markdown files from a contents-API directory listing, newest id first. */
|
|
20
|
-
export declare function markdownFiles(entries: ContentsEntry[]): RepoFile[];
|
|
21
|
-
/** List the markdown files in a collection directory. */
|
|
22
|
-
export declare function listMarkdown(repo: RepoRef, dir: string, token?: string): Promise<RepoFile[]>;
|
|
23
|
-
/** Fetch a file's raw markdown, or null if it does not exist. */
|
|
24
|
-
export declare function readRaw(repo: RepoRef, path: string, token?: string): Promise<string | null>;
|
|
25
|
-
/** Mint a GitHub App JWT (RS256), valid ~9 min, with `iat` backdated for clock skew. */
|
|
26
|
-
export declare function appJwt(appId: string, privateKeyPem: string): Promise<string>;
|
|
27
|
-
export interface AppCredentials {
|
|
28
|
-
appId: string;
|
|
29
|
-
installationId: string;
|
|
30
|
-
/** The stored GITHUB_APP_PRIVATE_KEY_B64: base64 of the PEM, single line. */
|
|
31
|
-
privateKeyB64: string;
|
|
32
|
-
}
|
|
33
|
-
/** Exchange the App JWT for a short-lived installation access token. */
|
|
34
|
-
export declare function installationToken(creds: AppCredentials): Promise<string>;
|
|
35
|
-
/** The current blob sha for a path, or null if the file does not yet exist. */
|
|
36
|
-
export declare function fileSha(repo: RepoRef, path: string, token: string): Promise<string | null>;
|
|
37
|
-
export interface CommitAuthor {
|
|
38
|
-
name: string;
|
|
39
|
-
email: string;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* A concurrent edit lost the SHA race (C3): the file changed between the read and the PUT,
|
|
43
|
-
* from another editor or the site's own CI. Thrown so callers can fail safe (re-fetch and ask
|
|
44
|
-
* the editor to reapply) instead of surfacing a raw 409. Defined and caught inside the package
|
|
45
|
-
* so `instanceof` is reliable (no peer-boundary identity split, unlike kit's `redirect`/`error`).
|
|
46
|
-
*/
|
|
47
|
-
export declare class CommitConflictError extends Error {
|
|
48
|
-
readonly path: string;
|
|
49
|
-
constructor(path: string);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Commit `content` to `path` on the configured branch via the contents API. Author is the
|
|
53
|
-
* editor; committer is omitted so GitHub attributes it to the App (cairn-cms[bot]). Updates
|
|
54
|
-
* the file in place when it exists (passing its sha), creates it otherwise. Returns the
|
|
55
|
-
* commit sha. A stale-sha 409 (someone committed in between) becomes a `CommitConflictError`.
|
|
56
|
-
*/
|
|
57
|
-
export declare function commitFile(repo: RepoRef, path: string, content: string, opts: {
|
|
58
|
-
message: string;
|
|
59
|
-
author: CommitAuthor;
|
|
60
|
-
}, token: string): Promise<string>;
|
|
61
|
-
/**
|
|
62
|
-
* Deploy-time self-test for the GitHub App signer (M2): sign a dummy JWT with the configured
|
|
63
|
-
* private key. Exercises the brittle PKCS#1→PKCS#8 conversion + Web Crypto import/sign without
|
|
64
|
-
* any network call or secret in the result, so `/admin/healthz` catches a bad/rotated key
|
|
65
|
-
* before an editor's save fails. Returns `{ ok: false, detail }` rather than throwing.
|
|
66
|
-
*/
|
|
67
|
-
export declare function signingSelfTest(appId: string, privateKeyB64: string): Promise<{
|
|
68
|
-
ok: boolean;
|
|
69
|
-
detail?: string;
|
|
70
|
-
}>;
|
|
71
|
-
export {};
|
|
72
|
-
//# sourceMappingURL=github.d.ts.map
|
package/dist/github.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/lib/github.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4FAA4F;AAC5F,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAcD,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,QAAQ,EAAE,CAKlE;AAED,yDAAyD;AACzD,wBAAsB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAIlG;AAED,iEAAiE;AACjE,wBAAsB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKjG;AAqCD,wFAAwF;AACxF,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAclF;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wEAAwE;AACxE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ9E;AAOD,+EAA+E;AAC/E,wBAAsB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKhG;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;aAChB,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIzC;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,EAC/C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAQrH"}
|
package/dist/github.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
// cairn-core: read and write repository content through the GitHub API.
|
|
2
|
-
//
|
|
3
|
-
// Reads (Pass B) list a collection directory and fetch a file's raw markdown; the token
|
|
4
|
-
// is optional because ecnordic's repo is public. Writes (Pass C) mint a short-lived
|
|
5
|
-
// GitHub App installation token (App JWT, RS256 signed with Web Crypto, no octokit
|
|
6
|
-
// dependency) and commit through the contents API with author = editor, committer = the
|
|
7
|
-
// App (cairn-cms[bot]). The same token also lifts reads to the authenticated rate limit
|
|
8
|
-
// and unlocks private repos (e.g. 907-life).
|
|
9
|
-
import { bytesToB64url } from './utils';
|
|
10
|
-
const API = 'https://api.github.com';
|
|
11
|
-
function ghHeaders(accept, token) {
|
|
12
|
-
const headers = {
|
|
13
|
-
Accept: accept,
|
|
14
|
-
'User-Agent': 'cairn-cms',
|
|
15
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
16
|
-
};
|
|
17
|
-
if (token)
|
|
18
|
-
headers.Authorization = `Bearer ${token}`;
|
|
19
|
-
return headers;
|
|
20
|
-
}
|
|
21
|
-
/** Build the contents-API URL for a repo path, pinned to the configured branch. */
|
|
22
|
-
export function contentsUrl(repo, path) {
|
|
23
|
-
const clean = path.replace(/^\/+|\/+$/g, '');
|
|
24
|
-
return `${API}/repos/${repo.owner}/${repo.repo}/contents/${clean}?ref=${encodeURIComponent(repo.branch)}`;
|
|
25
|
-
}
|
|
26
|
-
/** Keep only markdown files from a contents-API directory listing, newest id first. */
|
|
27
|
-
export function markdownFiles(entries) {
|
|
28
|
-
return entries
|
|
29
|
-
.filter((entry) => entry.type === 'file' && entry.name.endsWith('.md'))
|
|
30
|
-
.map((entry) => ({ id: entry.name.replace(/\.md$/, ''), name: entry.name, path: entry.path }))
|
|
31
|
-
.sort((a, b) => b.id.localeCompare(a.id));
|
|
32
|
-
}
|
|
33
|
-
/** List the markdown files in a collection directory. */
|
|
34
|
-
export async function listMarkdown(repo, dir, token) {
|
|
35
|
-
const res = await fetch(contentsUrl(repo, dir), { headers: ghHeaders('application/vnd.github+json', token) });
|
|
36
|
-
if (!res.ok)
|
|
37
|
-
throw new Error(`GitHub list ${dir} failed: ${res.status}`);
|
|
38
|
-
return markdownFiles((await res.json()));
|
|
39
|
-
}
|
|
40
|
-
/** Fetch a file's raw markdown, or null if it does not exist. */
|
|
41
|
-
export async function readRaw(repo, path, token) {
|
|
42
|
-
const res = await fetch(contentsUrl(repo, path), { headers: ghHeaders('application/vnd.github.raw', token) });
|
|
43
|
-
if (res.status === 404)
|
|
44
|
-
return null;
|
|
45
|
-
if (!res.ok)
|
|
46
|
-
throw new Error(`GitHub read ${path} failed: ${res.status}`);
|
|
47
|
-
return res.text();
|
|
48
|
-
}
|
|
49
|
-
// --- Write path: GitHub App auth + commit (Pass C) -------------------------------------
|
|
50
|
-
const encoder = new TextEncoder();
|
|
51
|
-
// TextEncoder/atob produce Uint8Arrays whose generic buffer type no longer satisfies
|
|
52
|
-
// Web Crypto's BufferSource under strict lib types; hand the underlying ArrayBuffer over.
|
|
53
|
-
function buf(bytes) {
|
|
54
|
-
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
55
|
-
}
|
|
56
|
-
/** DER length octets for a value of `n` bytes (short form < 128, else long form). */
|
|
57
|
-
function derLength(n) {
|
|
58
|
-
if (n < 0x80)
|
|
59
|
-
return [n];
|
|
60
|
-
const out = [];
|
|
61
|
-
for (let v = n; v > 0; v >>= 8)
|
|
62
|
-
out.unshift(v & 0xff);
|
|
63
|
-
return [0x80 | out.length, ...out];
|
|
64
|
-
}
|
|
65
|
-
// AlgorithmIdentifier for rsaEncryption (OID 1.2.840.113549.1.1.1) with NULL parameters.
|
|
66
|
-
const RSA_ALG_ID = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00];
|
|
67
|
-
/** Wrap a PKCS#1 RSAPrivateKey (DER) as PKCS#8 (the only RSA form Web Crypto importKey takes). */
|
|
68
|
-
function pkcs1ToPkcs8(pkcs1) {
|
|
69
|
-
const octet = [0x04, ...derLength(pkcs1.length), ...pkcs1];
|
|
70
|
-
const body = [0x02, 0x01, 0x00, ...RSA_ALG_ID, ...octet];
|
|
71
|
-
return Uint8Array.from([0x30, ...derLength(body.length), ...body]);
|
|
72
|
-
}
|
|
73
|
-
/** Decode a PEM private key to PKCS#8 DER, converting from PKCS#1 (GitHub's format) if needed. */
|
|
74
|
-
function pemToPkcs8(pem) {
|
|
75
|
-
const b64 = pem.replace(/-----[^-]+-----/g, '').replace(/\s+/g, '');
|
|
76
|
-
const der = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
77
|
-
return pem.includes('RSA PRIVATE KEY') ? pkcs1ToPkcs8(der) : der;
|
|
78
|
-
}
|
|
79
|
-
/** Mint a GitHub App JWT (RS256), valid ~9 min, with `iat` backdated for clock skew. */
|
|
80
|
-
export async function appJwt(appId, privateKeyPem) {
|
|
81
|
-
const now = Math.floor(Date.now() / 1000);
|
|
82
|
-
const header = bytesToB64url(encoder.encode(JSON.stringify({ alg: 'RS256', typ: 'JWT' })));
|
|
83
|
-
const payload = bytesToB64url(encoder.encode(JSON.stringify({ iat: now - 60, exp: now + 540, iss: appId })));
|
|
84
|
-
const signingInput = `${header}.${payload}`;
|
|
85
|
-
const key = await crypto.subtle.importKey('pkcs8', buf(pemToPkcs8(privateKeyPem)), { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
|
|
86
|
-
const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, buf(encoder.encode(signingInput)));
|
|
87
|
-
return `${signingInput}.${bytesToB64url(new Uint8Array(sig))}`;
|
|
88
|
-
}
|
|
89
|
-
/** Exchange the App JWT for a short-lived installation access token. */
|
|
90
|
-
export async function installationToken(creds) {
|
|
91
|
-
const jwt = await appJwt(creds.appId, atob(creds.privateKeyB64));
|
|
92
|
-
const res = await fetch(`${API}/app/installations/${creds.installationId}/access_tokens`, {
|
|
93
|
-
method: 'POST',
|
|
94
|
-
headers: ghHeaders('application/vnd.github+json', jwt),
|
|
95
|
-
});
|
|
96
|
-
if (!res.ok)
|
|
97
|
-
throw new Error(`GitHub installation token failed: ${res.status}`);
|
|
98
|
-
return (await res.json()).token;
|
|
99
|
-
}
|
|
100
|
-
/** Standard (padded) base64 of UTF-8 text, as the contents API expects. */
|
|
101
|
-
function toBase64(text) {
|
|
102
|
-
return btoa(Array.from(encoder.encode(text), (b) => String.fromCharCode(b)).join(''));
|
|
103
|
-
}
|
|
104
|
-
/** The current blob sha for a path, or null if the file does not yet exist. */
|
|
105
|
-
export async function fileSha(repo, path, token) {
|
|
106
|
-
const res = await fetch(contentsUrl(repo, path), { headers: ghHeaders('application/vnd.github+json', token) });
|
|
107
|
-
if (res.status === 404)
|
|
108
|
-
return null;
|
|
109
|
-
if (!res.ok)
|
|
110
|
-
throw new Error(`GitHub stat ${path} failed: ${res.status}`);
|
|
111
|
-
return (await res.json()).sha;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* A concurrent edit lost the SHA race (C3): the file changed between the read and the PUT,
|
|
115
|
-
* from another editor or the site's own CI. Thrown so callers can fail safe (re-fetch and ask
|
|
116
|
-
* the editor to reapply) instead of surfacing a raw 409. Defined and caught inside the package
|
|
117
|
-
* so `instanceof` is reliable (no peer-boundary identity split, unlike kit's `redirect`/`error`).
|
|
118
|
-
*/
|
|
119
|
-
export class CommitConflictError extends Error {
|
|
120
|
-
path;
|
|
121
|
-
constructor(path) {
|
|
122
|
-
super(`Commit conflict on ${path}: it changed since it was opened`);
|
|
123
|
-
this.path = path;
|
|
124
|
-
this.name = 'CommitConflictError';
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Commit `content` to `path` on the configured branch via the contents API. Author is the
|
|
129
|
-
* editor; committer is omitted so GitHub attributes it to the App (cairn-cms[bot]). Updates
|
|
130
|
-
* the file in place when it exists (passing its sha), creates it otherwise. Returns the
|
|
131
|
-
* commit sha. A stale-sha 409 (someone committed in between) becomes a `CommitConflictError`.
|
|
132
|
-
*/
|
|
133
|
-
export async function commitFile(repo, path, content, opts, token) {
|
|
134
|
-
const sha = await fileSha(repo, path, token);
|
|
135
|
-
const url = `${API}/repos/${repo.owner}/${repo.repo}/contents/${path.replace(/^\/+|\/+$/g, '')}`;
|
|
136
|
-
const res = await fetch(url, {
|
|
137
|
-
method: 'PUT',
|
|
138
|
-
headers: { ...ghHeaders('application/vnd.github+json', token), 'Content-Type': 'application/json' },
|
|
139
|
-
body: JSON.stringify({
|
|
140
|
-
message: opts.message,
|
|
141
|
-
content: toBase64(content),
|
|
142
|
-
branch: repo.branch,
|
|
143
|
-
author: opts.author,
|
|
144
|
-
...(sha ? { sha } : {}),
|
|
145
|
-
}),
|
|
146
|
-
});
|
|
147
|
-
// 409 = the blob sha we read is no longer current. Fail safe: the caller re-fetches and the
|
|
148
|
-
// editor reapplies. (Full three-way merge stays out of scope; see ARCHITECTURE §5.)
|
|
149
|
-
if (res.status === 409)
|
|
150
|
-
throw new CommitConflictError(path);
|
|
151
|
-
if (!res.ok)
|
|
152
|
-
throw new Error(`GitHub commit ${path} failed: ${res.status} ${await res.text()}`);
|
|
153
|
-
return (await res.json()).commit.sha;
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Deploy-time self-test for the GitHub App signer (M2): sign a dummy JWT with the configured
|
|
157
|
-
* private key. Exercises the brittle PKCS#1→PKCS#8 conversion + Web Crypto import/sign without
|
|
158
|
-
* any network call or secret in the result, so `/admin/healthz` catches a bad/rotated key
|
|
159
|
-
* before an editor's save fails. Returns `{ ok: false, detail }` rather than throwing.
|
|
160
|
-
*/
|
|
161
|
-
export async function signingSelfTest(appId, privateKeyB64) {
|
|
162
|
-
try {
|
|
163
|
-
const jwt = await appJwt(appId, atob(privateKeyB64));
|
|
164
|
-
if (jwt.split('.').length !== 3)
|
|
165
|
-
return { ok: false, detail: 'malformed JWT' };
|
|
166
|
-
return { ok: true };
|
|
167
|
-
}
|
|
168
|
-
catch (err) {
|
|
169
|
-
return { ok: false, detail: err instanceof Error ? err.message : 'sign failed' };
|
|
170
|
-
}
|
|
171
|
-
}
|
package/dist/utils.d.ts
DELETED
package/dist/utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.ts"],"names":[],"mappings":"AAOA,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAGvD"}
|
package/dist/utils.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// cairn-core: internal encoding helpers shared across modules.
|
|
2
|
-
//
|
|
3
|
-
// Deliberately NOT re-exported from index.ts. These are implementation details of the
|
|
4
|
-
// auth/github crypto, not part of the public API (auth.ts signs tokens, github.ts builds
|
|
5
|
-
// the App JWT; both need base64url). Keeping them here stops bytesToB64url leaking through
|
|
6
|
-
// the `export *` barrel.
|
|
7
|
-
/** Encode bytes as unpadded base64url (RFC 4648 §5), the JWT/token wire format. */
|
|
8
|
-
export function bytesToB64url(bytes) {
|
|
9
|
-
const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
|
|
10
|
-
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
11
|
-
}
|