@kdrgny/justjson-astro 1.6.0 → 1.8.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/README.md CHANGED
@@ -97,6 +97,18 @@ export const collections = {
97
97
  Format checks (is this a real URL?) are left to `justjson validate` on purpose,
98
98
  so a typo in one entry never breaks your build in a surprising place.
99
99
 
100
+ ## Theming
101
+
102
+ The generated site reads `content/_theme.json` through a small helper:
103
+
104
+ ```ts
105
+ import { parseTheme, themeCss } from '@kdrgny/justjson-astro/theme'
106
+ ```
107
+
108
+ `themeCss(parseTheme(theme))` returns a `:root` block of CSS variables
109
+ (`--jj-bg`, `--jj-accent`, `--jj-font`, `--jj-radius`…). Edit the theme in the
110
+ JustJSON **Design** tab; the site updates on the next reload.
111
+
100
112
  ## License
101
113
 
102
114
  [MIT](https://github.com/kdrgny-dev/justjson/blob/main/LICENSE) © Kadir Günay
@@ -0,0 +1,211 @@
1
+ // ../core/dist/index.js
2
+ import { z } from "zod";
3
+ var JustJsonError = class extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = new.target.name;
7
+ }
8
+ };
9
+ var SchemaError = class extends JustJsonError {
10
+ };
11
+ var fieldTypes = [
12
+ "text",
13
+ "richtext",
14
+ "number",
15
+ "boolean",
16
+ "date",
17
+ "select",
18
+ "relation",
19
+ "image",
20
+ "url",
21
+ "email",
22
+ "list",
23
+ "color",
24
+ "group"
25
+ ];
26
+ var zField = z.lazy(
27
+ () => z.object({
28
+ key: z.string().min(1),
29
+ label: z.string().optional(),
30
+ type: z.enum(fieldTypes),
31
+ required: z.boolean().optional(),
32
+ options: z.array(z.string()).optional(),
33
+ to: z.string().optional(),
34
+ fields: z.array(zField).optional()
35
+ }).superRefine((field, ctx) => {
36
+ if (field.type === "select" && (!field.options || field.options.length === 0)) {
37
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a select field requires options" });
38
+ }
39
+ if (field.type === "relation" && !field.to) {
40
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'a relation field requires "to"' });
41
+ }
42
+ if (field.type === "group" && (!field.fields || field.fields.length === 0)) {
43
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a group field requires fields" });
44
+ }
45
+ })
46
+ );
47
+ var zCollection = z.object({
48
+ name: z.string().min(1),
49
+ label: z.string().optional(),
50
+ path: z.string().min(1),
51
+ fields: z.array(zField)
52
+ });
53
+ var zSingleton = z.object({
54
+ name: z.string().min(1),
55
+ label: z.string().optional(),
56
+ path: z.string().min(1),
57
+ fields: z.array(zField)
58
+ });
59
+ var zSchema = z.object({
60
+ version: z.literal(1),
61
+ collections: z.array(zCollection),
62
+ singletons: z.array(zSingleton)
63
+ }).superRefine((schema, ctx) => {
64
+ const names = /* @__PURE__ */ new Set();
65
+ const paths = /* @__PURE__ */ new Set();
66
+ const collectionNames = new Set(schema.collections.map((c) => c.name));
67
+ const containers = [...schema.collections, ...schema.singletons];
68
+ for (const c of containers) {
69
+ if (names.has(c.name)) {
70
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate name: ${c.name}` });
71
+ }
72
+ names.add(c.name);
73
+ if (c.path.includes("..") || c.path.startsWith("/") || c.path.includes("\\")) {
74
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `unsafe path: ${c.path}` });
75
+ }
76
+ if (paths.has(c.path)) {
77
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate path: ${c.path}` });
78
+ }
79
+ paths.add(c.path);
80
+ const keys = /* @__PURE__ */ new Set();
81
+ for (const f of c.fields) {
82
+ if (keys.has(f.key)) {
83
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate field key: ${f.key}` });
84
+ }
85
+ keys.add(f.key);
86
+ if (f.type === "relation" && f.to && !collectionNames.has(f.to)) {
87
+ ctx.addIssue({
88
+ code: z.ZodIssueCode.custom,
89
+ message: `relation target does not exist: ${f.to}`
90
+ });
91
+ }
92
+ }
93
+ }
94
+ });
95
+ function parseSchema(input) {
96
+ const result = zSchema.safeParse(input);
97
+ if (result.success) return result.data;
98
+ const lines = result.error.issues.map(
99
+ (i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message
100
+ );
101
+ throw new SchemaError(lines.join("\n"));
102
+ }
103
+ var STATUS_KEY = "_status";
104
+ function entryStatus(data) {
105
+ return data[STATUS_KEY] === "draft" ? "draft" : "published";
106
+ }
107
+ var PALETTES = [
108
+ {
109
+ id: "paper",
110
+ label: "Paper",
111
+ bg: "#faf9f5",
112
+ text: "#1c1a17",
113
+ muted: "#6b6558",
114
+ border: "#e4e0d5"
115
+ },
116
+ {
117
+ id: "plain",
118
+ label: "Plain",
119
+ bg: "#ffffff",
120
+ text: "#18181b",
121
+ muted: "#71717a",
122
+ border: "#e4e4e7"
123
+ },
124
+ { id: "ink", label: "Ink", bg: "#141414", text: "#f4f4f5", muted: "#a1a1aa", border: "#2a2a2a" },
125
+ {
126
+ id: "sand",
127
+ label: "Sand",
128
+ bg: "#f5f1e8",
129
+ text: "#2b2620",
130
+ muted: "#7a7060",
131
+ border: "#ddd5c4"
132
+ },
133
+ { id: "sky", label: "Sky", bg: "#f7fafc", text: "#111b26", muted: "#5d7183", border: "#dbe6ef" }
134
+ ];
135
+ var THEME_FONTS = [
136
+ {
137
+ id: "sans",
138
+ label: "Sans",
139
+ stack: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
140
+ },
141
+ { id: "serif", label: "Serif", stack: 'ui-serif, Georgia, "Times New Roman", serif' },
142
+ { id: "mono", label: "Mono", stack: "ui-monospace, SFMono-Regular, Menlo, monospace" },
143
+ {
144
+ id: "humanist",
145
+ label: "Humanist",
146
+ stack: '"Optima", "Gill Sans", "Trebuchet MS", ui-sans-serif, sans-serif'
147
+ }
148
+ ];
149
+ var DENSITY_SCALE = {
150
+ tight: { gap: "0.5rem", page: "2rem", lead: "1.5" },
151
+ normal: { gap: "0.75rem", page: "3rem", lead: "1.65" },
152
+ roomy: { gap: "1.15rem", page: "4.5rem", lead: "1.8" }
153
+ };
154
+ var HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
155
+ var MAX_RADIUS = 24;
156
+ function defaultTheme() {
157
+ return { palette: "paper", accent: "#b8860b", font: "sans", radius: 8, density: "normal" };
158
+ }
159
+ function pick(list, id, fallback) {
160
+ return typeof id === "string" && list.some((x) => x.id === id) ? id : fallback;
161
+ }
162
+ function parseTheme(input) {
163
+ const base = defaultTheme();
164
+ if (typeof input !== "object" || input === null) return base;
165
+ const raw = input;
166
+ const radius = typeof raw.radius === "number" ? raw.radius : base.radius;
167
+ const density = raw.density;
168
+ return {
169
+ palette: pick(PALETTES, raw.palette, base.palette),
170
+ accent: typeof raw.accent === "string" && HEX.test(raw.accent) ? raw.accent : base.accent,
171
+ font: pick(THEME_FONTS, raw.font, base.font),
172
+ radius: Math.min(MAX_RADIUS, Math.max(0, Math.round(radius))),
173
+ density: density === "tight" || density === "normal" || density === "roomy" ? density : base.density
174
+ };
175
+ }
176
+ function paletteOf(theme) {
177
+ return PALETTES.find((p) => p.id === theme.palette) ?? PALETTES[0];
178
+ }
179
+ function fontOf(theme) {
180
+ return THEME_FONTS.find((f) => f.id === theme.font) ?? THEME_FONTS[0];
181
+ }
182
+ function themeCss(theme) {
183
+ const palette = paletteOf(theme);
184
+ const font = fontOf(theme);
185
+ const scale = DENSITY_SCALE[theme.density];
186
+ return [
187
+ ":root {",
188
+ ` --jj-bg: ${palette.bg};`,
189
+ ` --jj-text: ${palette.text};`,
190
+ ` --jj-muted: ${palette.muted};`,
191
+ ` --jj-border: ${palette.border};`,
192
+ ` --jj-accent: ${theme.accent};`,
193
+ ` --jj-font: ${font.stack};`,
194
+ ` --jj-radius: ${theme.radius}px;`,
195
+ ` --jj-gap: ${scale.gap};`,
196
+ ` --jj-page: ${scale.page};`,
197
+ ` --jj-lead: ${scale.lead};`,
198
+ "}"
199
+ ].join("\n");
200
+ }
201
+
202
+ export {
203
+ parseSchema,
204
+ STATUS_KEY,
205
+ entryStatus,
206
+ PALETTES,
207
+ THEME_FONTS,
208
+ defaultTheme,
209
+ parseTheme,
210
+ themeCss
211
+ };
package/dist/index.js CHANGED
@@ -1,144 +1,43 @@
1
+ import {
2
+ STATUS_KEY,
3
+ entryStatus,
4
+ parseSchema
5
+ } from "./chunk-LXL6TYDO.js";
6
+
1
7
  // src/loader.ts
2
8
  import { readFile, readdir } from "fs/promises";
3
9
  import { fileURLToPath } from "url";
4
10
 
5
- // ../core/dist/index.js
6
- import { z } from "zod";
7
- var JustJsonError = class extends Error {
8
- constructor(message) {
9
- super(message);
10
- this.name = new.target.name;
11
- }
12
- };
13
- var SchemaError = class extends JustJsonError {
14
- };
15
- var fieldTypes = [
16
- "text",
17
- "richtext",
18
- "number",
19
- "boolean",
20
- "date",
21
- "select",
22
- "relation",
23
- "image",
24
- "url",
25
- "email",
26
- "list",
27
- "color",
28
- "group"
29
- ];
30
- var zField = z.lazy(
31
- () => z.object({
32
- key: z.string().min(1),
33
- label: z.string().optional(),
34
- type: z.enum(fieldTypes),
35
- required: z.boolean().optional(),
36
- options: z.array(z.string()).optional(),
37
- to: z.string().optional(),
38
- fields: z.array(zField).optional()
39
- }).superRefine((field, ctx) => {
40
- if (field.type === "select" && (!field.options || field.options.length === 0)) {
41
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a select field requires options" });
42
- }
43
- if (field.type === "relation" && !field.to) {
44
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'a relation field requires "to"' });
45
- }
46
- if (field.type === "group" && (!field.fields || field.fields.length === 0)) {
47
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: "a group field requires fields" });
48
- }
49
- })
50
- );
51
- var zCollection = z.object({
52
- name: z.string().min(1),
53
- label: z.string().optional(),
54
- path: z.string().min(1),
55
- fields: z.array(zField)
56
- });
57
- var zSingleton = z.object({
58
- name: z.string().min(1),
59
- label: z.string().optional(),
60
- path: z.string().min(1),
61
- fields: z.array(zField)
62
- });
63
- var zSchema = z.object({
64
- version: z.literal(1),
65
- collections: z.array(zCollection),
66
- singletons: z.array(zSingleton)
67
- }).superRefine((schema, ctx) => {
68
- const names = /* @__PURE__ */ new Set();
69
- const paths = /* @__PURE__ */ new Set();
70
- const collectionNames = new Set(schema.collections.map((c) => c.name));
71
- const containers = [...schema.collections, ...schema.singletons];
72
- for (const c of containers) {
73
- if (names.has(c.name)) {
74
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate name: ${c.name}` });
75
- }
76
- names.add(c.name);
77
- if (c.path.includes("..") || c.path.startsWith("/") || c.path.includes("\\")) {
78
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: `unsafe path: ${c.path}` });
79
- }
80
- if (paths.has(c.path)) {
81
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate path: ${c.path}` });
82
- }
83
- paths.add(c.path);
84
- const keys = /* @__PURE__ */ new Set();
85
- for (const f of c.fields) {
86
- if (keys.has(f.key)) {
87
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate field key: ${f.key}` });
88
- }
89
- keys.add(f.key);
90
- if (f.type === "relation" && f.to && !collectionNames.has(f.to)) {
91
- ctx.addIssue({
92
- code: z.ZodIssueCode.custom,
93
- message: `relation target does not exist: ${f.to}`
94
- });
95
- }
96
- }
97
- }
98
- });
99
- function parseSchema(input) {
100
- const result = zSchema.safeParse(input);
101
- if (result.success) return result.data;
102
- const lines = result.error.issues.map(
103
- (i) => i.path.length ? `${i.path.join(".")}: ${i.message}` : i.message
104
- );
105
- throw new SchemaError(lines.join("\n"));
106
- }
107
- var STATUS_KEY = "_status";
108
- function entryStatus(data) {
109
- return data[STATUS_KEY] === "draft" ? "draft" : "published";
110
- }
111
-
112
11
  // src/zod-schema.ts
