octocms 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 (64) hide show
  1. package/dist/agentDocs-Z5BI2Y2G.js +38 -0
  2. package/dist/agentDocs-Z5BI2Y2G.js.map +1 -0
  3. package/dist/chunk-4MPOTHTY.js +9 -0
  4. package/dist/chunk-4MPOTHTY.js.map +1 -0
  5. package/dist/chunk-4VLN5EX2.js +9204 -0
  6. package/dist/chunk-4VLN5EX2.js.map +1 -0
  7. package/dist/chunk-6PHFHGTZ.js +35 -0
  8. package/dist/chunk-6PHFHGTZ.js.map +1 -0
  9. package/dist/chunk-7CFFE2I6.js +55 -0
  10. package/dist/chunk-7CFFE2I6.js.map +1 -0
  11. package/dist/chunk-B47VXAHT.js +28 -0
  12. package/dist/chunk-B47VXAHT.js.map +1 -0
  13. package/dist/chunk-BRTXBBVQ.js +46 -0
  14. package/dist/chunk-BRTXBBVQ.js.map +1 -0
  15. package/dist/chunk-C62C776U.js +79 -0
  16. package/dist/chunk-C62C776U.js.map +1 -0
  17. package/dist/chunk-I7KNSICQ.js +114 -0
  18. package/dist/chunk-I7KNSICQ.js.map +1 -0
  19. package/dist/chunk-Q73JSGXV.js +123 -0
  20. package/dist/chunk-Q73JSGXV.js.map +1 -0
  21. package/dist/chunk-W6QJTGBC.js +57 -0
  22. package/dist/chunk-W6QJTGBC.js.map +1 -0
  23. package/dist/cli/index.js +196 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/components/public/index.d.mts +40 -0
  26. package/dist/components/public/index.js +401 -0
  27. package/dist/components/public/index.js.map +1 -0
  28. package/dist/config.d.mts +4 -0
  29. package/dist/config.js +13 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/defineConfig.d.mts +126 -0
  32. package/dist/defineConfig.js +8 -0
  33. package/dist/defineConfig.js.map +1 -0
  34. package/dist/dev-QY534GEH.js +87 -0
  35. package/dist/dev-QY534GEH.js.map +1 -0
  36. package/dist/index.d.mts +5 -0
  37. package/dist/index.js +17 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/init-UGUTJFFI.js +145 -0
  40. package/dist/init-UGUTJFFI.js.map +1 -0
  41. package/dist/jiti-VYEW7A6R.js +3068 -0
  42. package/dist/jiti-VYEW7A6R.js.map +1 -0
  43. package/dist/localReader-I2THES24.js +40 -0
  44. package/dist/localReader-I2THES24.js.map +1 -0
  45. package/dist/query.d.mts +112 -0
  46. package/dist/query.js +11 -0
  47. package/dist/query.js.map +1 -0
  48. package/dist/types.d.mts +352 -0
  49. package/dist/types.js +1 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/typesGen-WBC6CNBG.js +241 -0
  52. package/dist/typesGen-WBC6CNBG.js.map +1 -0
  53. package/dist/update-RMGZMS56.js +57 -0
  54. package/dist/update-RMGZMS56.js.map +1 -0
  55. package/dist/validate-OTJ6ULMP.js +297 -0
  56. package/dist/validate-OTJ6ULMP.js.map +1 -0
  57. package/dist/withOctoCMS.d.mts +6 -0
  58. package/dist/withOctoCMS.js +9 -0
  59. package/dist/withOctoCMS.js.map +1 -0
  60. package/docs/index.md +27 -0
  61. package/docs/overview.md +113 -0
  62. package/docs/schema.md +279 -0
  63. package/globals.css +198 -0
  64. package/package.json +116 -0
