vestrycms 0.0.0-stage → 0.1.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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -2
  3. package/dist/config/fields.d.ts +30 -0
  4. package/dist/config/fields.js +75 -0
  5. package/dist/config/index.d.ts +9 -0
  6. package/dist/config/index.js +18 -0
  7. package/dist/config/types.d.ts +403 -0
  8. package/dist/config/types.js +10 -0
  9. package/dist/core/api.d.ts +67 -0
  10. package/dist/core/api.js +538 -0
  11. package/dist/core/auth.d.ts +1561 -0
  12. package/dist/core/auth.js +234 -0
  13. package/dist/core/cache.d.ts +27 -0
  14. package/dist/core/cache.js +52 -0
  15. package/dist/core/entries.d.ts +19 -0
  16. package/dist/core/entries.js +127 -0
  17. package/dist/core/index-types.d.ts +20 -0
  18. package/dist/core/index-types.js +1 -0
  19. package/dist/core/index.d.ts +13 -0
  20. package/dist/core/index.js +13 -0
  21. package/dist/core/mail.d.ts +28 -0
  22. package/dist/core/mail.js +62 -0
  23. package/dist/core/paths.d.ts +35 -0
  24. package/dist/core/paths.js +148 -0
  25. package/dist/core/roles.d.ts +24 -0
  26. package/dist/core/roles.js +15 -0
  27. package/dist/core/service.d.ts +102 -0
  28. package/dist/core/service.js +478 -0
  29. package/dist/core/source.d.ts +82 -0
  30. package/dist/core/source.js +75 -0
  31. package/dist/core/sources/github-app.d.ts +26 -0
  32. package/dist/core/sources/github-app.js +161 -0
  33. package/dist/core/sources/github.d.ts +40 -0
  34. package/dist/core/sources/github.js +278 -0
  35. package/dist/core/sources/local.d.ts +16 -0
  36. package/dist/core/sources/local.js +98 -0
  37. package/dist/core/values.d.ts +22 -0
  38. package/dist/core/values.js +123 -0
  39. package/dist/env.d.ts +19 -0
  40. package/dist/index.d.ts +18 -0
  41. package/dist/index.js +167 -0
  42. package/dist/integration/build-index.d.ts +8 -0
  43. package/dist/integration/build-index.js +67 -0
  44. package/dist/integration/client-deps.d.ts +10 -0
  45. package/dist/integration/client-deps.js +43 -0
  46. package/dist/integration/dev-middleware.d.ts +6 -0
  47. package/dist/integration/dev-middleware.js +48 -0
  48. package/dist/integration/formatter.d.ts +7 -0
  49. package/dist/integration/formatter.js +56 -0
  50. package/dist/mdx/expressions.d.ts +14 -0
  51. package/dist/mdx/expressions.js +141 -0
  52. package/dist/mdx/index.d.ts +4 -0
  53. package/dist/mdx/index.js +4 -0
  54. package/dist/mdx/parse.d.ts +6 -0
  55. package/dist/mdx/parse.js +378 -0
  56. package/dist/mdx/serialize.d.ts +9 -0
  57. package/dist/mdx/serialize.js +396 -0
  58. package/dist/mdx/types.d.ts +40 -0
  59. package/dist/mdx/types.js +6 -0
  60. package/dist/pages/admin.astro +27 -0
  61. package/dist/pages/api.d.ts +3 -0
  62. package/dist/pages/api.js +31 -0
  63. package/dist/ui/App.d.ts +4 -0
  64. package/dist/ui/App.js +70 -0
  65. package/dist/ui/api.d.ts +83 -0
  66. package/dist/ui/api.js +97 -0
  67. package/dist/ui/auth-client.d.ts +27 -0
  68. package/dist/ui/auth-client.js +29 -0
  69. package/dist/ui/components/AssetPicker.d.ts +27 -0
  70. package/dist/ui/components/AssetPicker.js +97 -0
  71. package/dist/ui/components/Icon.d.ts +11 -0
  72. package/dist/ui/components/Icon.js +46 -0
  73. package/dist/ui/components/Layout.d.ts +15 -0
  74. package/dist/ui/components/Layout.js +87 -0
  75. package/dist/ui/components/editor/EditorContext.d.ts +15 -0
  76. package/dist/ui/components/editor/EditorContext.js +9 -0
  77. package/dist/ui/components/editor/NodeViews.d.ts +4 -0
  78. package/dist/ui/components/editor/NodeViews.js +127 -0
  79. package/dist/ui/components/editor/RichTextEditor.d.ts +24 -0
  80. package/dist/ui/components/editor/RichTextEditor.js +253 -0
  81. package/dist/ui/components/editor/extensions.d.ts +20 -0
  82. package/dist/ui/components/editor/extensions.js +156 -0
  83. package/dist/ui/components/fields/CodeInput.d.ts +10 -0
  84. package/dist/ui/components/fields/CodeInput.js +8 -0
  85. package/dist/ui/components/fields/CodeMirrorInput.d.ts +2 -0
  86. package/dist/ui/components/fields/CodeMirrorInput.js +44 -0
  87. package/dist/ui/components/fields/FieldRenderer.d.ts +41 -0
  88. package/dist/ui/components/fields/FieldRenderer.js +299 -0
  89. package/dist/ui/components/fields/ImageTools.d.ts +31 -0
  90. package/dist/ui/components/fields/ImageTools.js +296 -0
  91. package/dist/ui/components/fields/MarkdownField.d.ts +7 -0
  92. package/dist/ui/components/fields/MarkdownField.js +16 -0
  93. package/dist/ui/components/fields/TreeInput.d.ts +13 -0
  94. package/dist/ui/components/fields/TreeInput.js +221 -0
  95. package/dist/ui/components/ui.d.ts +63 -0
  96. package/dist/ui/components/ui.js +113 -0
  97. package/dist/ui/context.d.ts +17 -0
  98. package/dist/ui/context.js +13 -0
  99. package/dist/ui/lib/format.d.ts +5 -0
  100. package/dist/ui/lib/format.js +11 -0
  101. package/dist/ui/lib/utils.d.ts +24 -0
  102. package/dist/ui/lib/utils.js +255 -0
  103. package/dist/ui/main.d.ts +1 -0
  104. package/dist/ui/main.js +11 -0
  105. package/dist/ui/pages/Assets.d.ts +1 -0
  106. package/dist/ui/pages/Assets.js +69 -0
  107. package/dist/ui/pages/CollectionList.d.ts +6 -0
  108. package/dist/ui/pages/CollectionList.js +115 -0
  109. package/dist/ui/pages/Dashboard.d.ts +1 -0
  110. package/dist/ui/pages/Dashboard.js +57 -0
  111. package/dist/ui/pages/EntryEditor.d.ts +3 -0
  112. package/dist/ui/pages/EntryEditor.js +313 -0
  113. package/dist/ui/pages/Login.d.ts +6 -0
  114. package/dist/ui/pages/Login.js +88 -0
  115. package/dist/ui/pages/Users.d.ts +1 -0
  116. package/dist/ui/pages/Users.js +102 -0
  117. package/dist/ui/styles.css +2536 -0
  118. package/package.json +121 -3