113
- import { z as z2 } from "zod";
12
+ import { z } from "zod";
114
13
  function base(field) {
115
14
  switch (field.type) {
116
15
  case "number":
117
- return z2.number();
16
+ return z.number();
118
17
  case "boolean":
119
- return z2.boolean();
18
+ return z.boolean();
120
19
  case "relation":
121
20
  case "list":
122
- return z2.array(z2.string());
21
+ return z.array(z.string());
123
22
  case "group":
124
23
  return fieldsToZod(field.fields ?? []);
125
24
  case "select": {
126
25
  const options = field.options ?? [];
127
- return options.length > 0 ? z2.enum(options) : z2.string();
26
+ return options.length > 0 ? z.enum(options) : z.string();
128
27
  }
129
28
  default:
130
- return z2.string();
29
+ return z.string();
131
30
  }
132
31
  }
133
32
  function fieldsToZod(fields) {
134
33
  const shape = {
135
- [STATUS_KEY]: z2.enum(["draft", "published"]).optional()
34
+ [STATUS_KEY]: z.enum(["draft", "published"]).optional()
136
35
  };
137
36
  for (const field of fields) {
138
37
  const type = base(field);
139
38
  shape[field.key] = field.required ? type : type.optional();
140
39
  }
141
- return z2.object(shape);
40
+ return z.object(shape);
142
41
  }