@@ -0,0 +1,241 @@
1
+ import {
2
+ validateConfig
3
+ } from "./chunk-Q73JSGXV.js";
4
+ import {
5
+ loadCollections,
6
+ loadFieldTypes,
7
+ loadProjectConfig
8
+ } from "./chunk-C62C776U.js";
9
+ import {
10
+ log
11
+ } from "./chunk-6PHFHGTZ.js";
12
+ import "./chunk-W6QJTGBC.js";
13
+
14
+ // cli/commands/typesGen.ts
15
+ import { mkdirSync, writeFileSync } from "fs";
16
+ import { join } from "path";
17
+
18
+ // cli/lib/codegen.ts
19
+ var CODEGEN_BANNER = `/*
20
+ * AUTO-GENERATED \u2014 DO NOT EDIT.
21
+ * Generated by scripts/generate-types.ts from cms/octocms.config.ts.
22
+ * Run \`npm run types:gen\` to regenerate.
23
+ */
24
+
25
+ `;
26
+ function pascalCase(s) {
27
+ return s.charAt(0).toUpperCase() + s.slice(1);
28
+ }
29
+ function fieldToTSType(field, collectionNames, mode = "resolved") {
30
+ var _a, _b;
31
+ switch (field.format) {
32
+ case "string":
33
+ return field.list ? "string[]" : "string";
34
+ case "text":
35
+ case "markdown":
36
+ case "slug":
37
+ case "url":
38
+ case "color":
39
+ return "string";
40
+ case "richtext":
41
+ return "RichTextDocument";
42
+ case "boolean":
43
+ return "'true' | 'false'";
44
+ case "number":
45
+ return "number | null";
46
+ case "datetime":
47
+ return "string | null";
48
+ case "image":
49
+ return mode === "raw" ? "string" : "ResolvedImageField";
50
+ case "json":
51
+ return "unknown";
52
+ case "select": {
53
+ const union = field.options.map((o) => `'${o.value}'`).join(" | ");
54
+ if (field.multiple) {
55
+ return field.options.length > 1 ? `(${union})[]` : `${union}[]`;
56
+ }
57
+ return union;
58
+ }
59
+ case "reference": {
60
+ if (mode === "raw") {
61
+ const cardinality = (_b = (_a = field.reference) == null ? void 0 : _a.cardinality) != null ? _b : "many";
62
+ return cardinality === "one" ? "string" : "string";
63
+ }
64
+ return referenceFieldType(field, collectionNames);
65
+ }
66
+ case "conditional": {
67
+ if (mode === "raw") {
68
+ return "unknown";
69
+ }
70
+ return conditionalFieldType(field, collectionNames);
71
+ }
72
+ default:
73
+ return "unknown";
74
+ }
75
+ }
76
+ function referenceFieldType(field, collectionNames) {
77
+ var _a, _b, _c, _d;
78
+ const cols = (_b = (_a = field.reference) == null ? void 0 : _a.collections) != null ? _b : field.collection ? [field.collection] : [...collectionNames];
79
+ const entryTypes = cols.map((c) => `${pascalCase(c)}Entry`);
80
+ const union = entryTypes.length > 1 ? entryTypes.join(" | ") : entryTypes[0];
81
+ const cardinality = (_d = (_c = field.reference) == null ? void 0 : _c.cardinality) != null ? _d : "many";
82
+ if (cardinality === "one") {
83
+ return entryTypes.length > 1 ? `(${union}) | null` : `${union} | null`;
84
+ }
85
+ return entryTypes.length > 1 ? `(${union})[]` : `${union}[]`;
86
+ }
87
+ function conditionalFieldType(field, collectionNames) {
88
+ const branchTypes = field.conditional.branches.map((branch) => branchValueType(branch, collectionNames));
89
+ return branchTypes.join(" | ");
90
+ }
91
+ function branchValueType(branch, collectionNames) {
92
+ if ("collection" in branch && typeof branch.collection === "string") {
93
+ return `${pascalCase(branch.collection)}Entry`;
94
+ }
95
+ if (branch.fields) {
96
+ const entries = Object.entries(branch.fields);
97
+ if (entries.length === 0) return "{}";
98
+ const props = entries.map(([name, f]) => ` ${name}: ${fieldToTSType(f, collectionNames)};`);
99
+ return `{
100
+ ${props.join("\n")}
101
+ }`;
102
+ }
103
+ return "unknown";
104
+ }
105
+ function generateTypes(cfg, collectionNames) {
106
+ const lines = [
107
+ CODEGEN_BANNER + "import type { EntryStatus, ResolvedImageField } from 'octocms/types';",
108
+ ""
109
+ ];
110
+ for (const key of collectionNames) {
111
+ const col = cfg.collections[key];
112
+ const pascal = pascalCase(key);
113
+ lines.push(`export interface ${pascal}Fields {`);
114
+ for (const [fieldName, field] of Object.entries(col.fields)) {
115
+ const tsType = fieldToTSType(field, collectionNames);
116
+ lines.push(` ${fieldName}: ${tsType};`);
117
+ }
118
+ lines.push("}");
119
+ lines.push("");
120
+ }
121
+ for (const key of collectionNames) {
122
+ const pascal = pascalCase(key);
123
+ lines.push(`export interface ${pascal}Entry {`);
124
+ lines.push(` sys: { id: string; type: '${key}'; status: EntryStatus };`);
125
+ lines.push(` fields: ${pascal}Fields;`);
126
+ lines.push("}");
127
+ lines.push("");
128
+ }
129
+ const entryNames = collectionNames.map((k) => `${pascalCase(k)}Entry`);
130
+ lines.push(`export type AnyEntry = ${entryNames.join(" | ")};`);
131
+ lines.push("");
132
+ lines.push("export type EntryMap = {");
133
+ for (const key of collectionNames) {
134
+ lines.push(` ${key}: ${pascalCase(key)}Entry;`);
135
+ }
136
+ lines.push("};");
137
+ lines.push("");
138
+ return lines.join("\n");
139
+ }
140
+ function generateEnums(cfg, collectionNames, fieldTypes) {
141
+ const lines = [];
142
+ lines.push("export const CollectionName = {");
143
+ for (const key of collectionNames) {
144
+ lines.push(` ${pascalCase(key)}: '${key}',`);
145
+ }
146
+ lines.push("} as const;");
147
+ lines.push("export type CollectionName = (typeof CollectionName)[keyof typeof CollectionName];");
148
+ lines.push("");
149
+ lines.push(`export const COLLECTION_NAMES = [${collectionNames.map((k) => `'${k}'`).join(", ")}] as const;`);
150
+ lines.push("");
151
+ for (const key of collectionNames) {
152
+ const col = cfg.collections[key];
153
+ for (const [fieldName, field] of Object.entries(col.fields)) {
154
+ if (field.format !== "select") continue;
155
+ const enumName = `${pascalCase(key)}${pascalCase(fieldName)}Option`;
156
+ lines.push(`export const ${enumName} = {`);
157
+ for (const opt of field.options) {
158
+ lines.push(` ${pascalCase(opt.value)}: '${opt.value}',`);
159
+ }
160
+ lines.push("} as const;");
161
+ lines.push(`export type ${enumName} = (typeof ${enumName})[keyof typeof ${enumName}];`);
162
+ lines.push("");
163
+ }
164
+ }
165
+ lines.push("export const FieldFormat = {");
166
+ for (const ft of fieldTypes) {
167
+ lines.push(` ${pascalCase(ft)}: '${ft}',`);
168
+ }
169
+ lines.push("} as const;");
170
+ lines.push("export type FieldFormat = (typeof FieldFormat)[keyof typeof FieldFormat];");
171
+ lines.push("");
172
+ return CODEGEN_BANNER + lines.join("\n");
173
+ }
174
+ function generateContentDecls(cfg, collectionNames) {
175
+ const lines = [
176
+ CODEGEN_BANNER + "import type { EntryStatus } from 'octocms/types';\n\n// Raw on-disk types (before query() processing).\n// Image fields are UUID strings, reference fields are key strings,\n// markdown fields are omitted (stored in companion .md files),\n// richtext fields are omitted (stored in companion .mdx files).\n"
177
+ ];
178
+ for (const key of collectionNames) {
179
+ const col = cfg.collections[key];
180
+ const pascal = pascalCase(key);
181
+ lines.push(`export interface Raw${pascal}Fields {`);
182
+ for (const [fieldName, field] of Object.entries(col.fields)) {
183
+ if (field.format === "markdown" || field.format === "richtext") continue;
184
+ const tsType = fieldToTSType(field, collectionNames, "raw");
185
+ lines.push(` ${fieldName}: ${tsType};`);
186
+ }
187
+ lines.push("}");
188
+ lines.push("");
189
+ lines.push(`export interface Raw${pascal}Entry {`);
190
+ lines.push(` sys: { id: string; type: '${key}'; status: EntryStatus };`);
191
+ lines.push(` fields: Raw${pascal}Fields;`);
192
+ lines.push("}");
193
+ lines.push("");
194
+ }
195
+ lines.push("export type RawEntryMap = {");
196
+ for (const key of collectionNames) {
197
+ lines.push(` ${key}: Raw${pascalCase(key)}Entry;`);
198
+ }
199
+ lines.push("};");
200
+ lines.push("");
201
+ return lines.join("\n");
202
+ }
203
+ function generateIndex() {
204
+ return CODEGEN_BANNER + "export * from './types';\nexport * from './enums';\nexport * from './query';\n";
205
+ }
206
+
207
+ // cli/commands/typesGen.ts
208
+ async function typesGenCommand(projectRoot) {
209
+ log.header("Generate types");
210
+ const config = await loadProjectConfig(projectRoot);
211
+ const collections = await loadCollections(projectRoot);
212
+ const fieldTypes = await loadFieldTypes(projectRoot);
213
+ log.info("Validating config...");
214
+ try {
215
+ validateConfig(config, collections);
216
+ } catch (e) {
217
+ log.error(String(e.message));
218
+ process.exitCode = 1;
219
+ return;
220
+ }
221
+ log.success(`${collections.length} collections validated`);
222
+ log.blank();
223
+ log.info("Generating types...");
224
+ const generatedDir = join(projectRoot, "cms", "__generated__");
225
+ mkdirSync(generatedDir, { recursive: true });
226
+ const files = [
227
+ { name: "types.ts", content: generateTypes(config, collections) },
228
+ { name: "enums.ts", content: generateEnums(config, collections, fieldTypes) },
229
+ { name: "content.d.ts", content: generateContentDecls(config, collections) },
230
+ { name: "index.ts", content: generateIndex() }
231
+ ];
232
+ for (const file of files) {
233
+ writeFileSync(join(generatedDir, file.name), file.content, "utf8");
234
+ log.success(`cms/__generated__/${file.name}`);
235
+ }
236
+ log.blank();
237
+ }
238
+ export {
239
+ typesGenCommand
240
+ };
241
+ //# sourceMappingURL=typesGen-WBC6CNBG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../cli/commands/typesGen.ts","../cli/lib/codegen.ts"],"sourcesContent":["/**\n * `octocms types:gen` — Generate TypeScript types from next.config.ts.\n *\n * Produces `cms/__generated__/types.ts`, `enums.ts`, `content.d.ts`, and `index.ts`.\n * This is the CLI equivalent of `npm run types:gen`.\n */\n\nimport { mkdirSync, writeFileSync } from 'fs';\nimport { join } from 'path';\n\nimport { generateContentDecls, generateEnums, generateIndex, generateTypes } from '../lib/codegen';\nimport { log } from '../lib/logger';\nimport { loadCollections, loadFieldTypes, loadProjectConfig } from '../lib/project';\nimport { validateConfig } from '../lib/validateConfig';\n\nexport async function typesGenCommand(projectRoot: string): Promise<void> {\n log.header('Generate types');\n\n const config = await loadProjectConfig(projectRoot);\n const collections = await loadCollections(projectRoot);\n const fieldTypes = await loadFieldTypes(projectRoot);\n\n log.info('Validating config...');\n try {\n validateConfig(config, collections);\n } catch (e) {\n log.error(String((e as Error).message));\n process.exitCode = 1;\n return;\n }\n log.success(`${collections.length} collections validated`);\n\n log.blank();\n log.info('Generating types...');\n\n const generatedDir = join(projectRoot, 'cms', '__generated__');\n mkdirSync(generatedDir, { recursive: true });\n\n const files = [\n { name: 'types.ts', content: generateTypes(config, collections) },\n { name: 'enums.ts', content: generateEnums(config, collections, fieldTypes) },\n { name: 'content.d.ts', content: generateContentDecls(config, collections) },\n { name: 'index.ts', content: generateIndex() },\n ];\n\n for (const file of files) {\n writeFileSync(join(generatedDir, file.name), file.content, 'utf8');\n log.success(`cms/__generated__/${file.name}`);\n }\n\n log.blank();\n}\n","/**\n * Type generation functions — pure functions that produce TypeScript source strings.\n *\n * Moved here from `scripts/generate-types.ts` as part of Phase 4 (CLI).\n * The script now imports these functions; the CLI `types:gen` command calls them directly.\n */\n\nimport type { CollectionField, Config, ConditionalBranchConfig } from '../../types';\n\nexport const CODEGEN_BANNER = `/*\n * AUTO-GENERATED — DO NOT EDIT.\n * Generated by scripts/generate-types.ts from cms/octocms.config.ts.\n * Run \\`npm run types:gen\\` to regenerate.\n */\n\n`;\n\n/** Capitalize the first letter of a string: `post` → `Post`, `homePage` → `HomePage`. */\nexport function pascalCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/** Build the TypeScript type string for a single field definition. */\nexport function fieldToTSType(\n field: CollectionField,\n collectionNames: readonly string[],\n mode: 'resolved' | 'raw' = 'resolved',\n): string {\n switch (field.format) {\n case 'string':\n return field.list ? 'string[]' : 'string';\n case 'text':\n case 'markdown':\n case 'slug':\n case 'url':\n case 'color':\n return 'string';\n case 'richtext':\n return 'RichTextDocument';\n case 'boolean':\n return \"'true' | 'false'\";\n case 'number':\n return 'number | null';\n case 'datetime':\n return 'string | null';\n case 'image':\n return mode === 'raw' ? 'string' : 'ResolvedImageField';\n case 'json':\n return 'unknown';\n case 'select': {\n const union = field.options.map((o) => `'${o.value}'`).join(' | ');\n if (field.multiple) {\n return field.options.length > 1 ? `(${union})[]` : `${union}[]`;\n }\n return union;\n }\n case 'reference': {\n if (mode === 'raw') {\n const cardinality = field.reference?.cardinality ?? 'many';\n return cardinality === 'one' ? 'string' : 'string';\n }\n return referenceFieldType(field, collectionNames);\n }\n case 'conditional': {\n if (mode === 'raw') {\n return 'unknown';\n }\n return conditionalFieldType(field, collectionNames);\n }\n default:\n return 'unknown';\n }\n}\n\nfunction referenceFieldType(\n field: Extract<CollectionField, { format: 'reference' }>,\n collectionNames: readonly string[],\n): string {\n const cols = field.reference?.collections ?? (field.collection ? [field.collection] : [...collectionNames]);\n const entryTypes = cols.map((c) => `${pascalCase(c)}Entry`);\n const union = entryTypes.length > 1 ? entryTypes.join(' | ') : entryTypes[0];\n const cardinality = field.reference?.cardinality ?? 'many';\n if (cardinality === 'one') {\n return entryTypes.length > 1 ? `(${union}) | null` : `${union} | null`;\n }\n return entryTypes.length > 1 ? `(${union})[]` : `${union}[]`;\n}\n\nfunction conditionalFieldType(\n field: Extract<CollectionField, { format: 'conditional' }>,\n collectionNames: readonly string[],\n): string {\n const branchTypes = field.conditional.branches.map((branch) => branchValueType(branch, collectionNames));\n return branchTypes.join(' | ');\n}\n\nfunction branchValueType(branch: ConditionalBranchConfig, collectionNames: readonly string[]): string {\n if ('collection' in branch && typeof branch.collection === 'string') {\n return `${pascalCase(branch.collection)}Entry`;\n }\n if (branch.fields) {\n const entries = Object.entries(branch.fields);\n if (entries.length === 0) return '{}';\n const props = entries.map(([name, f]) => ` ${name}: ${fieldToTSType(f, collectionNames)};`);\n return `{\\n${props.join('\\n')}\\n}`;\n }\n return 'unknown';\n}\n\n// ---------------------------------------------------------------------------\n// Generators\n// ---------------------------------------------------------------------------\n\nexport function generateTypes(cfg: Config, collectionNames: readonly string[]): string {\n const lines: string[] = [\n CODEGEN_BANNER + \"import type { EntryStatus, ResolvedImageField } from 'octocms/types';\",\n '',\n ];\n\n // Emit Fields interfaces\n for (const key of collectionNames) {\n const col = cfg.collections[key as keyof typeof cfg.collections];\n const pascal = pascalCase(key);\n lines.push(`export interface ${pascal}Fields {`);\n for (const [fieldName, field] of Object.entries(col.fields)) {\n const tsType = fieldToTSType(field, collectionNames);\n lines.push(` ${fieldName}: ${tsType};`);\n }\n lines.push('}');\n lines.push('');\n }\n\n // Emit Entry interfaces\n for (const key of collectionNames) {\n const pascal = pascalCase(key);\n lines.push(`export interface ${pascal}Entry {`);\n lines.push(` sys: { id: string; type: '${key}'; status: EntryStatus };`);\n lines.push(` fields: ${pascal}Fields;`);\n lines.push('}');\n lines.push('');\n }\n\n // AnyEntry union\n const entryNames = collectionNames.map((k) => `${pascalCase(k)}Entry`);\n lines.push(`export type AnyEntry = ${entryNames.join(' | ')};`);\n lines.push('');\n\n // EntryMap\n lines.push('export type EntryMap = {');\n for (const key of collectionNames) {\n lines.push(` ${key}: ${pascalCase(key)}Entry;`);\n }\n lines.push('};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nexport function generateEnums(cfg: Config, collectionNames: readonly string[], fieldTypes: readonly string[]): string {\n const lines: string[] = [];\n\n // CollectionName const object\n lines.push('export const CollectionName = {');\n for (const key of collectionNames) {\n lines.push(` ${pascalCase(key)}: '${key}',`);\n }\n lines.push('} as const;');\n lines.push('export type CollectionName = (typeof CollectionName)[keyof typeof CollectionName];');\n lines.push('');\n\n // COLLECTION_NAMES array\n lines.push(`export const COLLECTION_NAMES = [${collectionNames.map((k) => `'${k}'`).join(', ')}] as const;`);\n lines.push('');\n\n // Select option enums per collection\n for (const key of collectionNames) {\n const col = cfg.collections[key as keyof typeof cfg.collections];\n for (const [fieldName, field] of Object.entries(col.fields)) {\n if (field.format !== 'select') continue;\n const enumName = `${pascalCase(key)}${pascalCase(fieldName)}Option`;\n lines.push(`export const ${enumName} = {`);\n for (const opt of field.options) {\n lines.push(` ${pascalCase(opt.value)}: '${opt.value}',`);\n }\n lines.push('} as const;');\n lines.push(`export type ${enumName} = (typeof ${enumName})[keyof typeof ${enumName}];`);\n lines.push('');\n }\n }\n\n // FieldFormat const object\n lines.push('export const FieldFormat = {');\n for (const ft of fieldTypes) {\n lines.push(` ${pascalCase(ft)}: '${ft}',`);\n }\n lines.push('} as const;');\n lines.push('export type FieldFormat = (typeof FieldFormat)[keyof typeof FieldFormat];');\n lines.push('');\n\n return CODEGEN_BANNER + lines.join('\\n');\n}\n\nexport function generateContentDecls(cfg: Config, collectionNames: readonly string[]): string {\n const lines: string[] = [\n CODEGEN_BANNER +\n \"import type { EntryStatus } from 'octocms/types';\\n\\n\" +\n '// Raw on-disk types (before query() processing).\\n' +\n '// Image fields are UUID strings, reference fields are key strings,\\n' +\n '// markdown fields are omitted (stored in companion .md files),\\n' +\n '// richtext fields are omitted (stored in companion .mdx files).\\n',\n ];\n\n for (const key of collectionNames) {\n const col = cfg.collections[key as keyof typeof cfg.collections];\n const pascal = pascalCase(key);\n\n lines.push(`export interface Raw${pascal}Fields {`);\n for (const [fieldName, field] of Object.entries(col.fields)) {\n if (field.format === 'markdown' || field.format === 'richtext') continue; // companion files, not in JSON\n const tsType = fieldToTSType(field, collectionNames, 'raw');\n lines.push(` ${fieldName}: ${tsType};`);\n }\n lines.push('}');\n lines.push('');\n\n lines.push(`export interface Raw${pascal}Entry {`);\n lines.push(` sys: { id: string; type: '${key}'; status: EntryStatus };`);\n lines.push(` fields: Raw${pascal}Fields;`);\n lines.push('}');\n lines.push('');\n }\n\n // RawEntryMap\n lines.push('export type RawEntryMap = {');\n for (const key of collectionNames) {\n lines.push(` ${key}: Raw${pascalCase(key)}Entry;`);\n }\n lines.push('};');\n lines.push('');\n\n return lines.join('\\n');\n}\n\nexport function generateIndex(): string {\n return CODEGEN_BANNER + \"export * from './types';\\nexport * from './enums';\\nexport * from './query';\\n\";\n}\n\n/**\n * Generate the app-specific `query.ts` file that binds `createQuery` from `octocms/query`\n * to the app's config and generated `EntryMap` type.\n *\n * This file is emitted to `cms/__generated__/query.ts` by `npm run types:gen`.\n * Consumers import the typed `query` function from `cms/__generated__/query`.\n */\nexport function generateQuery(): string {\n return (\n CODEGEN_BANNER +\n `import { createQuery } from 'octocms/query';\nimport { configOctoCMS, type OctoConfig } from '../octocms.config';\nimport type { EntryMap } from './types';\n\n// configOctoCMS is widened to Config for admin internals; cast back to OctoConfig so\n// createQuery preserves literal collection/field names for type-safe queries.\nexport const query = createQuery<EntryMap, OctoConfig>(configOctoCMS as unknown as OctoConfig);\n`\n );\n}\n\n/**\n * Generate `configInit.ts` — a side-effect module that imports the app config\n * and registers it with the `octocms` config store.\n *\n * Importing this file ensures the singleton is populated even in serverless\n * cold starts where `cms/octocms.config.ts` (and therefore `withOctoCMS`) has not run.\n *\n * Emitted to `cms/__generated__/configInit.ts` by `npm run types:gen`.\n */\nexport function generateConfigInit(): string {\n return (\n CODEGEN_BANNER +\n `import { configOctoCMS } from '../octocms.config';\nimport { setConfig } from 'octocms/lib/configStore';\n\nsetConfig(configOctoCMS);\n`\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,57 @@
1
+ import {
2
+ adminLayoutTemplate,
3
+ adminPageTemplate
4
+ } from "./chunk-I7KNSICQ.js";
5
+ import {
6
+ log
7
+ } from "./chunk-6PHFHGTZ.js";
8
+ import "./chunk-W6QJTGBC.js";
9
+
10
+ // cli/commands/update.ts
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
12
+ import { join } from "path";
13
+ async function updateCommand(projectRoot) {
14
+ log.header("Update admin routes");
15
+ const files = [
16
+ {
17
+ path: join(projectRoot, "src", "app", "cms", "layout.tsx"),
18
+ displayPath: "src/app/cms/layout.tsx",
19
+ expected: adminLayoutTemplate
20
+ },
21
+ {
22
+ path: join(projectRoot, "src", "app", "cms", "[[...path]]", "page.tsx"),
23
+ displayPath: "src/app/cms/[[...path]]/page.tsx",
24
+ expected: adminPageTemplate
25
+ }
26
+ ];
27
+ log.info("Checking admin route files...");
28
+ let allUpToDate = true;
29
+ for (const file of files) {
30
+ if (!existsSync(file.path)) {
31
+ mkdirSync(join(file.path, ".."), { recursive: true });
32
+ writeFileSync(file.path, file.expected, "utf8");
33
+ log.success(`${file.displayPath} \u2014 created`);
34
+ allUpToDate = false;
35
+ } else {
36
+ const current = readFileSync(file.path, "utf8");
37
+ if (current === file.expected) {
38
+ log.success(`${file.displayPath} \u2014 up to date`);
39
+ } else {
40
+ writeFileSync(file.path, file.expected, "utf8");
41
+ log.step(`${file.displayPath} \u2014 updated`);
42
+ allUpToDate = false;
43
+ }
44
+ }
45
+ }
46
+ log.blank();
47
+ if (allUpToDate) {
48
+ log.info("Admin routes are current.");
49
+ } else {
50
+ log.info("Admin routes have been updated.");
51
+ }
52
+ log.blank();
53
+ }
54
+ export {
55
+ updateCommand
56
+ };
57
+ //# sourceMappingURL=update-RMGZMS56.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../cli/commands/update.ts"],"sourcesContent":["/**\n * `octocms update` — Regenerate admin route files in `src/app/cms/`.\n *\n * Ensures the admin layout and catch-all page re-export files are up-to-date\n * with the latest OctoCMS version. Useful after upgrading `octocms`.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\n\nimport { log } from '../lib/logger';\nimport { adminLayoutTemplate, adminPageTemplate } from '../lib/templates';\n\ntype FileCheck = {\n path: string;\n displayPath: string;\n expected: string;\n};\n\nexport async function updateCommand(projectRoot: string): Promise<void> {\n log.header('Update admin routes');\n\n const files: FileCheck[] = [\n {\n path: join(projectRoot, 'src', 'app', 'cms', 'layout.tsx'),\n displayPath: 'src/app/cms/layout.tsx',\n expected: adminLayoutTemplate,\n },\n {\n path: join(projectRoot, 'src', 'app', 'cms', '[[...path]]', 'page.tsx'),\n displayPath: 'src/app/cms/[[...path]]/page.tsx',\n expected: adminPageTemplate,\n },\n ];\n\n log.info('Checking admin route files...');\n\n let allUpToDate = true;\n\n for (const file of files) {\n if (!existsSync(file.path)) {\n mkdirSync(join(file.path, '..'), { recursive: true });\n writeFileSync(file.path, file.expected, 'utf8');\n log.success(`${file.displayPath} — created`);\n allUpToDate = false;\n } else {\n const current = readFileSync(file.path, 'utf8');\n if (current === file.expected) {\n log.success(`${file.displayPath} — up to date`);\n } else {\n writeFileSync(file.path, file.expected, 'utf8');\n log.step(`${file.displayPath} — updated`);\n allUpToDate = false;\n }\n }\n }\n\n log.blank();\n if (allUpToDate) {\n log.info('Admin routes are current.');\n } else {\n log.info('Admin routes have been updated.');\n }\n log.blank();\n}\n"],"mappings":";;;;;;;;;;AAOA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,YAAY;AAWrB,eAAsB,cAAc,aAAoC;AACtE,MAAI,OAAO,qBAAqB;AAEhC,QAAM,QAAqB;AAAA,IACzB;AAAA,MACE,MAAM,KAAK,aAAa,OAAO,OAAO,OAAO,YAAY;AAAA,MACzD,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,MACE,MAAM,KAAK,aAAa,OAAO,OAAO,OAAO,eAAe,UAAU;AAAA,MACtE,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,EACF;AAEA,MAAI,KAAK,+BAA+B;AAExC,MAAI,cAAc;AAElB,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,WAAW,KAAK,IAAI,GAAG;AAC1B,gBAAU,KAAK,KAAK,MAAM,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACpD,oBAAc,KAAK,MAAM,KAAK,UAAU,MAAM;AAC9C,UAAI,QAAQ,GAAG,KAAK,WAAW,iBAAY;AAC3C,oBAAc;AAAA,IAChB,OAAO;AACL,YAAM,UAAU,aAAa,KAAK,MAAM,MAAM;AAC9C,UAAI,YAAY,KAAK,UAAU;AAC7B,YAAI,QAAQ,GAAG,KAAK,WAAW,oBAAe;AAAA,MAChD,OAAO;AACL,sBAAc,KAAK,MAAM,KAAK,UAAU,MAAM;AAC9C,YAAI,KAAK,GAAG,KAAK,WAAW,iBAAY;AACxC,sBAAc;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,MAAM;AACV,MAAI,aAAa;AACf,QAAI,KAAK,2BAA2B;AAAA,EACtC,OAAO;AACL,QAAI,KAAK,iCAAiC;AAAA,EAC5C;AACA,MAAI,MAAM;AACZ;","names":[]}
@@ -0,0 +1,297 @@
1
+ import {
2
+ validateConfig
3
+ } from "./chunk-Q73JSGXV.js";
4
+ import {
5
+ loadCollections,
6
+ loadProjectConfig
7
+ } from "./chunk-C62C776U.js";
8
+ import {
9
+ fmt,
10
+ log
11
+ } from "./chunk-6PHFHGTZ.js";
12
+ import {
13
+ __spreadProps,
14
+ __spreadValues
15
+ } from "./chunk-W6QJTGBC.js";
16
+
17
+ // cli/lib/contentValidator.ts
18
+ import { existsSync, readdirSync, readFileSync } from "fs";
19
+ import { join } from "path";
20
+ function validateContent(projectRoot, config) {
21
+ const errors = [];
22
+ const counts = {};
23
+ const contentDir = join(projectRoot, config.contentFolder);
24
+ for (const [collectionName, collection] of Object.entries(config.collections)) {
25
+ const collDir = join(contentDir, collectionName);
26
+ if (!existsSync(collDir)) {
27
+ counts[collectionName] = 0;
28
+ continue;
29
+ }
30
+ const jsonFiles = readdirSync(collDir).filter((f) => f.endsWith(".json"));
31
+ counts[collectionName] = jsonFiles.length;
32
+ for (const file of jsonFiles) {
33
+ const filePath = join(collDir, file);
34
+ const fileErrors = validateEntry(filePath, file, collectionName, collection, contentDir, config);
35
+ errors.push(...fileErrors);
36
+ }
37
+ }
38
+ return { errors, counts };
39
+ }
40
+ function validateEntry(filePath, fileName, collectionName, collection, contentDir, config) {
41
+ const errors = [];
42
+ const ctx = { file: fileName, collection: collectionName };
43
+ let raw;
44
+ try {
45
+ raw = readFileSync(filePath, "utf8");
46
+ } catch (e) {
47
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: "Could not read file" }));
48
+ return errors;
49
+ }
50
+ let entry;
51
+ try {
52
+ entry = JSON.parse(raw);
53
+ } catch (e) {
54
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: "Invalid JSON" }));
55
+ return errors;
56
+ }
57
+ const sys = entry.sys;
58
+ if (!sys || typeof sys !== "object") {
59
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: "Missing sys object" }));
60
+ return errors;
61
+ }
62
+ if (!sys.id) {
63
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: "Missing sys.id" }));
64
+ }
65
+ if (sys.type !== collectionName) {
66
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: `sys.type is "${String(sys.type)}", expected "${collectionName}"` }));
67
+ }
68
+ const fields = entry.fields;
69
+ if (!fields || typeof fields !== "object") {
70
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: "Missing fields object" }));
71
+ return errors;
72
+ }
73
+ for (const [fieldName, fieldDef] of Object.entries(collection.fields)) {
74
+ const value = fields[fieldName];
75
+ const fieldErrors = validateFieldValue(fieldName, fieldDef, value, contentDir, config, ctx);
76
+ errors.push(...fieldErrors);
77
+ }
78
+ for (const [fieldName, fieldDef] of Object.entries(collection.fields)) {
79
+ if (fieldDef.format === "markdown") {
80
+ const companionPath = filePath.replace(/\.json$/, `.${fieldName}.md`);
81
+ if (!existsSync(companionPath) && fieldDef.required) {
82
+ errors.push(__spreadProps(__spreadValues({}, ctx), { field: fieldName, message: `Missing required companion file (.${fieldName}.md)` }));
83
+ }
84
+ }
85
+ if (fieldDef.format === "richtext") {
86
+ const companionPath = filePath.replace(/\.json$/, `.${fieldName}.mdx`);
87
+ if (!existsSync(companionPath) && fieldDef.required) {
88
+ errors.push(__spreadProps(__spreadValues({}, ctx), { field: fieldName, message: `Missing required companion file (.${fieldName}.mdx)` }));
89
+ }
90
+ }
91
+ }
92
+ return errors;
93
+ }
94
+ function validateFieldValue(fieldName, fieldDef, value, contentDir, config, ctx) {
95
+ var _a, _b, _c, _d;
96
+ const errors = [];
97
+ const fctx = __spreadProps(__spreadValues({}, ctx), { field: fieldName });
98
+ if (fieldDef.format === "markdown" || fieldDef.format === "richtext") {
99
+ return errors;
100
+ }
101
+ if (fieldDef.required && (value === void 0 || value === null || value === "")) {
102
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: `Required field "${fieldDef.label}" is empty` }));
103
+ return errors;
104
+ }
105
+ if (value === void 0 || value === null) return errors;
106
+ switch (fieldDef.format) {
107
+ case "string": {
108
+ if (fieldDef.list) {
109
+ if (!Array.isArray(value)) {
110
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Expected string array" }));
111
+ } else if (!value.every((v) => typeof v === "string")) {
112
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "All list items must be strings" }));
113
+ }
114
+ } else if (typeof value !== "string") {
115
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Expected string" }));
116
+ }
117
+ break;
118
+ }
119
+ case "text":
120
+ case "slug":
121
+ case "url":
122
+ case "color":
123
+ case "image": {
124
+ if (typeof value !== "string") {
125
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: `Expected string for ${fieldDef.format} field` }));
126
+ }
127
+ break;
128
+ }
129
+ case "boolean": {
130
+ if (value !== "true" && value !== "false") {
131
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: 'Expected "true" or "false"' }));
132
+ }
133
+ break;
134
+ }
135
+ case "number": {
136
+ if (typeof value !== "number" && value !== null) {
137
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Expected number or null" }));
138
+ }
139
+ if (typeof value === "number") {
140
+ if (fieldDef.min != null && value < fieldDef.min) {
141
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: `Value ${value} is below min ${fieldDef.min}` }));
142
+ }
143
+ if (fieldDef.max != null && value > fieldDef.max) {
144
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: `Value ${value} is above max ${fieldDef.max}` }));
145
+ }
146
+ }
147
+ break;
148
+ }
149
+ case "datetime": {
150
+ if (typeof value === "string") {
151
+ if (isNaN(Date.parse(value))) {
152
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Invalid datetime format" }));
153
+ }
154
+ } else if (value !== null) {
155
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Expected ISO date string or null" }));
156
+ }
157
+ break;
158
+ }
159
+ case "select": {
160
+ const validValues = new Set(fieldDef.options.map((o) => o.value));
161
+ if (fieldDef.multiple) {
162
+ if (!Array.isArray(value)) {
163
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Expected array for multi-select" }));
164
+ } else {
165
+ for (const v of value) {
166
+ if (!validValues.has(String(v))) {
167
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: `Invalid select value "${String(v)}"` }));
168
+ }
169
+ }
170
+ }
171
+ } else {
172
+ if (typeof value !== "string" || !validValues.has(value)) {
173
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: `Invalid select value "${String(value)}"` }));
174
+ }
175
+ }
176
+ break;
177
+ }
178
+ case "reference": {
179
+ const cardinality = (_b = (_a = fieldDef.reference) == null ? void 0 : _a.cardinality) != null ? _b : "many";
180
+ if (cardinality === "one") {
181
+ if (typeof value === "string" && value) {
182
+ validateReferenceTarget(value, (_c = fieldDef.reference) == null ? void 0 : _c.collections, contentDir, config, fctx, errors);
183
+ }
184
+ } else {
185
+ const refs = parseRefs(value);
186
+ for (const ref of refs) {
187
+ validateReferenceTarget(ref, (_d = fieldDef.reference) == null ? void 0 : _d.collections, contentDir, config, fctx, errors);
188
+ }
189
+ }
190
+ break;
191
+ }
192
+ case "json": {
193
+ break;
194
+ }
195
+ case "conditional": {
196
+ if (typeof value !== "object" || value === null) {
197
+ errors.push(__spreadProps(__spreadValues({}, fctx), { message: "Expected object for conditional field" }));
198
+ }
199
+ break;
200
+ }
201
+ }
202
+ return errors;
203
+ }
204
+ function parseRefs(value) {
205
+ if (typeof value === "string") {
206
+ try {
207
+ const arr = JSON.parse(value);
208
+ return Array.isArray(arr) ? arr.map(String) : [];
209
+ } catch (e) {
210
+ return [];
211
+ }
212
+ }
213
+ if (Array.isArray(value)) {
214
+ return value.map(String);
215
+ }
216
+ return [];
217
+ }
218
+ function validateReferenceTarget(refKey, allowedCollections, contentDir, config, ctx, errors) {
219
+ const match = refKey.match(/^(\w+)-(.+)\.json$/);
220
+ if (!match) {
221
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: `Invalid reference key format: "${refKey}"` }));
222
+ return;
223
+ }
224
+ const [, refCollection] = match;
225
+ if (allowedCollections && !allowedCollections.includes(refCollection)) {
226
+ errors.push(__spreadProps(__spreadValues({}, ctx), {
227
+ message: `Reference "${refKey}" targets collection "${refCollection}" which is not allowed`
228
+ }));
229
+ return;
230
+ }
231
+ if (!config.collections[refCollection]) {
232
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: `Reference "${refKey}" targets unknown collection "${refCollection}"` }));
233
+ return;
234
+ }
235
+ const targetPath = join(contentDir, refCollection, refKey);
236
+ if (!existsSync(targetPath)) {
237
+ errors.push(__spreadProps(__spreadValues({}, ctx), { message: `Reference target "${refKey}" does not exist` }));
238
+ }
239
+ }
240
+
241
+ // cli/commands/validate.ts
242
+ async function validateCommand(projectRoot) {
243
+ var _a;
244
+ log.header("Validate content");
245
+ const config = await loadProjectConfig(projectRoot);
246
+ const collections = await loadCollections(projectRoot);
247
+ try {
248
+ validateConfig(config, collections);
249
+ } catch (e) {
250
+ log.error(`Config error: ${e.message}`);
251
+ process.exitCode = 1;
252
+ return;
253
+ }
254
+ const collectionNames = Object.keys(config.collections);
255
+ log.info(`Validating ${collectionNames.length} collections...`);
256
+ log.blank();
257
+ const result = validateContent(projectRoot, config);
258
+ const errorsByFile = /* @__PURE__ */ new Map();
259
+ for (const err of result.errors) {
260
+ const key = `${err.collection}/${err.file}`;
261
+ if (!errorsByFile.has(key)) errorsByFile.set(key, []);
262
+ errorsByFile.get(key).push(err);
263
+ }
264
+ let totalEntries = 0;
265
+ for (const name of collectionNames) {
266
+ const count = (_a = result.counts[name]) != null ? _a : 0;
267
+ totalEntries += count;
268
+ const collErrors = result.errors.filter((e) => e.collection === name);
269
+ if (collErrors.length === 0) {
270
+ log.success(`${name} \u2014 ${count} ${count === 1 ? "entry" : "entries"}`);
271
+ } else {
272
+ const errorFiles = new Set(collErrors.map((e) => e.file));
273
+ log.error(`${name} \u2014 ${count} ${count === 1 ? "entry" : "entries"}, ${errorFiles.size} with errors`);
274
+ }
275
+ }
276
+ if (errorsByFile.size > 0) {
277
+ log.blank();
278
+ for (const [fileKey, fileErrors] of errorsByFile) {
279
+ log.error(fileKey);
280
+ for (const err of fileErrors) {
281
+ const field = err.field ? `${fmt.dim(err.field)}: ` : "";
282
+ log.info(` ${fmt.yellow("\u2022")} ${field}${err.message}`);
283
+ }
284
+ }
285
+ log.blank();
286
+ log.error(`${result.errors.length} ${result.errors.length === 1 ? "error" : "errors"} found.`);
287
+ process.exitCode = 1;
288
+ } else {
289
+ log.blank();
290
+ log.success(`All ${totalEntries} ${totalEntries === 1 ? "entry" : "entries"} valid.`);
291
+ }
292
+ log.blank();
293
+ }
294
+ export {
295
+ validateCommand
296
+ };
297
+ //# sourceMappingURL=validate-OTJ6ULMP.js.map