@@ -0,0 +1,148 @@
1
+ export function normalizePath(p) {
2
+ return p
3
+ .replace(/\\/g, "/")
4
+ .replace(/^\.\//, "")
5
+ .replace(/\/+/g, "/")
6
+ .replace(/^\//, "")
7
+ .replace(/\/$/, "");
8
+ }
9
+ export function extname(p) {
10
+ const i = p.lastIndexOf(".");
11
+ const s = p.lastIndexOf("/");
12
+ return i > s ? p.slice(i + 1).toLowerCase() : "";
13
+ }
14
+ export function basename(p) {
15
+ return p.slice(p.lastIndexOf("/") + 1);
16
+ }
17
+ export function dirname(p) {
18
+ const i = p.lastIndexOf("/");
19
+ return i === -1 ? "" : p.slice(0, i);
20
+ }
21
+ export function joinPath(...parts) {
22
+ return normalizePath(parts.filter(Boolean).join("/"));
23
+ }
24
+ /** Resolve `../x` style segments against a directory (repo-relative). */
25
+ export function resolveRelative(fromDir, rel) {
26
+ const out = normalizePath(fromDir).split("/").filter(Boolean);
27
+ for (const seg of normalizePath(rel).split("/")) {
28
+ if (seg === "..")
29
+ out.pop();
30
+ else if (seg !== "." && seg !== "")
31
+ out.push(seg);
32
+ }
33
+ return out.join("/");
34
+ }
35
+ /** Relative path from one directory to a file (repo-relative, posix). */
36
+ export function relativePath(fromDir, toFile) {
37
+ const a = normalizePath(fromDir).split("/").filter(Boolean);
38
+ const b = normalizePath(toFile).split("/").filter(Boolean);
39
+ let i = 0;
40
+ while (i < a.length && i < b.length && a[i] === b[i])
41
+ i++;
42
+ const ups = a.slice(i).map(() => "..");
43
+ const rest = b.slice(i);
44
+ const parts = [...ups, ...rest];
45
+ const rel = parts.join("/");
46
+ return rel.startsWith(".") ? rel : `./${rel}`;
47
+ }
48
+ export function formatFromPath(p) {
49
+ const ext = extname(p);
50
+ if (ext === "mdx")
51
+ return "mdx";
52
+ if (ext === "md" || ext === "markdown")
53
+ return "md";
54
+ if (ext === "yaml" || ext === "yml")
55
+ return "yaml";
56
+ if (ext === "json")
57
+ return "json";
58
+ throw new Error(`Cannot infer content format from path "${p}"`);
59
+ }
60
+ export function parsePathPattern(pattern) {
61
+ const p = normalizePath(pattern);
62
+ const deepIdx = p.indexOf("**");
63
+ const starIdx = p.indexOf("*");
64
+ if (starIdx === -1) {
65
+ return {
66
+ prefix: p,
67
+ suffix: "",
68
+ deep: false,
69
+ single: true,
70
+ directory: dirname(p),
71
+ };
72
+ }
73
+ const deep = deepIdx !== -1;
74
+ const prefix = p.slice(0, starIdx);
75
+ const after = deep ? p.slice(deepIdx + 2) : p.slice(starIdx + 1);
76
+ // `** /index.md` → suffix `/index.md`; `*.mdx` → suffix `.mdx`
77
+ if (after.includes("*")) {
78
+ throw new Error(`Path pattern "${pattern}" may contain only one wildcard`);
79
+ }
80
+ return {
81
+ prefix,
82
+ suffix: after,
83
+ deep,
84
+ single: false,
85
+ directory: prefix.replace(/\/$/, ""),
86
+ };
87
+ }
88
+ export function entryPath(pattern, slug) {
89
+ if (pattern.single)
90
+ return pattern.prefix;
91
+ return `${pattern.prefix}${slug}${pattern.suffix}`;
92
+ }
93
+ /** Returns the slug for a file path if it matches the pattern, else null. */
94
+ export function slugFromPath(pattern, path) {
95
+ const p = normalizePath(path);
96
+ if (pattern.single)
97
+ return null;
98
+ if (!p.startsWith(pattern.prefix) || !p.endsWith(pattern.suffix))
99
+ return null;
100
+ const slug = p.slice(pattern.prefix.length, p.length - pattern.suffix.length);
101
+ if (!slug)
102
+ return null;
103
+ if (!pattern.deep && slug.includes("/"))
104
+ return null;
105
+ if (slug.startsWith("_") || slug.split("/").some((s) => s.startsWith("_"))) {
106
+ // Astro ignores underscore-prefixed content files.
107
+ return null;
108
+ }
109
+ return slug;
110
+ }
111
+ export function collectionFormat(c) {
112
+ return c.format ?? formatFromPath(c.path);
113
+ }
114
+ export function slugify(input) {
115
+ return input
116
+ .normalize("NFKD")
117
+ .replace(/[̀-ͯ]/g, "")
118
+ .toLowerCase()
119
+ .replace(/['’"]/g, "")
120
+ .replace(/[^a-z0-9]+/g, "-")
121
+ .replace(/^-+|-+$/g, "");
122
+ }
123
+ /** Fill `{field}` placeholders from a record. Values are slugified when `slug` is true. */
124
+ export function fillTemplate(template, values, opts = {}) {
125
+ return template.replace(/\{([\w.]+)\}/g, (_, key) => {
126
+ const raw = key.split(".").reduce((acc, k) => {
127
+ if (acc && typeof acc === "object")
128
+ return acc[k];
129
+ return undefined;
130
+ }, values);
131
+ if (raw === undefined || raw === null)
132
+ return "";
133
+ let s = raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);
134
+ if (opts.slug)
135
+ s = slugify(s);
136
+ return s;
137
+ });
138
+ }
139
+ const SAFE_SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]*(\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/;
140
+ export function isSafeSlug(slug) {
141
+ return SAFE_SLUG.test(slug) && !slug.includes("..");
142
+ }
143
+ export function isSafeRepoPath(path) {
144
+ const p = normalizePath(path);
145
+ return (p.length > 0 &&
146
+ !p.split("/").some((s) => s === ".." || s === "" || s === ".git") &&
147
+ !p.startsWith("/"));
148
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Roles and the user shape the control panel works with. Kept free of
3
+ * server-only imports so the React bundle can share it.
4
+ */
5
+ export type Role = "admin" | "editor";
6
+ export declare const ROLES: {
7
+ value: Role;
8
+ label: string;
9
+ description: string;
10
+ }[];
11
+ export declare function isRole(value: unknown): value is Role;
12
+ /** What the API sends to the browser about a user. */
13
+ export interface PublicUser {
14
+ id: string;
15
+ email: string;
16
+ name: string;
17
+ role: Role;
18
+ /** `invited` until the first successful sign-in. */
19
+ status: "invited" | "active";
20
+ /** `config` for addresses listed in `auth.admins`; they cannot be removed or demoted. */
21
+ origin: "config" | "invite";
22
+ createdAt: string;
23
+ lastLoginAt?: string;
24
+ }
@@ -0,0 +1,15 @@
1
+ export const ROLES = [
2
+ {
3
+ value: "editor",
4
+ label: "Editor",
5
+ description: "Can create, edit and publish content and upload assets.",
6
+ },
7
+ {
8
+ value: "admin",
9
+ label: "Admin",
10
+ description: "Everything an editor can do, plus manage users.",
11
+ },
12
+ ];
13
+ export function isRole(value) {
14
+ return value === "admin" || value === "editor";
15
+ }
@@ -0,0 +1,102 @@
1
+ import type { AssetContainerConfig, CollectionConfig, SingletonConfig, VestryConfig } from "../config/types.js";
2
+ import { splitFrontmatter } from "./entries.js";
3
+ import type { BuildIndex } from "./index-types.js";
4
+ import { type ContentSource } from "./source.js";
5
+ export interface EntrySummary {
6
+ slug: string;
7
+ path: string;
8
+ sha: string;
9
+ title: string;
10
+ data: Record<string, unknown>;
11
+ status: "published" | "draft" | null;
12
+ excerpt?: string;
13
+ }
14
+ export interface EntryDetail extends EntrySummary {
15
+ body: string;
16
+ /** Head commit when the entry was read, for conflict detection. */
17
+ head: string;
18
+ }
19
+ export interface Upload {
20
+ /** Repository path to write the file to. */
21
+ path: string;
22
+ /** Base64 file contents. */
23
+ base64: string;
24
+ }
25
+ export interface SaveEntryInput {
26
+ data: Record<string, unknown>;
27
+ body?: string;
28
+ /**
29
+ * Full file text composed (and formatted) by the client. When present it is
30
+ * written verbatim; `data`/`body` are still used for slugs and messages.
31
+ */
32
+ raw?: string;
33
+ /** Slug for a new entry (otherwise derived from the slug template / title). */
34
+ slug?: string;
35
+ /** Blob SHA the client last saw for this entry (omit for new entries). */
36
+ sha?: string | null;
37
+ uploads?: Upload[];
38
+ message?: string;
39
+ }
40
+ export interface AssetItem {
41
+ name: string;
42
+ path: string;
43
+ sha: string;
44
+ size?: number;
45
+ kind: "image" | "file";
46
+ ext: string;
47
+ }
48
+ export declare class ContentService {
49
+ config: VestryConfig;
50
+ source: ContentSource;
51
+ private index;
52
+ constructor(config: VestryConfig, source: ContentSource, index: BuildIndex);
53
+ collection(key: string): CollectionConfig;
54
+ singleton(key: string): SingletonConfig;
55
+ assetContainer(key: string): AssetContainerConfig;
56
+ listEntries(key: string): Promise<EntrySummary[]>;
57
+ /**
58
+ * Frontmatter for a file: served from the build-time index when the blob
59
+ * SHA still matches, otherwise read from the source.
60
+ */
61
+ private frontmatterFor;
62
+ getEntry(key: string, slug: string): Promise<EntryDetail | null>;
63
+ saveEntry(key: string, slug: string | null, input: SaveEntryInput): Promise<{
64
+ slug: string;
65
+ sha: string;
66
+ commit: string;
67
+ path: string;
68
+ }>;
69
+ deleteEntry(key: string, slug: string): Promise<{
70
+ commit: string;
71
+ }>;
72
+ getSingleton(key: string): Promise<EntryDetail>;
73
+ saveSingleton(key: string, input: SaveEntryInput): Promise<{
74
+ sha: string;
75
+ commit: string;
76
+ }>;
77
+ listAssets(containerKey: string, subdir?: string): Promise<{
78
+ items: AssetItem[];
79
+ folders: string[];
80
+ }>;
81
+ uploadAssets(containerKey: string, subdir: string, files: {
82
+ name: string;
83
+ base64: string;
84
+ }[]): Promise<{
85
+ commit: string;
86
+ paths: string[];
87
+ }>;
88
+ deleteAsset(containerKey: string, path: string): Promise<{
89
+ commit: string;
90
+ }>;
91
+ /** Raw file read restricted to configured asset containers and entry directories. */
92
+ readFile(path: string): Promise<{
93
+ bytes: Uint8Array;
94
+ sha: string;
95
+ } | null>;
96
+ allowedDirectories(): string[];
97
+ titleOf(col: CollectionConfig, data: Record<string, unknown>, slug: string): string;
98
+ newSlug(col: CollectionConfig, data: Record<string, unknown>): string;
99
+ commitMessage(col: CollectionConfig, action: string, slug: string, title: string, collection: string, override?: string): string;
100
+ }
101
+ export declare function safeFileName(name: string): string;
102
+ export { splitFrontmatter };