143
42
 
144
43
  // src/loader.ts
@@ -0,0 +1 @@
1
+ export { Density, PALETTES, Palette, THEME_FONTS, Theme, ThemeFont, defaultTheme, parseTheme, themeCss } from '@justjson/core';
package/dist/theme.js ADDED
@@ -0,0 +1,14 @@
1
+ import {
2
+ PALETTES,
3
+ THEME_FONTS,
4
+ defaultTheme,
5
+ parseTheme,
6
+ themeCss
7
+ } from "./chunk-LXL6TYDO.js";
8
+ export {
9
+ PALETTES,
10
+ THEME_FONTS,
11
+ defaultTheme,
12
+ parseTheme,
13
+ themeCss
14
+ };
package/package.json CHANGED
@@ -1,29 +1,43 @@
1
1
  {
2
2
  "name": "@kdrgny/justjson-astro",
3
- "version": "1.6.0",
4
- "description": "Astro content loader for JustJSON turn your content/ folder into typed Astro content collections.",
3
+ "version": "1.8.0",
4
+ "description": "Astro content loader for JustJSON \u2014 turn your content/ folder into typed Astro content collections.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
- "author": "Kadir Günay",
10
+ "author": "Kadir G\u00fcnay",
11
11
  "homepage": "https://github.com/kdrgny-dev/justjson",
12
12
  "repository": {
13
13
  "type": "git",
14
14
  "url": "git+https://github.com/kdrgny-dev/justjson.git",
15
15
  "directory": "packages/astro"
16
16
  },
17
- "keywords": ["astro", "astro-loader", "astro-integration", "cms", "json", "content", "justjson"],
17
+ "keywords": [
18
+ "astro",
19
+ "astro-loader",
20
+ "astro-integration",
21
+ "cms",
22
+ "json",
23
+ "content",
24
+ "justjson"
25
+ ],
18
26
  "exports": {
19
27
  ".": {
20
28
  "types": "./dist/index.d.ts",
21
29
  "import": "./dist/index.js"
30
+ },
31
+ "./theme": {
32
+ "types": "./dist/theme.d.ts",
33
+ "import": "./dist/theme.js"
22
34
  }
23
35
  },
24
36
  "main": "./dist/index.js",
25
37
  "types": "./dist/index.d.ts",
26
- "files": ["dist"],
38
+ "files": [
39
+ "dist"
40
+ ],
27
41
  "scripts": {
28
42
  "build": "tsup",
29
43
  "dev": "tsup --watch",