nextjs-studio 0.3.0 → 0.4.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/bin/nextjs-studio.js +44 -35
- package/dist/bin/nextjs-studio.js.map +1 -1
- package/dist/core/index.d.ts +40 -5
- package/dist/core/index.js +9 -3
- package/dist/core/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -443,11 +443,12 @@ function generateCollectionTypes(schemas) {
|
|
|
443
443
|
"export type Slug = Brand<string, 'Slug'>;"
|
|
444
444
|
].join("\n");
|
|
445
445
|
const interfaces = schemas.map(generateInterfaceForSchema).join("\n\n");
|
|
446
|
-
const collectionMap = schemas.map((schema) => ` ${JSON.stringify(schema.collection)}: ${toPascalCase(schema.collection)}Entry;`).join("\n");
|
|
447
446
|
const collectionRegistry = [
|
|
448
|
-
"
|
|
449
|
-
"
|
|
450
|
-
|
|
447
|
+
"// Augment the nextjs-studio module so queryCollection() is fully typed.",
|
|
448
|
+
"declare module 'nextjs-studio' {",
|
|
449
|
+
" interface CollectionTypeMap {",
|
|
450
|
+
schemas.map((schema) => ` ${JSON.stringify(schema.collection)}: ${toPascalCase(schema.collection)}Entry;`).join("\n"),
|
|
451
|
+
" }",
|
|
451
452
|
"}"
|
|
452
453
|
].join("\n");
|
|
453
454
|
return [banner, interfaces, collectionRegistry].join("\n\n") + "\n";
|
|
@@ -456,7 +457,7 @@ function generateCollectionTypes(schemas) {
|
|
|
456
457
|
// package.json
|
|
457
458
|
var package_default = {
|
|
458
459
|
name: "nextjs-studio",
|
|
459
|
-
version: "0.
|
|
460
|
+
version: "0.4.0",
|
|
460
461
|
description: "A Git-based, local-first CMS for Next.js projects",
|
|
461
462
|
keywords: [
|
|
462
463
|
"nextjs",
|
|
@@ -470,7 +471,7 @@ var package_default = {
|
|
|
470
471
|
homepage: "https://github.com/TiagoDanin/Nextjs-Studio",
|
|
471
472
|
repository: {
|
|
472
473
|
type: "git",
|
|
473
|
-
url: "https://github.com/TiagoDanin/Nextjs-Studio.git"
|
|
474
|
+
url: "git+https://github.com/TiagoDanin/Nextjs-Studio.git"
|
|
474
475
|
},
|
|
475
476
|
license: "MIT",
|
|
476
477
|
author: "Tiago Danin",
|
|
@@ -483,7 +484,9 @@ var package_default = {
|
|
|
483
484
|
},
|
|
484
485
|
main: "./dist/core/index.js",
|
|
485
486
|
types: "./dist/core/index.d.ts",
|
|
486
|
-
bin:
|
|
487
|
+
bin: {
|
|
488
|
+
"nextjs-studio": "dist/bin/nextjs-studio.js"
|
|
489
|
+
},
|
|
487
490
|
files: [
|
|
488
491
|
"dist",
|
|
489
492
|
"README.md",
|
|
@@ -559,52 +562,58 @@ var program = new Command().name("Nextjs Studio").description("Local-first CMS f
|
|
|
559
562
|
var opts = program.opts();
|
|
560
563
|
var contentsDir = path2.resolve(opts.dir);
|
|
561
564
|
var port = Number(opts.port);
|
|
562
|
-
|
|
565
|
+
async function runGenerateTypes(sourceDir) {
|
|
563
566
|
const outDir = path2.resolve(".studio");
|
|
564
567
|
const outFile = path2.join(outDir, "studio.d.ts");
|
|
565
|
-
console.log(`Generating types from ${
|
|
566
|
-
const fsAdapter = new FsAdapter(
|
|
568
|
+
console.log(`Generating types from ${sourceDir}...`);
|
|
569
|
+
const fsAdapter = new FsAdapter(sourceDir);
|
|
567
570
|
const index = await loadContent(fsAdapter);
|
|
568
|
-
const
|
|
569
|
-
const schemas = collections.flatMap((c) => c.schema ? [c.schema] : []);
|
|
571
|
+
const schemas = index.getCollections().flatMap((c) => c.schema ? [c.schema] : []);
|
|
570
572
|
const code = generateCollectionTypes(schemas);
|
|
571
573
|
await fs2.mkdir(outDir, { recursive: true });
|
|
572
574
|
await fs2.writeFile(outFile, code, "utf-8");
|
|
573
575
|
console.log(`Types written to ${outFile}`);
|
|
574
|
-
process.exit(0);
|
|
575
576
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
var serverEnv = {
|
|
579
|
-
...process.env,
|
|
580
|
-
STUDIO_CONTENTS_DIR: contentsDir,
|
|
581
|
-
PORT: String(port),
|
|
582
|
-
HOSTNAME: "0.0.0.0"
|
|
583
|
-
};
|
|
584
|
-
console.log(`Nextjs Studio v${version}`);
|
|
585
|
-
console.log(`Contents: ${contentsDir}`);
|
|
586
|
-
console.log(`Starting on http://localhost:${port}`);
|
|
587
|
-
function createServerProcess() {
|
|
577
|
+
function resolveServerProcess(uiDir2, serverPort, env) {
|
|
578
|
+
const standaloneServer = path2.resolve(uiDir2, ".next/standalone/src/cli/ui/server.js");
|
|
588
579
|
if (existsSync(standaloneServer)) {
|
|
589
|
-
return spawn("node", [standaloneServer], { stdio: "inherit", env
|
|
580
|
+
return spawn("node", [standaloneServer], { stdio: "inherit", env });
|
|
581
|
+
}
|
|
582
|
+
const uiPackageJson = path2.resolve(uiDir2, "package.json");
|
|
583
|
+
if (existsSync(uiPackageJson)) {
|
|
584
|
+
const nextBin = path2.resolve(uiDir2, "../../../node_modules/next/dist/bin/next");
|
|
585
|
+
return spawn("node", [nextBin, "dev", "--port", String(serverPort), "--webpack"], {
|
|
586
|
+
cwd: uiDir2,
|
|
587
|
+
stdio: "inherit",
|
|
588
|
+
env
|
|
589
|
+
});
|
|
590
590
|
}
|
|
591
|
-
|
|
592
|
-
return spawn(npxBin, ["next", "dev", "--port", String(port), "--webpack"], {
|
|
593
|
-
cwd: uiDir,
|
|
594
|
-
stdio: "inherit",
|
|
595
|
-
env: serverEnv
|
|
596
|
-
});
|
|
591
|
+
return null;
|
|
597
592
|
}
|
|
598
|
-
function
|
|
593
|
+
function forwardSignals(child) {
|
|
599
594
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
600
595
|
process.on(signal, () => child.kill(signal));
|
|
601
596
|
}
|
|
602
597
|
}
|
|
603
|
-
|
|
598
|
+
if (opts.generateTypes) {
|
|
599
|
+
await runGenerateTypes(contentsDir);
|
|
600
|
+
process.exit(0);
|
|
601
|
+
}
|
|
602
|
+
var uiDir = path2.resolve(import.meta.dirname, "../cli/ui");
|
|
603
|
+
var serverEnv = { ...process.env, STUDIO_CONTENTS_DIR: contentsDir, PORT: String(port), HOSTNAME: "0.0.0.0" };
|
|
604
|
+
var serverProcess = resolveServerProcess(uiDir, port, serverEnv);
|
|
605
|
+
if (!serverProcess) {
|
|
606
|
+
console.error("Error: Studio UI server not found.");
|
|
607
|
+
console.error("The pre-built UI is not included in this installation.");
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
console.log(`Nextjs Studio v${version}`);
|
|
611
|
+
console.log(`Contents: ${contentsDir}`);
|
|
612
|
+
console.log(`Starting on http://localhost:${port}`);
|
|
604
613
|
serverProcess.on("error", (error) => {
|
|
605
614
|
console.error("Failed to start server:", error.message);
|
|
606
615
|
process.exit(1);
|
|
607
616
|
});
|
|
608
617
|
serverProcess.on("close", (code) => process.exit(code ?? 0));
|
|
609
|
-
|
|
618
|
+
forwardSignals(serverProcess);
|
|
610
619
|
//# sourceMappingURL=nextjs-studio.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/nextjs-studio.ts","../../src/shared/constants.ts","../../src/cli/adapters/fs-adapter.ts","../../src/core/indexer.ts","../../src/core/parsers/parser-mdx.ts","../../src/core/parsers/parser-json.ts","../../src/core/schema-inferrer.ts","../../src/core/content-store.ts","../../src/core/type-generator.ts","../../package.json"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * @context bin layer — CLI entry point at src/bin/nextjs-studio.ts\n * @does Parses CLI args, resolves paths, and spawns the UI server process\n * @depends src/shared/constants.ts\n * @do Add new CLI flags here; keep only process bootstrap logic\n * @dont Import UI components, access the filesystem beyond existsSync, or contain business logic\n */\n\nimport { existsSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { Command } from \"commander\";\nimport { CLI_PORT, CONTENTS_DIR } from \"../shared/constants.js\";\nimport { FsAdapter } from \"../cli/adapters/fs-adapter.js\";\nimport { loadContent } from \"../core/content-store.js\";\nimport { generateCollectionTypes } from \"../core/type-generator.js\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nconst { version } = pkg;\n\nconst program = new Command()\n .name(\"Nextjs Studio\")\n .description(\"Local-first CMS for Next.js projects\")\n .version(version)\n .option(\"-d, --dir <path>\", \"Path to contents directory\", CONTENTS_DIR)\n .option(\"-p, --port <number>\", \"Port to run the studio on\", String(CLI_PORT))\n .option(\"--generate-types\", \"Generate TypeScript types for content collections\")\n .parse();\n\nconst opts = program.opts<{ dir: string; port: string; generateTypes?: boolean }>();\nconst contentsDir = path.resolve(opts.dir);\nconst port = Number(opts.port);\n\nif (opts.generateTypes) {\n const outDir = path.resolve(\".studio\");\n const outFile = path.join(outDir, \"studio.d.ts\");\n\n console.log(`Generating types from ${contentsDir}...`);\n\n const fsAdapter = new FsAdapter(contentsDir);\n const index = await loadContent(fsAdapter);\n const collections = index.getCollections();\n const schemas = collections.flatMap((c) => (c.schema ? [c.schema] : []));\n const code = generateCollectionTypes(schemas);\n\n await fs.mkdir(outDir, { recursive: true });\n await fs.writeFile(outFile, code, \"utf-8\");\n\n console.log(`Types written to ${outFile}`);\n process.exit(0);\n}\n\nconst uiDir = path.resolve(import.meta.dirname, \"../cli/ui\");\nconst standaloneServer = path.resolve(uiDir, \".next/standalone/src/cli/ui/server.js\");\n\nconst serverEnv = {\n ...process.env,\n STUDIO_CONTENTS_DIR: contentsDir,\n PORT: String(port),\n HOSTNAME: \"0.0.0.0\",\n};\n\nconsole.log(`Nextjs Studio v${version}`);\nconsole.log(`Contents: ${contentsDir}`);\nconsole.log(`Starting on http://localhost:${port}`);\n\nfunction createServerProcess(): ChildProcess {\n if (existsSync(standaloneServer)) {\n return spawn(\"node\", [standaloneServer], { stdio: \"inherit\", env: serverEnv });\n }\n\n const npxBin = process.platform === \"win32\" ? \"npx.cmd\" : \"npx\";\n return spawn(npxBin, [\"next\", \"dev\", \"--port\", String(port), \"--webpack\"], {\n cwd: uiDir,\n stdio: \"inherit\",\n env: serverEnv,\n });\n}\n\nfunction registerSignalForwarding(child: ChildProcess): void {\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => child.kill(signal));\n }\n}\n\nconst serverProcess = createServerProcess();\n\nserverProcess.on(\"error\", (error) => {\n console.error(\"Failed to start server:\", error.message);\n process.exit(1);\n});\n\nserverProcess.on(\"close\", (code) => process.exit(code ?? 0));\n\nregisterSignalForwarding(serverProcess);\n","/**\n * @context Shared layer — constants at src/shared/constants.ts\n * @does Defines project-wide constants shared across core, CLI, and UI layers\n * @depends none\n * @do Add new shared constants here\n * @dont Import from CLI or UI; constants must be framework-agnostic\n */\n\nexport const CONTENTS_DIR = \"contents\";\nexport const CLI_PORT = 3030;\nexport const CONFIG_FILE = \"studio.config.ts\";\nexport const SUPPORTED_EXTENSIONS = [\".mdx\", \".json\"] as const;\nexport const COLLECTION_ORDER_FILE = \"collection.json\";\nexport const WATCHER_DEBOUNCE_MS = 5_000;\nexport const MEDIA_DIR = \"media\";\n\nexport const IMAGE_MIME_TYPES = [\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n \"image/svg+xml\",\n \"image/avif\",\n] as const;\n\nexport const VIDEO_MIME_TYPES = [\"video/mp4\", \"video/webm\", \"video/ogg\"] as const;\n\nexport const AUDIO_MIME_TYPES = [\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/wav\",\n \"audio/webm\",\n \"audio/aac\",\n \"audio/flac\",\n] as const;\n\nexport const MEDIA_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES, ...AUDIO_MIME_TYPES] as const;\n\nexport const IMAGE_EXTENSIONS = [\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".avif\"] as const;\nexport const VIDEO_EXTENSIONS = [\".mp4\", \".webm\", \".ogv\"] as const;\nexport const AUDIO_EXTENSIONS = [\".mp3\", \".ogg\", \".wav\", \".m4a\", \".aac\", \".flac\"] as const;\n","/**\n * @context CLI layer — filesystem adapter at src/cli/adapters/fs-adapter.ts\n * @does Implements IFsAdapter; abstracts all file read/write/list operations behind a single interface\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts\n * @do Add new I/O operations here; all file access must go through this adapter\n * @dont Import UI components, run HTTP requests, or contain business logic\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Dirent } from \"node:fs\";\nimport type { FileInfo, DirectoryFileEntry } from \"../../shared/types.js\";\nimport type { IFsAdapter } from \"../../shared/fs-adapter.interface.js\";\nimport { SUPPORTED_EXTENSIONS } from \"../../shared/constants.js\";\n\nexport class FsAdapter implements IFsAdapter {\n private readonly basePath: string;\n\n constructor(basePath: string) {\n this.basePath = path.resolve(basePath);\n }\n\n private resolve(...segments: string[]): string {\n return path.resolve(this.basePath, ...segments);\n }\n\n async readFile(filePath: string): Promise<string> {\n return fs.readFile(this.resolve(filePath), \"utf-8\");\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, content, \"utf-8\");\n }\n\n async deleteFile(filePath: string): Promise<void> {\n await fs.unlink(this.resolve(filePath));\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n await fs.access(this.resolve(filePath));\n return true;\n } catch {\n return false;\n }\n }\n\n async getStats(filePath: string): Promise<FileInfo> {\n const fullPath = this.resolve(filePath);\n const stats = await fs.stat(fullPath);\n return { path: filePath, size: stats.size, modifiedAt: stats.mtime };\n }\n\n async listFiles(dirPath: string, extensions?: readonly string[]): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n const filterExts = extensions ?? SUPPORTED_EXTENSIONS;\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isFile() && filterExts.some((ext) => entry.name.endsWith(ext)))\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async listDirectories(dirPath: string): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async readBuffer(filePath: string): Promise<Buffer> {\n return fs.readFile(this.resolve(filePath));\n }\n\n async writeBuffer(filePath: string, data: Buffer): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, data);\n }\n\n async listAllFiles(dirPath: string): Promise<DirectoryFileEntry[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const results: DirectoryFileEntry[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const relativePath = this.join(dirPath, entry.name);\n const stats = await fs.stat(this.resolve(relativePath));\n results.push({ name: entry.name, relativePath, size: stats.size, modifiedAt: stats.mtime });\n }\n return results;\n }\n\n join(...segments: string[]): string {\n return path.join(...segments);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n extname(filePath: string): string {\n return path.extname(filePath);\n }\n\n relative(from: string, to: string): string {\n return path.relative(from, to);\n }\n\n normalizeSlug(relativePath: string, ext: string): string {\n return relativePath.replace(ext, \"\").split(path.sep).join(\"/\");\n }\n}\n","/**\n * @context Core layer — content indexer at src/core/indexer.ts\n * @does Scans the contents directory, parses MDX/JSON files, and builds an in-memory index\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts, src/core/parsers/, src/core/schema-inferrer.ts\n * @do Add new file type handling here; extend indexCollection for new collection behaviors\n * @dont Import from CLI or UI; instantiate FsAdapter; access the filesystem directly\n */\n\nimport slugify from \"@sindresorhus/slugify\";\nimport type { CollectionSchema } from \"../shared/fields.js\";\nimport type { ContentEntry, Collection, StudioConfig } from \"../shared/types.js\";\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport { COLLECTION_ORDER_FILE } from \"../shared/constants.js\";\nimport { parseMdx } from \"./parsers/parser-mdx.js\";\nimport { parseJson } from \"./parsers/parser-json.js\";\nimport { inferSchema } from \"./schema-inferrer.js\";\n\nexport class ContentIndex {\n private readonly entries = new Map<string, ContentEntry[]>();\n private readonly collections = new Map<string, Collection>();\n private readonly fs: IFsAdapter;\n\n constructor(fsAdapter: IFsAdapter) {\n this.fs = fsAdapter;\n }\n\n async build(config?: StudioConfig): Promise<void> {\n this.clear();\n const dirs = await this.fs.listDirectories(\".\");\n\n for (const dir of dirs) {\n const dirName = this.fs.basename(dir);\n const collectionName = slugify(dirName);\n const collectionConfig = config?.collections?.[collectionName];\n await this.indexCollection(dirName, collectionName, collectionConfig?.schema);\n }\n }\n\n getCollection(name: string): ContentEntry[] {\n return this.entries.get(name) ?? [];\n }\n\n getCollections(): Collection[] {\n return Array.from(this.collections.values());\n }\n\n clear(): void {\n this.entries.clear();\n this.collections.clear();\n }\n\n private async indexCollection(\n dirName: string,\n collectionName: string,\n manualSchema?: CollectionSchema,\n ): Promise<void> {\n const entries: ContentEntry[] = [];\n await this.scanDir(dirName, collectionName, dirName, entries);\n\n const orderPath = this.fs.join(dirName, COLLECTION_ORDER_FILE);\n const ordering = await this.readOrdering(orderPath);\n if (ordering) {\n this.applyOrdering(entries, ordering);\n }\n\n const schema = manualSchema ?? inferSchema(entries, collectionName);\n\n this.entries.set(collectionName, entries);\n this.collections.set(collectionName, {\n name: collectionName,\n type: this.detectCollectionType(entries),\n count: entries.length,\n basePath: dirName,\n schema,\n });\n }\n\n private async scanDir(\n dirName: string,\n collectionName: string,\n dirPath: string,\n entries: ContentEntry[],\n ): Promise<void> {\n const subDirs = await this.fs.listDirectories(dirPath);\n for (const subDir of subDirs) {\n await this.scanDir(dirName, collectionName, subDir, entries);\n }\n\n const files = await this.fs.listFiles(dirPath);\n for (const filePath of files) {\n const fileName = this.fs.basename(filePath);\n if (fileName === COLLECTION_ORDER_FILE) continue;\n\n const ext = this.fs.extname(fileName);\n const content = await this.fs.readFile(filePath);\n const relativePath = this.fs.relative(dirName, filePath);\n const slug = this.fs\n .normalizeSlug(relativePath, ext)\n .split(\"/\")\n .map((segment) => slugify(segment))\n .join(\"/\");\n\n if (ext === \".mdx\") {\n entries.push(this.buildMdxEntry(collectionName, slug, content));\n } else if (ext === \".json\") {\n entries.push(...this.buildJsonEntries(collectionName, slug, content));\n }\n }\n }\n\n private buildMdxEntry(collectionName: string, slug: string, content: string): ContentEntry {\n const parsed = parseMdx(content);\n return {\n collection: collectionName,\n slug,\n path: `/${collectionName}/${slug}`,\n body: parsed.body,\n data: parsed.data,\n };\n }\n\n private buildJsonEntries(collectionName: string, slug: string, content: string): ContentEntry[] {\n const parsed = parseJson(content);\n\n if (parsed.type === \"json-array\") {\n return parsed.entries.map((data, index) => {\n const entrySlug =\n typeof data[\"slug\"] === \"string\" ? slugify(data[\"slug\"]) : `${slug}/${index}`;\n return {\n collection: collectionName,\n slug: entrySlug,\n path: `/${collectionName}/${entrySlug}`,\n data,\n };\n });\n }\n\n return [{ collection: collectionName, slug, path: `/${collectionName}/${slug}`, data: parsed.data }];\n }\n\n private async readOrdering(orderPath: string): Promise<string[] | null> {\n if (!(await this.fs.exists(orderPath))) return null;\n\n try {\n const content = await this.fs.readFile(orderPath);\n const parsed: unknown = JSON.parse(content);\n if (Array.isArray(parsed)) return parsed as string[];\n } catch (error) {\n console.warn(`[Nextjs Studio] Failed to parse ordering file: ${orderPath}`, error);\n }\n return null;\n }\n\n private applyOrdering(entries: ContentEntry[], ordering: string[]): void {\n const orderMap = new Map(ordering.map((slug, index) => [slug, index]));\n entries.sort((a, b) => {\n const aIndex = orderMap.get(a.slug) ?? Infinity;\n const bIndex = orderMap.get(b.slug) ?? Infinity;\n return aIndex - bIndex;\n });\n }\n\n private detectCollectionType(entries: ContentEntry[]): Collection[\"type\"] {\n if (entries.length === 0) return \"mdx\";\n const first = entries[0];\n if (first.body !== undefined) return \"mdx\";\n if (entries.length === 1 && !first.slug.includes(\"/\")) return \"json-object\";\n return \"json-array\";\n }\n}\n","/**\n * @context Core layer — MDX parser/serializer at src/core/parsers/parser-mdx.ts\n * @does Parses .mdx content into frontmatter + body, and serializes them back to MDX strings\n * @depends none (gray-matter is an external dep)\n * @do Add MDX transform steps here; both parse and serialize live here intentionally\n * @dont Access the filesystem; import from CLI or UI; handle JSON content\n */\n\nimport matter from \"gray-matter\";\n\nexport interface ParsedMdx {\n data: Record<string, unknown>;\n body: string;\n}\n\nexport function parseMdx(content: string): ParsedMdx {\n const { data, content: body } = matter(content);\n return { data, body: body.trim() };\n}\n\nexport function serializeMdx(data: Record<string, unknown>, body: string): string {\n return matter.stringify(body, data);\n}\n","/**\n * @context Core layer — JSON parser at src/core/parsers/parser-json.ts\n * @does Parses JSON content strings into typed ParsedJson results (array or object)\n * @depends none\n * @do Extend ParsedJson variants here if new JSON structures are supported\n * @dont Access the filesystem; import from CLI or UI; contain serialization logic\n */\n\nexport interface ParsedJsonArray {\n type: \"json-array\";\n entries: Record<string, unknown>[];\n}\n\nexport interface ParsedJsonObject {\n type: \"json-object\";\n data: Record<string, unknown>;\n}\n\nexport type ParsedJson = ParsedJsonArray | ParsedJsonObject;\n\nexport function parseJson(content: string): ParsedJson {\n const parsed: unknown = JSON.parse(content);\n\n if (Array.isArray(parsed)) {\n return {\n type: \"json-array\",\n entries: parsed as Record<string, unknown>[],\n };\n }\n\n if (typeof parsed === \"object\" && parsed !== null) {\n return {\n type: \"json-object\",\n data: parsed as Record<string, unknown>,\n };\n }\n\n throw new Error(\"JSON content must be an array or object\");\n}\n","/**\n * @context Core layer — schema inferrer at src/core/schema-inferrer.ts\n * @does Infers a CollectionSchema from actual content entries when no manual schema is defined\n * @depends src/shared/types.ts, src/shared/fields.ts\n * @do Add new type detection heuristics here (e.g. color, phone)\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport type { ContentEntry } from \"../shared/types.js\";\nimport type { CollectionSchema, FieldDefinition, SelectOption } from \"../shared/fields.js\";\n\n// Value detector patterns\nconst RE_ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst RE_ISO_DATETIME =\n /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?$/;\nconst RE_EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst RE_URL = /^https?:\\/\\/.+/;\nconst LONG_TEXT_THRESHOLD = 200;\n\nfunction isISODate(value: string): boolean {\n return RE_ISO_DATE.test(value);\n}\n\nfunction isISODateTime(value: string): boolean {\n return RE_ISO_DATETIME.test(value);\n}\n\nfunction isEmail(value: string): boolean {\n return RE_EMAIL.test(value);\n}\n\nfunction isUrl(value: string): boolean {\n return RE_URL.test(value);\n}\n\nfunction inferStringField(name: string, strings: string[]): FieldDefinition {\n if (strings.every(isEmail)) return { name, type: \"email\" };\n if (strings.every(isUrl)) return { name, type: \"url\" };\n if (strings.every(isISODateTime)) return { name, type: \"date\", includeTime: true };\n if (strings.every(isISODate)) return { name, type: \"date\" };\n\n const isLong = strings.some((s) => s.length > LONG_TEXT_THRESHOLD || s.includes(\"\\n\"));\n return { name, type: isLong ? \"long-text\" : \"text\" };\n}\n\nfunction inferArrayField(name: string, items: unknown[]): FieldDefinition {\n if (items.length === 0) return { name, type: \"array\", itemFields: [] };\n\n if (items.every((item) => typeof item === \"string\")) {\n const unique = [...new Set(items as string[])].slice(0, 50);\n const options: SelectOption[] = unique.map((v) => ({ label: v, value: v }));\n return { name, type: \"multi-select\", options };\n }\n\n if (items.every((item) => typeof item === \"object\" && item !== null && !Array.isArray(item))) {\n return { name, type: \"array\", itemFields: inferFields(items as Record<string, unknown>[]) };\n }\n\n return { name, type: \"array\", itemFields: [] };\n}\n\nfunction inferFieldDefinition(name: string, values: unknown[]): FieldDefinition {\n const present = values.filter((v) => v !== null && v !== undefined);\n\n if (present.length === 0) return { name, type: \"text\" };\n if (present.every((v) => typeof v === \"boolean\")) return { name, type: \"boolean\" };\n\n if (present.every((v) => typeof v === \"number\")) {\n const format = present.every((v) => Number.isInteger(v)) ? \"integer\" : \"decimal\";\n return { name, type: \"number\", format };\n }\n\n if (present.every((v) => typeof v === \"string\")) {\n return inferStringField(name, present as string[]);\n }\n\n if (present.every((v) => Array.isArray(v))) {\n return inferArrayField(name, (present as unknown[][]).flat());\n }\n\n if (present.every((v) => typeof v === \"object\" && v !== null && !Array.isArray(v))) {\n return { name, type: \"object\", fields: inferFields(present as Record<string, unknown>[]) };\n }\n\n return { name, type: \"text\" };\n}\n\nfunction inferFields(rows: Record<string, unknown>[]): FieldDefinition[] {\n const keySet = new Set<string>(rows.flatMap((row) => Object.keys(row)));\n return Array.from(keySet).map((key) => inferFieldDefinition(key, rows.map((row) => row[key])));\n}\n\n/**\n * Infer a `CollectionSchema` from the data of a set of content entries.\n *\n * The result is a best-effort approximation — string fields that look like\n * emails, URLs, or ISO dates get the correct semantic type. Everything else\n * falls back to `text`.\n */\nexport function inferSchema(entries: ContentEntry[], collectionName: string): CollectionSchema {\n const rows = entries.map((entry) => entry.data as Record<string, unknown>);\n return { collection: collectionName, fields: inferFields(rows) };\n}\n","/**\n * @context Core layer — content store at src/core/content-store.ts\n * @does Manages a singleton ContentIndex; exposes loadContent() and getStore() for consumers\n * @depends src/core/indexer.ts, src/shared/fs-adapter.interface.ts, src/shared/types.ts\n * @do Use this as the single access point for in-memory indexed content\n * @dont Import from CLI or UI; instantiate FsAdapter here; contain parsing or I/O logic\n */\n\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport type { StudioConfig } from \"../shared/types.js\";\nimport { ContentIndex } from \"./indexer.js\";\n\nlet store: ContentIndex | null = null;\n\nexport function getStore(): ContentIndex {\n if (!store) {\n throw new Error(\"Content not loaded. Call loadContent() before querying.\");\n }\n return store;\n}\n\nexport async function loadContent(\n fsAdapter: IFsAdapter,\n config?: StudioConfig,\n): Promise<ContentIndex> {\n const index = new ContentIndex(fsAdapter);\n await index.build(config);\n store = index;\n return index;\n}\n","/**\n * @context Core layer — type generator at src/core/type-generator.ts\n * @does Converts CollectionSchema definitions into TypeScript declaration strings for .d.ts output\n * @depends src/shared/fields.ts\n * @do Add new field-to-type mappings here as new field types are introduced\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport type { FieldDefinition, CollectionSchema } from \"../shared/fields.js\";\n\nfunction indent(code: string, spaces = 2): string {\n return code\n .split(\"\\n\")\n .map((line) => (line.trim() === \"\" ? \"\" : \" \".repeat(spaces) + line))\n .join(\"\\n\");\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .replace(/[-_\\s]+(.)/g, (_, c: string) => c.toUpperCase())\n .replace(/^(.)/, (_, c: string) => c.toUpperCase());\n}\n\nfunction fieldToTsType(field: FieldDefinition): string {\n switch (field.type) {\n case \"text\":\n case \"long-text\":\n return \"string\";\n\n case \"email\":\n return \"Email\";\n\n case \"url\":\n return \"HttpUrl\";\n\n case \"media\":\n return \"MediaPath\";\n\n case \"id\":\n return \"ID\";\n\n case \"slug\":\n return \"Slug\";\n\n case \"date\":\n return field.includeTime ? \"Date\" : \"ISODate\";\n\n case \"created-time\":\n case \"updated-time\":\n return \"Date\";\n\n case \"number\":\n return \"number\";\n\n case \"boolean\":\n return \"boolean\";\n\n case \"select\":\n case \"status\": {\n const values = field.options.map((o) => JSON.stringify(o.value));\n return values.length > 0 ? values.join(\" | \") : \"string\";\n }\n\n case \"multi-select\": {\n const values = field.options.map((o) => JSON.stringify(o.value));\n const union = values.length > 0 ? values.join(\" | \") : \"string\";\n return `Array<${union}>`;\n }\n\n case \"object\":\n return generateObjectType(field.fields);\n\n case \"array\":\n return `Array<${generateObjectType(field.itemFields)}>`;\n\n case \"relation\":\n return field.multiple === true ? \"ID[]\" : \"ID\";\n\n case \"formula\":\n if (field.resultType === \"number\") return \"number\";\n if (field.resultType === \"boolean\") return \"boolean\";\n return \"string\";\n\n default:\n return \"unknown\";\n }\n}\n\nfunction generateObjectType(fields: FieldDefinition[]): string {\n if (fields.length === 0) return \"Record<string, unknown>\";\n\n const lines = fields.map((field) => {\n const optional = field.required === false ? \"?\" : \"\";\n const tsType = fieldToTsType(field);\n const comment = field.description ? `/** ${field.description} */\\n` : \"\";\n return `${comment}${field.name}${optional}: ${tsType};`;\n });\n\n return `{\\n${indent(lines.join(\"\\n\"))}\\n}`;\n}\n\nexport function generateInterfaceForSchema(schema: CollectionSchema): string {\n const name = toPascalCase(schema.collection) + \"Entry\";\n const label = schema.label ?? schema.collection;\n const body = generateObjectType(schema.fields);\n return `/** Data shape for the \"${label}\" collection. */\\nexport interface ${name} ${body}`;\n}\n\n/**\n * Generate a complete TypeScript declaration file for all provided schemas.\n *\n * @example\n * ```ts\n * const code = generateCollectionTypes([blogSchema, authorSchema]);\n * await fs.writeFile(\".studio/types.d.ts\", code, \"utf-8\");\n * ```\n */\nexport function generateCollectionTypes(schemas: CollectionSchema[]): string {\n const banner = [\n \"// This file is auto-generated by nextjs-studio.\",\n \"// Do not edit manually — re-run `npx nextjs-studio --generate-types` to update.\",\n \"\",\n \"// Branded scalar types — structurally strings/numbers but semantically distinct.\",\n \"declare const __brand: unique symbol;\",\n \"type Brand<T, B extends string> = T & { readonly [__brand]: B };\",\n \"\",\n \"export type Email = Brand<string, 'Email'>;\",\n \"export type HttpUrl = Brand<string, 'HttpUrl'>;\",\n \"export type ISODate = Brand<string, 'ISODate'>;\",\n \"export type MediaPath = Brand<string, 'MediaPath'>;\",\n \"export type ID = Brand<string, 'ID'>;\",\n \"export type Slug = Brand<string, 'Slug'>;\",\n ].join(\"\\n\");\n\n const interfaces = schemas.map(generateInterfaceForSchema).join(\"\\n\\n\");\n\n const collectionMap = schemas\n .map((schema) => ` ${JSON.stringify(schema.collection)}: ${toPascalCase(schema.collection)}Entry;`)\n .join(\"\\n\");\n\n const collectionRegistry = [\n \"/** Maps collection names to their entry types for use with `queryCollection()`. */\",\n \"export interface CollectionTypeMap {\",\n collectionMap,\n \"}\",\n ].join(\"\\n\");\n\n return [banner, interfaces, collectionRegistry].join(\"\\n\\n\") + \"\\n\";\n}\n","{\n \"name\": \"nextjs-studio\",\n \"version\": \"0.3.0\",\n \"description\": \"A Git-based, local-first CMS for Next.js projects\",\n \"keywords\": [\n \"nextjs\",\n \"cms\",\n \"mdx\",\n \"content\",\n \"studio\",\n \"static-site\",\n \"local-first\"\n ],\n \"homepage\": \"https://github.com/TiagoDanin/Nextjs-Studio\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/TiagoDanin/Nextjs-Studio.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Tiago Danin\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/core/index.d.ts\",\n \"import\": \"./dist/core/index.js\"\n }\n },\n \"main\": \"./dist/core/index.js\",\n \"types\": \"./dist/core/index.d.ts\",\n \"bin\": \"./dist/bin/nextjs-studio.js\",\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"dev\": \"tsx src/bin/nextjs-studio.ts --dir example/contents\",\n \"studio:dev\": \"cross-env STUDIO_CONTENTS_DIR=example/contents next dev --port 3030 --webpack src/cli/ui\",\n \"studio:build\": \"next build --webpack src/cli/ui\",\n \"build\": \"tsup && yarn studio:build\",\n \"lint\": \"eslint src/\",\n \"type-check\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"engines\": {\n \"node\": \">=22.10.0\"\n },\n \"packageManager\": \"yarn@4.6.0\",\n \"devDependencies\": {\n \"@radix-ui/react-collapsible\": \"^1.1.12\",\n \"@radix-ui/react-label\": \"^2.1.8\",\n \"@radix-ui/react-switch\": \"^1.2.6\",\n \"@tailwindcss/postcss\": \"^4.1.18\",\n \"@tanstack/react-table\": \"^8.21.3\",\n \"@tiptap/extension-bubble-menu\": \"^3.20.0\",\n \"@tiptap/extension-code-block-lowlight\": \"^3.20.0\",\n \"@tiptap/extension-file-handler\": \"^3.20.0\",\n \"@tiptap/extension-image\": \"^3.20.0\",\n \"@tiptap/extension-link\": \"^3.20.0\",\n \"@tiptap/extension-placeholder\": \"^3.20.0\",\n \"@tiptap/react\": \"^3.20.0\",\n \"@tiptap/starter-kit\": \"^3.20.0\",\n \"@tiptap/suggestion\": \"^3.20.0\",\n \"@types/lodash-es\": \"^4.17.12\",\n \"@types/node\": \"^25.2.3\",\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cross-env\": \"^10.1.0\",\n \"eslint\": \"^10.0.0\",\n \"lowlight\": \"^3.3.0\",\n \"lucide-react\": \"^0.574.0\",\n \"mermaid\": \"^11.6.0\",\n \"next\": \"^16.1.6\",\n \"next-themes\": \"^0.4.6\",\n \"react\": \"^19.2.4\",\n \"react-dom\": \"^19.2.4\",\n \"tailwind-merge\": \"^3.4.1\",\n \"tailwindcss\": \"^4.1.18\",\n \"tippy.js\": \"^6.3.7\",\n \"tiptap-extension-global-drag-handle\": \"^0.1.18\",\n \"tiptap-markdown\": \"^0.9.0\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\",\n \"zustand\": \"^5.0.11\"\n },\n \"dependencies\": {\n \"@sindresorhus/slugify\": \"^3.0.0\",\n \"chokidar\": \"^5.0.0\",\n \"commander\": \"^14.0.3\",\n \"gray-matter\": \"^4.0.3\",\n \"lodash-es\": \"^4.17.23\"\n }\n}"],"mappings":"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","names":["fs","path","path","fs"]}
|
|
1
|
+
{"version":3,"sources":["../../src/bin/nextjs-studio.ts","../../src/shared/constants.ts","../../src/cli/adapters/fs-adapter.ts","../../src/core/indexer.ts","../../src/core/parsers/parser-mdx.ts","../../src/core/parsers/parser-json.ts","../../src/core/schema-inferrer.ts","../../src/core/content-store.ts","../../src/core/type-generator.ts","../../package.json"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * @context bin layer — CLI entry point at src/bin/nextjs-studio.ts\n * @does Parses CLI args, then either generates types or spawns the UI server process\n * @depends src/shared/constants.ts, src/cli/adapters/fs-adapter.ts, src/core/content-store.ts, src/core/type-generator.ts\n * @do Add new CLI flags here; keep only process bootstrap logic\n * @dont Import UI components or contain parsing/indexing business logic\n */\n\nimport { existsSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { Command } from \"commander\";\nimport { CLI_PORT, CONTENTS_DIR } from \"../shared/constants.js\";\nimport { FsAdapter } from \"../cli/adapters/fs-adapter.js\";\nimport { loadContent } from \"../core/content-store.js\";\nimport { generateCollectionTypes } from \"../core/type-generator.js\";\nimport pkg from \"../../package.json\" with { type: \"json\" };\n\nconst { version } = pkg;\n\nconst program = new Command()\n .name(\"Nextjs Studio\")\n .description(\"Local-first CMS for Next.js projects\")\n .version(version)\n .option(\"-d, --dir <path>\", \"Path to contents directory\", CONTENTS_DIR)\n .option(\"-p, --port <number>\", \"Port to run the studio on\", String(CLI_PORT))\n .option(\"--generate-types\", \"Generate TypeScript types for content collections\")\n .parse();\n\nconst opts = program.opts<{ dir: string; port: string; generateTypes?: boolean }>();\nconst contentsDir = path.resolve(opts.dir);\nconst port = Number(opts.port);\n\nasync function runGenerateTypes(sourceDir: string): Promise<void> {\n const outDir = path.resolve(\".studio\");\n const outFile = path.join(outDir, \"studio.d.ts\");\n\n console.log(`Generating types from ${sourceDir}...`);\n\n const fsAdapter = new FsAdapter(sourceDir);\n const index = await loadContent(fsAdapter);\n const schemas = index.getCollections().flatMap((c) => (c.schema ? [c.schema] : []));\n const code = generateCollectionTypes(schemas);\n\n await fs.mkdir(outDir, { recursive: true });\n await fs.writeFile(outFile, code, \"utf-8\");\n\n console.log(`Types written to ${outFile}`);\n}\n\nfunction resolveServerProcess(\n uiDir: string,\n serverPort: number,\n env: NodeJS.ProcessEnv,\n): ChildProcess | null {\n const standaloneServer = path.resolve(uiDir, \".next/standalone/src/cli/ui/server.js\");\n if (existsSync(standaloneServer)) {\n return spawn(\"node\", [standaloneServer], { stdio: \"inherit\", env });\n }\n\n // Dev mode: UI source present (running from repo with `yarn dev`)\n const uiPackageJson = path.resolve(uiDir, \"package.json\");\n if (existsSync(uiPackageJson)) {\n // Resolve `next` bin from workspace root (3 levels up from src/cli/ui)\n const nextBin = path.resolve(uiDir, \"../../../node_modules/next/dist/bin/next\");\n return spawn(\"node\", [nextBin, \"dev\", \"--port\", String(serverPort), \"--webpack\"], {\n cwd: uiDir,\n stdio: \"inherit\",\n env,\n });\n }\n\n return null;\n}\n\nfunction forwardSignals(child: ChildProcess): void {\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.on(signal, () => child.kill(signal));\n }\n}\n\nif (opts.generateTypes) {\n await runGenerateTypes(contentsDir);\n process.exit(0);\n}\n\nconst uiDir = path.resolve(import.meta.dirname, \"../cli/ui\");\nconst serverEnv = { ...process.env, STUDIO_CONTENTS_DIR: contentsDir, PORT: String(port), HOSTNAME: \"0.0.0.0\" };\nconst serverProcess = resolveServerProcess(uiDir, port, serverEnv);\n\nif (!serverProcess) {\n console.error(\"Error: Studio UI server not found.\");\n console.error(\"The pre-built UI is not included in this installation.\");\n process.exit(1);\n}\n\nconsole.log(`Nextjs Studio v${version}`);\nconsole.log(`Contents: ${contentsDir}`);\nconsole.log(`Starting on http://localhost:${port}`);\n\nserverProcess.on(\"error\", (error) => {\n console.error(\"Failed to start server:\", error.message);\n process.exit(1);\n});\n\nserverProcess.on(\"close\", (code) => process.exit(code ?? 0));\n\nforwardSignals(serverProcess);\n","/**\n * @context Shared layer — constants at src/shared/constants.ts\n * @does Defines project-wide constants shared across core, CLI, and UI layers\n * @depends none\n * @do Add new shared constants here\n * @dont Import from CLI or UI; constants must be framework-agnostic\n */\n\nexport const CONTENTS_DIR = \"contents\";\nexport const CLI_PORT = 3030;\nexport const CONFIG_FILE = \"studio.config.ts\";\nexport const SUPPORTED_EXTENSIONS = [\".mdx\", \".json\"] as const;\nexport const COLLECTION_ORDER_FILE = \"collection.json\";\nexport const WATCHER_DEBOUNCE_MS = 5_000;\nexport const MEDIA_DIR = \"media\";\n\nexport const IMAGE_MIME_TYPES = [\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n \"image/svg+xml\",\n \"image/avif\",\n] as const;\n\nexport const VIDEO_MIME_TYPES = [\"video/mp4\", \"video/webm\", \"video/ogg\"] as const;\n\nexport const AUDIO_MIME_TYPES = [\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/wav\",\n \"audio/webm\",\n \"audio/aac\",\n \"audio/flac\",\n] as const;\n\nexport const MEDIA_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES, ...AUDIO_MIME_TYPES] as const;\n\nexport const IMAGE_EXTENSIONS = [\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".avif\"] as const;\nexport const VIDEO_EXTENSIONS = [\".mp4\", \".webm\", \".ogv\"] as const;\nexport const AUDIO_EXTENSIONS = [\".mp3\", \".ogg\", \".wav\", \".m4a\", \".aac\", \".flac\"] as const;\n","/**\n * @context CLI layer — filesystem adapter at src/cli/adapters/fs-adapter.ts\n * @does Implements IFsAdapter; abstracts all file read/write/list operations behind a single interface\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts\n * @do Add new I/O operations here; all file access must go through this adapter\n * @dont Import UI components, run HTTP requests, or contain business logic\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Dirent } from \"node:fs\";\nimport type { FileInfo, DirectoryFileEntry } from \"../../shared/types.js\";\nimport type { IFsAdapter } from \"../../shared/fs-adapter.interface.js\";\nimport { SUPPORTED_EXTENSIONS } from \"../../shared/constants.js\";\n\nexport class FsAdapter implements IFsAdapter {\n private readonly basePath: string;\n\n constructor(basePath: string) {\n this.basePath = path.resolve(basePath);\n }\n\n private resolve(...segments: string[]): string {\n return path.resolve(this.basePath, ...segments);\n }\n\n async readFile(filePath: string): Promise<string> {\n return fs.readFile(this.resolve(filePath), \"utf-8\");\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, content, \"utf-8\");\n }\n\n async deleteFile(filePath: string): Promise<void> {\n await fs.unlink(this.resolve(filePath));\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n await fs.access(this.resolve(filePath));\n return true;\n } catch {\n return false;\n }\n }\n\n async getStats(filePath: string): Promise<FileInfo> {\n const fullPath = this.resolve(filePath);\n const stats = await fs.stat(fullPath);\n return { path: filePath, size: stats.size, modifiedAt: stats.mtime };\n }\n\n async listFiles(dirPath: string, extensions?: readonly string[]): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n const filterExts = extensions ?? SUPPORTED_EXTENSIONS;\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isFile() && filterExts.some((ext) => entry.name.endsWith(ext)))\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async listDirectories(dirPath: string): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async readBuffer(filePath: string): Promise<Buffer> {\n return fs.readFile(this.resolve(filePath));\n }\n\n async writeBuffer(filePath: string, data: Buffer): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, data);\n }\n\n async listAllFiles(dirPath: string): Promise<DirectoryFileEntry[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const results: DirectoryFileEntry[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const relativePath = this.join(dirPath, entry.name);\n const stats = await fs.stat(this.resolve(relativePath));\n results.push({ name: entry.name, relativePath, size: stats.size, modifiedAt: stats.mtime });\n }\n return results;\n }\n\n join(...segments: string[]): string {\n return path.join(...segments);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n extname(filePath: string): string {\n return path.extname(filePath);\n }\n\n relative(from: string, to: string): string {\n return path.relative(from, to);\n }\n\n normalizeSlug(relativePath: string, ext: string): string {\n return relativePath.replace(ext, \"\").split(path.sep).join(\"/\");\n }\n}\n","/**\n * @context Core layer — content indexer at src/core/indexer.ts\n * @does Scans the contents directory, parses MDX/JSON files, and builds an in-memory index\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts, src/core/parsers/, src/core/schema-inferrer.ts\n * @do Add new file type handling here; extend indexCollection for new collection behaviors\n * @dont Import from CLI or UI; instantiate FsAdapter; access the filesystem directly\n */\n\nimport slugify from \"@sindresorhus/slugify\";\nimport type { CollectionSchema } from \"../shared/fields.js\";\nimport type { ContentEntry, Collection, StudioConfig } from \"../shared/types.js\";\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport { COLLECTION_ORDER_FILE } from \"../shared/constants.js\";\nimport { parseMdx } from \"./parsers/parser-mdx.js\";\nimport { parseJson } from \"./parsers/parser-json.js\";\nimport { inferSchema } from \"./schema-inferrer.js\";\n\nexport class ContentIndex {\n private readonly entries = new Map<string, ContentEntry[]>();\n private readonly collections = new Map<string, Collection>();\n private readonly fs: IFsAdapter;\n\n constructor(fsAdapter: IFsAdapter) {\n this.fs = fsAdapter;\n }\n\n async build(config?: StudioConfig): Promise<void> {\n this.clear();\n const dirs = await this.fs.listDirectories(\".\");\n\n for (const dir of dirs) {\n const dirName = this.fs.basename(dir);\n const collectionName = slugify(dirName);\n const collectionConfig = config?.collections?.[collectionName];\n await this.indexCollection(dirName, collectionName, collectionConfig?.schema);\n }\n }\n\n getCollection(name: string): ContentEntry[] {\n return this.entries.get(name) ?? [];\n }\n\n getCollections(): Collection[] {\n return Array.from(this.collections.values());\n }\n\n clear(): void {\n this.entries.clear();\n this.collections.clear();\n }\n\n private async indexCollection(\n dirName: string,\n collectionName: string,\n manualSchema?: CollectionSchema,\n ): Promise<void> {\n const entries: ContentEntry[] = [];\n await this.scanDir(dirName, collectionName, dirName, entries);\n\n const orderPath = this.fs.join(dirName, COLLECTION_ORDER_FILE);\n const ordering = await this.readOrdering(orderPath);\n if (ordering) {\n this.applyOrdering(entries, ordering);\n }\n\n const schema = manualSchema ?? inferSchema(entries, collectionName);\n\n this.entries.set(collectionName, entries);\n this.collections.set(collectionName, {\n name: collectionName,\n type: this.detectCollectionType(entries),\n count: entries.length,\n basePath: dirName,\n schema,\n });\n }\n\n private async scanDir(\n dirName: string,\n collectionName: string,\n dirPath: string,\n entries: ContentEntry[],\n ): Promise<void> {\n const subDirs = await this.fs.listDirectories(dirPath);\n for (const subDir of subDirs) {\n await this.scanDir(dirName, collectionName, subDir, entries);\n }\n\n const files = await this.fs.listFiles(dirPath);\n for (const filePath of files) {\n const fileName = this.fs.basename(filePath);\n if (fileName === COLLECTION_ORDER_FILE) continue;\n\n const ext = this.fs.extname(fileName);\n const content = await this.fs.readFile(filePath);\n const relativePath = this.fs.relative(dirName, filePath);\n const slug = this.fs\n .normalizeSlug(relativePath, ext)\n .split(\"/\")\n .map((segment) => slugify(segment))\n .join(\"/\");\n\n if (ext === \".mdx\") {\n entries.push(this.buildMdxEntry(collectionName, slug, content));\n } else if (ext === \".json\") {\n entries.push(...this.buildJsonEntries(collectionName, slug, content));\n }\n }\n }\n\n private buildMdxEntry(collectionName: string, slug: string, content: string): ContentEntry {\n const parsed = parseMdx(content);\n return {\n collection: collectionName,\n slug,\n path: `/${collectionName}/${slug}`,\n body: parsed.body,\n data: parsed.data,\n };\n }\n\n private buildJsonEntries(collectionName: string, slug: string, content: string): ContentEntry[] {\n const parsed = parseJson(content);\n\n if (parsed.type === \"json-array\") {\n return parsed.entries.map((data, index) => {\n const entrySlug =\n typeof data[\"slug\"] === \"string\" ? slugify(data[\"slug\"]) : `${slug}/${index}`;\n return {\n collection: collectionName,\n slug: entrySlug,\n path: `/${collectionName}/${entrySlug}`,\n data,\n };\n });\n }\n\n return [{ collection: collectionName, slug, path: `/${collectionName}/${slug}`, data: parsed.data }];\n }\n\n private async readOrdering(orderPath: string): Promise<string[] | null> {\n if (!(await this.fs.exists(orderPath))) return null;\n\n try {\n const content = await this.fs.readFile(orderPath);\n const parsed: unknown = JSON.parse(content);\n if (Array.isArray(parsed)) return parsed as string[];\n } catch (error) {\n console.warn(`[Nextjs Studio] Failed to parse ordering file: ${orderPath}`, error);\n }\n return null;\n }\n\n private applyOrdering(entries: ContentEntry[], ordering: string[]): void {\n const orderMap = new Map(ordering.map((slug, index) => [slug, index]));\n entries.sort((a, b) => {\n const aIndex = orderMap.get(a.slug) ?? Infinity;\n const bIndex = orderMap.get(b.slug) ?? Infinity;\n return aIndex - bIndex;\n });\n }\n\n private detectCollectionType(entries: ContentEntry[]): Collection[\"type\"] {\n if (entries.length === 0) return \"mdx\";\n const first = entries[0];\n if (first.body !== undefined) return \"mdx\";\n if (entries.length === 1 && !first.slug.includes(\"/\")) return \"json-object\";\n return \"json-array\";\n }\n}\n","/**\n * @context Core layer — MDX parser/serializer at src/core/parsers/parser-mdx.ts\n * @does Parses .mdx content into frontmatter + body, and serializes them back to MDX strings\n * @depends none (gray-matter is an external dep)\n * @do Add MDX transform steps here; both parse and serialize live here intentionally\n * @dont Access the filesystem; import from CLI or UI; handle JSON content\n */\n\nimport matter from \"gray-matter\";\n\nexport interface ParsedMdx {\n data: Record<string, unknown>;\n body: string;\n}\n\nexport function parseMdx(content: string): ParsedMdx {\n const { data, content: body } = matter(content);\n return { data, body: body.trim() };\n}\n\nexport function serializeMdx(data: Record<string, unknown>, body: string): string {\n return matter.stringify(body, data);\n}\n","/**\n * @context Core layer — JSON parser at src/core/parsers/parser-json.ts\n * @does Parses JSON content strings into typed ParsedJson results (array or object)\n * @depends none\n * @do Extend ParsedJson variants here if new JSON structures are supported\n * @dont Access the filesystem; import from CLI or UI; contain serialization logic\n */\n\nexport interface ParsedJsonArray {\n type: \"json-array\";\n entries: Record<string, unknown>[];\n}\n\nexport interface ParsedJsonObject {\n type: \"json-object\";\n data: Record<string, unknown>;\n}\n\nexport type ParsedJson = ParsedJsonArray | ParsedJsonObject;\n\nexport function parseJson(content: string): ParsedJson {\n const parsed: unknown = JSON.parse(content);\n\n if (Array.isArray(parsed)) {\n return {\n type: \"json-array\",\n entries: parsed as Record<string, unknown>[],\n };\n }\n\n if (typeof parsed === \"object\" && parsed !== null) {\n return {\n type: \"json-object\",\n data: parsed as Record<string, unknown>,\n };\n }\n\n throw new Error(\"JSON content must be an array or object\");\n}\n","/**\n * @context Core layer — schema inferrer at src/core/schema-inferrer.ts\n * @does Infers a CollectionSchema from actual content entries when no manual schema is defined\n * @depends src/shared/types.ts, src/shared/fields.ts\n * @do Add new type detection heuristics here (e.g. color, phone)\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport type { ContentEntry } from \"../shared/types.js\";\nimport type { CollectionSchema, FieldDefinition, SelectOption } from \"../shared/fields.js\";\n\n// Value detector patterns\nconst RE_ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst RE_ISO_DATETIME =\n /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?$/;\nconst RE_EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst RE_URL = /^https?:\\/\\/.+/;\nconst LONG_TEXT_THRESHOLD = 200;\n\nfunction isISODate(value: string): boolean {\n return RE_ISO_DATE.test(value);\n}\n\nfunction isISODateTime(value: string): boolean {\n return RE_ISO_DATETIME.test(value);\n}\n\nfunction isEmail(value: string): boolean {\n return RE_EMAIL.test(value);\n}\n\nfunction isUrl(value: string): boolean {\n return RE_URL.test(value);\n}\n\nfunction inferStringField(name: string, strings: string[]): FieldDefinition {\n if (strings.every(isEmail)) return { name, type: \"email\" };\n if (strings.every(isUrl)) return { name, type: \"url\" };\n if (strings.every(isISODateTime)) return { name, type: \"date\", includeTime: true };\n if (strings.every(isISODate)) return { name, type: \"date\" };\n\n const isLong = strings.some((s) => s.length > LONG_TEXT_THRESHOLD || s.includes(\"\\n\"));\n return { name, type: isLong ? \"long-text\" : \"text\" };\n}\n\nfunction inferArrayField(name: string, items: unknown[]): FieldDefinition {\n if (items.length === 0) return { name, type: \"array\", itemFields: [] };\n\n if (items.every((item) => typeof item === \"string\")) {\n const unique = [...new Set(items as string[])].slice(0, 50);\n const options: SelectOption[] = unique.map((v) => ({ label: v, value: v }));\n return { name, type: \"multi-select\", options };\n }\n\n if (items.every((item) => typeof item === \"object\" && item !== null && !Array.isArray(item))) {\n return { name, type: \"array\", itemFields: inferFields(items as Record<string, unknown>[]) };\n }\n\n return { name, type: \"array\", itemFields: [] };\n}\n\nfunction inferFieldDefinition(name: string, values: unknown[]): FieldDefinition {\n const present = values.filter((v) => v !== null && v !== undefined);\n\n if (present.length === 0) return { name, type: \"text\" };\n if (present.every((v) => typeof v === \"boolean\")) return { name, type: \"boolean\" };\n\n if (present.every((v) => typeof v === \"number\")) {\n const format = present.every((v) => Number.isInteger(v)) ? \"integer\" : \"decimal\";\n return { name, type: \"number\", format };\n }\n\n if (present.every((v) => typeof v === \"string\")) {\n return inferStringField(name, present as string[]);\n }\n\n if (present.every((v) => Array.isArray(v))) {\n return inferArrayField(name, (present as unknown[][]).flat());\n }\n\n if (present.every((v) => typeof v === \"object\" && v !== null && !Array.isArray(v))) {\n return { name, type: \"object\", fields: inferFields(present as Record<string, unknown>[]) };\n }\n\n return { name, type: \"text\" };\n}\n\nfunction inferFields(rows: Record<string, unknown>[]): FieldDefinition[] {\n const keySet = new Set<string>(rows.flatMap((row) => Object.keys(row)));\n return Array.from(keySet).map((key) => inferFieldDefinition(key, rows.map((row) => row[key])));\n}\n\n/**\n * Infer a `CollectionSchema` from the data of a set of content entries.\n *\n * The result is a best-effort approximation — string fields that look like\n * emails, URLs, or ISO dates get the correct semantic type. Everything else\n * falls back to `text`.\n */\nexport function inferSchema(entries: ContentEntry[], collectionName: string): CollectionSchema {\n const rows = entries.map((entry) => entry.data as Record<string, unknown>);\n return { collection: collectionName, fields: inferFields(rows) };\n}\n","/**\n * @context Core layer — content store at src/core/content-store.ts\n * @does Manages a singleton ContentIndex; exposes loadContent() and getStore() for consumers\n * @depends src/core/indexer.ts, src/shared/fs-adapter.interface.ts, src/shared/types.ts\n * @do Use this as the single access point for in-memory indexed content\n * @dont Import from CLI or UI; instantiate FsAdapter here; contain parsing or I/O logic\n */\n\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport type { StudioConfig } from \"../shared/types.js\";\nimport { ContentIndex } from \"./indexer.js\";\n\nlet store: ContentIndex | null = null;\n\nexport function getStore(): ContentIndex {\n if (!store) {\n throw new Error(\"Content not loaded. Call loadContent() before querying.\");\n }\n return store;\n}\n\nexport async function loadContent(\n fsAdapter: IFsAdapter,\n config?: StudioConfig,\n): Promise<ContentIndex> {\n const index = new ContentIndex(fsAdapter);\n await index.build(config);\n store = index;\n return index;\n}\n","/**\n * @context Core layer — type generator at src/core/type-generator.ts\n * @does Converts CollectionSchema definitions into TypeScript declaration strings for .d.ts output\n * @depends src/shared/fields.ts\n * @do Add new field-to-type mappings here as new field types are introduced\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport type { FieldDefinition, CollectionSchema } from \"../shared/fields.js\";\n\nfunction indent(code: string, spaces = 2): string {\n return code\n .split(\"\\n\")\n .map((line) => (line.trim() === \"\" ? \"\" : \" \".repeat(spaces) + line))\n .join(\"\\n\");\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .replace(/[-_\\s]+(.)/g, (_, c: string) => c.toUpperCase())\n .replace(/^(.)/, (_, c: string) => c.toUpperCase());\n}\n\nfunction fieldToTsType(field: FieldDefinition): string {\n switch (field.type) {\n case \"text\":\n case \"long-text\":\n return \"string\";\n\n case \"email\":\n return \"Email\";\n\n case \"url\":\n return \"HttpUrl\";\n\n case \"media\":\n return \"MediaPath\";\n\n case \"id\":\n return \"ID\";\n\n case \"slug\":\n return \"Slug\";\n\n case \"date\":\n return field.includeTime ? \"Date\" : \"ISODate\";\n\n case \"created-time\":\n case \"updated-time\":\n return \"Date\";\n\n case \"number\":\n return \"number\";\n\n case \"boolean\":\n return \"boolean\";\n\n case \"select\":\n case \"status\": {\n const values = field.options.map((o) => JSON.stringify(o.value));\n return values.length > 0 ? values.join(\" | \") : \"string\";\n }\n\n case \"multi-select\": {\n const values = field.options.map((o) => JSON.stringify(o.value));\n const union = values.length > 0 ? values.join(\" | \") : \"string\";\n return `Array<${union}>`;\n }\n\n case \"object\":\n return generateObjectType(field.fields);\n\n case \"array\":\n return `Array<${generateObjectType(field.itemFields)}>`;\n\n case \"relation\":\n return field.multiple === true ? \"ID[]\" : \"ID\";\n\n case \"formula\":\n if (field.resultType === \"number\") return \"number\";\n if (field.resultType === \"boolean\") return \"boolean\";\n return \"string\";\n\n default:\n return \"unknown\";\n }\n}\n\nfunction generateObjectType(fields: FieldDefinition[]): string {\n if (fields.length === 0) return \"Record<string, unknown>\";\n\n const lines = fields.map((field) => {\n const optional = field.required === false ? \"?\" : \"\";\n const tsType = fieldToTsType(field);\n const comment = field.description ? `/** ${field.description} */\\n` : \"\";\n return `${comment}${field.name}${optional}: ${tsType};`;\n });\n\n return `{\\n${indent(lines.join(\"\\n\"))}\\n}`;\n}\n\nexport function generateInterfaceForSchema(schema: CollectionSchema): string {\n const name = toPascalCase(schema.collection) + \"Entry\";\n const label = schema.label ?? schema.collection;\n const body = generateObjectType(schema.fields);\n return `/** Data shape for the \"${label}\" collection. */\\nexport interface ${name} ${body}`;\n}\n\n/**\n * Generate a complete TypeScript declaration file for all provided schemas.\n *\n * @example\n * ```ts\n * const code = generateCollectionTypes([blogSchema, authorSchema]);\n * await fs.writeFile(\".studio/types.d.ts\", code, \"utf-8\");\n * ```\n */\nexport function generateCollectionTypes(schemas: CollectionSchema[]): string {\n const banner = [\n \"// This file is auto-generated by nextjs-studio.\",\n \"// Do not edit manually — re-run `npx nextjs-studio --generate-types` to update.\",\n \"\",\n \"// Branded scalar types — structurally strings/numbers but semantically distinct.\",\n \"declare const __brand: unique symbol;\",\n \"type Brand<T, B extends string> = T & { readonly [__brand]: B };\",\n \"\",\n \"export type Email = Brand<string, 'Email'>;\",\n \"export type HttpUrl = Brand<string, 'HttpUrl'>;\",\n \"export type ISODate = Brand<string, 'ISODate'>;\",\n \"export type MediaPath = Brand<string, 'MediaPath'>;\",\n \"export type ID = Brand<string, 'ID'>;\",\n \"export type Slug = Brand<string, 'Slug'>;\",\n ].join(\"\\n\");\n\n const interfaces = schemas.map(generateInterfaceForSchema).join(\"\\n\\n\");\n\n const collectionRegistry = [\n \"// Augment the nextjs-studio module so queryCollection() is fully typed.\",\n \"declare module 'nextjs-studio' {\",\n \" interface CollectionTypeMap {\",\n schemas.map((schema) => ` ${JSON.stringify(schema.collection)}: ${toPascalCase(schema.collection)}Entry;`).join(\"\\n\"),\n \" }\",\n \"}\",\n ].join(\"\\n\");\n\n return [banner, interfaces, collectionRegistry].join(\"\\n\\n\") + \"\\n\";\n}\n","{\n \"name\": \"nextjs-studio\",\n \"version\": \"0.4.0\",\n \"description\": \"A Git-based, local-first CMS for Next.js projects\",\n \"keywords\": [\n \"nextjs\",\n \"cms\",\n \"mdx\",\n \"content\",\n \"studio\",\n \"static-site\",\n \"local-first\"\n ],\n \"homepage\": \"https://github.com/TiagoDanin/Nextjs-Studio\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/TiagoDanin/Nextjs-Studio.git\"\n },\n \"license\": \"MIT\",\n \"author\": \"Tiago Danin\",\n \"type\": \"module\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/core/index.d.ts\",\n \"import\": \"./dist/core/index.js\"\n }\n },\n \"main\": \"./dist/core/index.js\",\n \"types\": \"./dist/core/index.d.ts\",\n \"bin\": {\n \"nextjs-studio\": \"dist/bin/nextjs-studio.js\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"dev\": \"tsx src/bin/nextjs-studio.ts --dir example/contents\",\n \"studio:dev\": \"cross-env STUDIO_CONTENTS_DIR=example/contents next dev --port 3030 --webpack src/cli/ui\",\n \"studio:build\": \"next build --webpack src/cli/ui\",\n \"build\": \"tsup && yarn studio:build\",\n \"lint\": \"eslint src/\",\n \"type-check\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\"\n },\n \"engines\": {\n \"node\": \">=22.10.0\"\n },\n \"packageManager\": \"yarn@4.6.0\",\n \"devDependencies\": {\n \"@radix-ui/react-collapsible\": \"^1.1.12\",\n \"@radix-ui/react-label\": \"^2.1.8\",\n \"@radix-ui/react-switch\": \"^1.2.6\",\n \"@tailwindcss/postcss\": \"^4.1.18\",\n \"@tanstack/react-table\": \"^8.21.3\",\n \"@tiptap/extension-bubble-menu\": \"^3.20.0\",\n \"@tiptap/extension-code-block-lowlight\": \"^3.20.0\",\n \"@tiptap/extension-file-handler\": \"^3.20.0\",\n \"@tiptap/extension-image\": \"^3.20.0\",\n \"@tiptap/extension-link\": \"^3.20.0\",\n \"@tiptap/extension-placeholder\": \"^3.20.0\",\n \"@tiptap/react\": \"^3.20.0\",\n \"@tiptap/starter-kit\": \"^3.20.0\",\n \"@tiptap/suggestion\": \"^3.20.0\",\n \"@types/lodash-es\": \"^4.17.12\",\n \"@types/node\": \"^25.2.3\",\n \"@types/react\": \"^19\",\n \"@types/react-dom\": \"^19\",\n \"class-variance-authority\": \"^0.7.1\",\n \"clsx\": \"^2.1.1\",\n \"cross-env\": \"^10.1.0\",\n \"eslint\": \"^10.0.0\",\n \"lowlight\": \"^3.3.0\",\n \"lucide-react\": \"^0.574.0\",\n \"mermaid\": \"^11.6.0\",\n \"next\": \"^16.1.6\",\n \"next-themes\": \"^0.4.6\",\n \"react\": \"^19.2.4\",\n \"react-dom\": \"^19.2.4\",\n \"tailwind-merge\": \"^3.4.1\",\n \"tailwindcss\": \"^4.1.18\",\n \"tippy.js\": \"^6.3.7\",\n \"tiptap-extension-global-drag-handle\": \"^0.1.18\",\n \"tiptap-markdown\": \"^0.9.0\",\n \"tsup\": \"^8.5.1\",\n \"tsx\": \"^4.21.0\",\n \"typescript\": \"^5.9.3\",\n \"vitest\": \"^4.0.18\",\n \"zustand\": \"^5.0.11\"\n },\n \"dependencies\": {\n \"@sindresorhus/slugify\": \"^3.0.0\",\n \"chokidar\": \"^5.0.0\",\n \"commander\": \"^14.0.3\",\n \"gray-matter\": \"^4.0.3\",\n \"lodash-es\": \"^4.17.23\"\n }\n}"],"mappings":"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","names":["fs","path","path","fs","uiDir"]}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -210,6 +210,20 @@ interface CollectionConfig {
|
|
|
210
210
|
sync?: string;
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Augmentable map of collection names to their typed entry shapes.
|
|
215
|
+
* Extend this via generated types or manual declaration:
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* // .studio/studio.d.ts (auto-generated)
|
|
219
|
+
* declare module 'nextjs-studio' {
|
|
220
|
+
* interface CollectionTypeMap {
|
|
221
|
+
* posts: { title: string; date: string; slug: string };
|
|
222
|
+
* }
|
|
223
|
+
* }
|
|
224
|
+
*/
|
|
225
|
+
interface CollectionTypeMap {
|
|
226
|
+
}
|
|
213
227
|
/**
|
|
214
228
|
* Query options for the content query builder.
|
|
215
229
|
*/
|
|
@@ -264,7 +278,7 @@ interface DirectoryFileEntry {
|
|
|
264
278
|
* queryCollection("pages").where({ "hero.title": "Welcome" }).all();
|
|
265
279
|
* ```
|
|
266
280
|
*/
|
|
267
|
-
declare class QueryBuilder {
|
|
281
|
+
declare class QueryBuilder<T = Record<string, unknown>> {
|
|
268
282
|
private readonly collectionName;
|
|
269
283
|
private options;
|
|
270
284
|
constructor(collection: string);
|
|
@@ -272,14 +286,15 @@ declare class QueryBuilder {
|
|
|
272
286
|
sort(field: string, order?: "asc" | "desc"): this;
|
|
273
287
|
limit(count: number): this;
|
|
274
288
|
offset(count: number): this;
|
|
275
|
-
all():
|
|
276
|
-
first():
|
|
289
|
+
all(): T[];
|
|
290
|
+
first(): T | undefined;
|
|
277
291
|
count(): number;
|
|
278
292
|
}
|
|
279
293
|
/**
|
|
280
294
|
* Entry point for querying a content collection.
|
|
281
295
|
*/
|
|
282
|
-
declare function queryCollection(
|
|
296
|
+
declare function queryCollection<K extends keyof CollectionTypeMap>(name: K): QueryBuilder<CollectionTypeMap[K]>;
|
|
297
|
+
declare function queryCollection(name: string): QueryBuilder<Record<string, unknown>>;
|
|
283
298
|
|
|
284
299
|
/**
|
|
285
300
|
* @context Core layer — studio initializer at src/core/init.ts
|
|
@@ -309,6 +324,26 @@ declare function initStudio(contentsDir?: string, config?: StudioConfig): Promis
|
|
|
309
324
|
* Returns true if the content store has been initialized.
|
|
310
325
|
*/
|
|
311
326
|
declare function isStudioInitialized(): boolean;
|
|
327
|
+
/**
|
|
328
|
+
* Ensures the content store is initialized. Safe to call multiple times — only
|
|
329
|
+
* initializes once. Ideal for use at the top of Next.js server components and
|
|
330
|
+
* `generateStaticParams` / `generateMetadata` functions.
|
|
331
|
+
*
|
|
332
|
+
* @param contentsDir - Path to the contents directory. Defaults to `./contents` relative to cwd.
|
|
333
|
+
* @param config - Optional studio config for schemas and scripts.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* import { ensureContentLoaded, queryCollection } from "nextjs-studio";
|
|
338
|
+
*
|
|
339
|
+
* export default async function Page() {
|
|
340
|
+
* await ensureContentLoaded();
|
|
341
|
+
* const posts = queryCollection("posts");
|
|
342
|
+
* // ...
|
|
343
|
+
* }
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
declare function ensureContentLoaded(contentsDir?: string, config?: StudioConfig): Promise<void>;
|
|
312
347
|
|
|
313
348
|
/**
|
|
314
349
|
* @context Shared layer — FS adapter interface at src/shared/fs-adapter.interface.ts
|
|
@@ -436,4 +471,4 @@ declare function fieldLabel(field: Pick<BaseField, "name" | "label">): string;
|
|
|
436
471
|
*/
|
|
437
472
|
declare function keyLabel(name: string): string;
|
|
438
473
|
|
|
439
|
-
export { type Collection, type CollectionConfig, type CollectionSchema, type ContentEntry, ContentIndex, type FieldDefinition, type FieldType, type InferFieldValue, type InferSchemaData, type QueryOptions, type StudioConfig, fieldLabel, initStudio, isStudioInitialized, keyLabel, loadContent, queryCollection };
|
|
474
|
+
export { type Collection, type CollectionConfig, type CollectionSchema, type ContentEntry, ContentIndex, type FieldDefinition, type FieldType, type InferFieldValue, type InferSchemaData, type QueryOptions, type StudioConfig, ensureContentLoaded, fieldLabel, initStudio, isStudioInitialized, keyLabel, loadContent, queryCollection };
|
package/dist/core/index.js
CHANGED
|
@@ -289,7 +289,7 @@ var QueryBuilder = class {
|
|
|
289
289
|
}
|
|
290
290
|
const start = this.options.offset ?? 0;
|
|
291
291
|
const end = this.options.limit ? start + this.options.limit : void 0;
|
|
292
|
-
return slice(entries, start, end);
|
|
292
|
+
return slice(entries, start, end).map((e) => e.data);
|
|
293
293
|
}
|
|
294
294
|
first() {
|
|
295
295
|
return this.limit(1).all()[0];
|
|
@@ -298,8 +298,8 @@ var QueryBuilder = class {
|
|
|
298
298
|
return this.all().length;
|
|
299
299
|
}
|
|
300
300
|
};
|
|
301
|
-
function queryCollection(
|
|
302
|
-
return new QueryBuilder(
|
|
301
|
+
function queryCollection(name) {
|
|
302
|
+
return new QueryBuilder(name);
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
// src/core/init.ts
|
|
@@ -416,6 +416,11 @@ function isStudioInitialized() {
|
|
|
416
416
|
return false;
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
|
+
async function ensureContentLoaded(contentsDir, config) {
|
|
420
|
+
if (!isStudioInitialized()) {
|
|
421
|
+
await initStudio(contentsDir, config);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
419
424
|
|
|
420
425
|
// src/shared/field-utils.ts
|
|
421
426
|
function fieldLabel(field) {
|
|
@@ -427,6 +432,7 @@ function keyLabel(name) {
|
|
|
427
432
|
}
|
|
428
433
|
export {
|
|
429
434
|
ContentIndex,
|
|
435
|
+
ensureContentLoaded,
|
|
430
436
|
fieldLabel,
|
|
431
437
|
initStudio,
|
|
432
438
|
isStudioInitialized,
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/query-builder.ts","../../src/core/indexer.ts","../../src/shared/constants.ts","../../src/core/parsers/parser-mdx.ts","../../src/core/parsers/parser-json.ts","../../src/core/schema-inferrer.ts","../../src/core/content-store.ts","../../src/core/init.ts","../../src/cli/adapters/fs-adapter.ts","../../src/shared/field-utils.ts"],"sourcesContent":["/**\n * @context Core layer — query builder at src/core/query-builder.ts\n * @does Provides a fluent API to filter, sort, and paginate content entries from a collection\n * @depends src/shared/types.ts, src/core/content-store.ts\n * @do Add new query capabilities here (e.g. search, groupBy)\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport { filter, orderBy, get, slice } from \"lodash-es\";\nimport type { ContentEntry, QueryOptions } from \"../shared/types.js\";\nimport { getStore } from \"./content-store.js\";\n\n/**\n * Fluent query builder for content collections.\n *\n * ```ts\n * const posts = queryCollection(\"blog\")\n * .where({ published: true })\n * .sort(\"date\", \"desc\")\n * .limit(10)\n * .all();\n * ```\n *\n * Supports dot notation for nested properties:\n * ```ts\n * queryCollection(\"pages\").where({ \"hero.title\": \"Welcome\" }).all();\n * ```\n */\nexport class QueryBuilder {\n private readonly collectionName: string;\n private options: QueryOptions = {};\n\n constructor(collection: string) {\n this.collectionName = collection;\n }\n\n where(conditions: Record<string, unknown>): this {\n this.options.where = { ...this.options.where, ...conditions };\n return this;\n }\n\n sort(field: string, order: \"asc\" | \"desc\" = \"asc\"): this {\n this.options.sort = { field, order };\n return this;\n }\n\n limit(count: number): this {\n this.options.limit = count;\n return this;\n }\n\n offset(count: number): this {\n this.options.offset = count;\n return this;\n }\n\n all(): ContentEntry[] {\n let entries = [...getStore().getCollection(this.collectionName)];\n\n if (this.options.where) {\n const conditions = this.options.where;\n entries = filter(entries, (entry) =>\n Object.entries(conditions).every(([key, value]) => get(entry.data, key) === value),\n );\n }\n\n if (this.options.sort) {\n const { field, order } = this.options.sort;\n entries = orderBy(entries, [(entry) => get(entry.data, field)], [order]);\n }\n\n const start = this.options.offset ?? 0;\n const end = this.options.limit ? start + this.options.limit : undefined;\n return slice(entries, start, end);\n }\n\n first(): ContentEntry | undefined {\n return this.limit(1).all()[0];\n }\n\n count(): number {\n return this.all().length;\n }\n}\n\n/**\n * Entry point for querying a content collection.\n */\nexport function queryCollection(collection: string): QueryBuilder {\n return new QueryBuilder(collection);\n}\n","/**\n * @context Core layer — content indexer at src/core/indexer.ts\n * @does Scans the contents directory, parses MDX/JSON files, and builds an in-memory index\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts, src/core/parsers/, src/core/schema-inferrer.ts\n * @do Add new file type handling here; extend indexCollection for new collection behaviors\n * @dont Import from CLI or UI; instantiate FsAdapter; access the filesystem directly\n */\n\nimport slugify from \"@sindresorhus/slugify\";\nimport type { CollectionSchema } from \"../shared/fields.js\";\nimport type { ContentEntry, Collection, StudioConfig } from \"../shared/types.js\";\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport { COLLECTION_ORDER_FILE } from \"../shared/constants.js\";\nimport { parseMdx } from \"./parsers/parser-mdx.js\";\nimport { parseJson } from \"./parsers/parser-json.js\";\nimport { inferSchema } from \"./schema-inferrer.js\";\n\nexport class ContentIndex {\n private readonly entries = new Map<string, ContentEntry[]>();\n private readonly collections = new Map<string, Collection>();\n private readonly fs: IFsAdapter;\n\n constructor(fsAdapter: IFsAdapter) {\n this.fs = fsAdapter;\n }\n\n async build(config?: StudioConfig): Promise<void> {\n this.clear();\n const dirs = await this.fs.listDirectories(\".\");\n\n for (const dir of dirs) {\n const dirName = this.fs.basename(dir);\n const collectionName = slugify(dirName);\n const collectionConfig = config?.collections?.[collectionName];\n await this.indexCollection(dirName, collectionName, collectionConfig?.schema);\n }\n }\n\n getCollection(name: string): ContentEntry[] {\n return this.entries.get(name) ?? [];\n }\n\n getCollections(): Collection[] {\n return Array.from(this.collections.values());\n }\n\n clear(): void {\n this.entries.clear();\n this.collections.clear();\n }\n\n private async indexCollection(\n dirName: string,\n collectionName: string,\n manualSchema?: CollectionSchema,\n ): Promise<void> {\n const entries: ContentEntry[] = [];\n await this.scanDir(dirName, collectionName, dirName, entries);\n\n const orderPath = this.fs.join(dirName, COLLECTION_ORDER_FILE);\n const ordering = await this.readOrdering(orderPath);\n if (ordering) {\n this.applyOrdering(entries, ordering);\n }\n\n const schema = manualSchema ?? inferSchema(entries, collectionName);\n\n this.entries.set(collectionName, entries);\n this.collections.set(collectionName, {\n name: collectionName,\n type: this.detectCollectionType(entries),\n count: entries.length,\n basePath: dirName,\n schema,\n });\n }\n\n private async scanDir(\n dirName: string,\n collectionName: string,\n dirPath: string,\n entries: ContentEntry[],\n ): Promise<void> {\n const subDirs = await this.fs.listDirectories(dirPath);\n for (const subDir of subDirs) {\n await this.scanDir(dirName, collectionName, subDir, entries);\n }\n\n const files = await this.fs.listFiles(dirPath);\n for (const filePath of files) {\n const fileName = this.fs.basename(filePath);\n if (fileName === COLLECTION_ORDER_FILE) continue;\n\n const ext = this.fs.extname(fileName);\n const content = await this.fs.readFile(filePath);\n const relativePath = this.fs.relative(dirName, filePath);\n const slug = this.fs\n .normalizeSlug(relativePath, ext)\n .split(\"/\")\n .map((segment) => slugify(segment))\n .join(\"/\");\n\n if (ext === \".mdx\") {\n entries.push(this.buildMdxEntry(collectionName, slug, content));\n } else if (ext === \".json\") {\n entries.push(...this.buildJsonEntries(collectionName, slug, content));\n }\n }\n }\n\n private buildMdxEntry(collectionName: string, slug: string, content: string): ContentEntry {\n const parsed = parseMdx(content);\n return {\n collection: collectionName,\n slug,\n path: `/${collectionName}/${slug}`,\n body: parsed.body,\n data: parsed.data,\n };\n }\n\n private buildJsonEntries(collectionName: string, slug: string, content: string): ContentEntry[] {\n const parsed = parseJson(content);\n\n if (parsed.type === \"json-array\") {\n return parsed.entries.map((data, index) => {\n const entrySlug =\n typeof data[\"slug\"] === \"string\" ? slugify(data[\"slug\"]) : `${slug}/${index}`;\n return {\n collection: collectionName,\n slug: entrySlug,\n path: `/${collectionName}/${entrySlug}`,\n data,\n };\n });\n }\n\n return [{ collection: collectionName, slug, path: `/${collectionName}/${slug}`, data: parsed.data }];\n }\n\n private async readOrdering(orderPath: string): Promise<string[] | null> {\n if (!(await this.fs.exists(orderPath))) return null;\n\n try {\n const content = await this.fs.readFile(orderPath);\n const parsed: unknown = JSON.parse(content);\n if (Array.isArray(parsed)) return parsed as string[];\n } catch (error) {\n console.warn(`[Nextjs Studio] Failed to parse ordering file: ${orderPath}`, error);\n }\n return null;\n }\n\n private applyOrdering(entries: ContentEntry[], ordering: string[]): void {\n const orderMap = new Map(ordering.map((slug, index) => [slug, index]));\n entries.sort((a, b) => {\n const aIndex = orderMap.get(a.slug) ?? Infinity;\n const bIndex = orderMap.get(b.slug) ?? Infinity;\n return aIndex - bIndex;\n });\n }\n\n private detectCollectionType(entries: ContentEntry[]): Collection[\"type\"] {\n if (entries.length === 0) return \"mdx\";\n const first = entries[0];\n if (first.body !== undefined) return \"mdx\";\n if (entries.length === 1 && !first.slug.includes(\"/\")) return \"json-object\";\n return \"json-array\";\n }\n}\n","/**\n * @context Shared layer — constants at src/shared/constants.ts\n * @does Defines project-wide constants shared across core, CLI, and UI layers\n * @depends none\n * @do Add new shared constants here\n * @dont Import from CLI or UI; constants must be framework-agnostic\n */\n\nexport const CONTENTS_DIR = \"contents\";\nexport const CLI_PORT = 3030;\nexport const CONFIG_FILE = \"studio.config.ts\";\nexport const SUPPORTED_EXTENSIONS = [\".mdx\", \".json\"] as const;\nexport const COLLECTION_ORDER_FILE = \"collection.json\";\nexport const WATCHER_DEBOUNCE_MS = 5_000;\nexport const MEDIA_DIR = \"media\";\n\nexport const IMAGE_MIME_TYPES = [\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n \"image/svg+xml\",\n \"image/avif\",\n] as const;\n\nexport const VIDEO_MIME_TYPES = [\"video/mp4\", \"video/webm\", \"video/ogg\"] as const;\n\nexport const AUDIO_MIME_TYPES = [\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/wav\",\n \"audio/webm\",\n \"audio/aac\",\n \"audio/flac\",\n] as const;\n\nexport const MEDIA_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES, ...AUDIO_MIME_TYPES] as const;\n\nexport const IMAGE_EXTENSIONS = [\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".avif\"] as const;\nexport const VIDEO_EXTENSIONS = [\".mp4\", \".webm\", \".ogv\"] as const;\nexport const AUDIO_EXTENSIONS = [\".mp3\", \".ogg\", \".wav\", \".m4a\", \".aac\", \".flac\"] as const;\n","/**\n * @context Core layer — MDX parser/serializer at src/core/parsers/parser-mdx.ts\n * @does Parses .mdx content into frontmatter + body, and serializes them back to MDX strings\n * @depends none (gray-matter is an external dep)\n * @do Add MDX transform steps here; both parse and serialize live here intentionally\n * @dont Access the filesystem; import from CLI or UI; handle JSON content\n */\n\nimport matter from \"gray-matter\";\n\nexport interface ParsedMdx {\n data: Record<string, unknown>;\n body: string;\n}\n\nexport function parseMdx(content: string): ParsedMdx {\n const { data, content: body } = matter(content);\n return { data, body: body.trim() };\n}\n\nexport function serializeMdx(data: Record<string, unknown>, body: string): string {\n return matter.stringify(body, data);\n}\n","/**\n * @context Core layer — JSON parser at src/core/parsers/parser-json.ts\n * @does Parses JSON content strings into typed ParsedJson results (array or object)\n * @depends none\n * @do Extend ParsedJson variants here if new JSON structures are supported\n * @dont Access the filesystem; import from CLI or UI; contain serialization logic\n */\n\nexport interface ParsedJsonArray {\n type: \"json-array\";\n entries: Record<string, unknown>[];\n}\n\nexport interface ParsedJsonObject {\n type: \"json-object\";\n data: Record<string, unknown>;\n}\n\nexport type ParsedJson = ParsedJsonArray | ParsedJsonObject;\n\nexport function parseJson(content: string): ParsedJson {\n const parsed: unknown = JSON.parse(content);\n\n if (Array.isArray(parsed)) {\n return {\n type: \"json-array\",\n entries: parsed as Record<string, unknown>[],\n };\n }\n\n if (typeof parsed === \"object\" && parsed !== null) {\n return {\n type: \"json-object\",\n data: parsed as Record<string, unknown>,\n };\n }\n\n throw new Error(\"JSON content must be an array or object\");\n}\n","/**\n * @context Core layer — schema inferrer at src/core/schema-inferrer.ts\n * @does Infers a CollectionSchema from actual content entries when no manual schema is defined\n * @depends src/shared/types.ts, src/shared/fields.ts\n * @do Add new type detection heuristics here (e.g. color, phone)\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport type { ContentEntry } from \"../shared/types.js\";\nimport type { CollectionSchema, FieldDefinition, SelectOption } from \"../shared/fields.js\";\n\n// Value detector patterns\nconst RE_ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst RE_ISO_DATETIME =\n /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?$/;\nconst RE_EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst RE_URL = /^https?:\\/\\/.+/;\nconst LONG_TEXT_THRESHOLD = 200;\n\nfunction isISODate(value: string): boolean {\n return RE_ISO_DATE.test(value);\n}\n\nfunction isISODateTime(value: string): boolean {\n return RE_ISO_DATETIME.test(value);\n}\n\nfunction isEmail(value: string): boolean {\n return RE_EMAIL.test(value);\n}\n\nfunction isUrl(value: string): boolean {\n return RE_URL.test(value);\n}\n\nfunction inferStringField(name: string, strings: string[]): FieldDefinition {\n if (strings.every(isEmail)) return { name, type: \"email\" };\n if (strings.every(isUrl)) return { name, type: \"url\" };\n if (strings.every(isISODateTime)) return { name, type: \"date\", includeTime: true };\n if (strings.every(isISODate)) return { name, type: \"date\" };\n\n const isLong = strings.some((s) => s.length > LONG_TEXT_THRESHOLD || s.includes(\"\\n\"));\n return { name, type: isLong ? \"long-text\" : \"text\" };\n}\n\nfunction inferArrayField(name: string, items: unknown[]): FieldDefinition {\n if (items.length === 0) return { name, type: \"array\", itemFields: [] };\n\n if (items.every((item) => typeof item === \"string\")) {\n const unique = [...new Set(items as string[])].slice(0, 50);\n const options: SelectOption[] = unique.map((v) => ({ label: v, value: v }));\n return { name, type: \"multi-select\", options };\n }\n\n if (items.every((item) => typeof item === \"object\" && item !== null && !Array.isArray(item))) {\n return { name, type: \"array\", itemFields: inferFields(items as Record<string, unknown>[]) };\n }\n\n return { name, type: \"array\", itemFields: [] };\n}\n\nfunction inferFieldDefinition(name: string, values: unknown[]): FieldDefinition {\n const present = values.filter((v) => v !== null && v !== undefined);\n\n if (present.length === 0) return { name, type: \"text\" };\n if (present.every((v) => typeof v === \"boolean\")) return { name, type: \"boolean\" };\n\n if (present.every((v) => typeof v === \"number\")) {\n const format = present.every((v) => Number.isInteger(v)) ? \"integer\" : \"decimal\";\n return { name, type: \"number\", format };\n }\n\n if (present.every((v) => typeof v === \"string\")) {\n return inferStringField(name, present as string[]);\n }\n\n if (present.every((v) => Array.isArray(v))) {\n return inferArrayField(name, (present as unknown[][]).flat());\n }\n\n if (present.every((v) => typeof v === \"object\" && v !== null && !Array.isArray(v))) {\n return { name, type: \"object\", fields: inferFields(present as Record<string, unknown>[]) };\n }\n\n return { name, type: \"text\" };\n}\n\nfunction inferFields(rows: Record<string, unknown>[]): FieldDefinition[] {\n const keySet = new Set<string>(rows.flatMap((row) => Object.keys(row)));\n return Array.from(keySet).map((key) => inferFieldDefinition(key, rows.map((row) => row[key])));\n}\n\n/**\n * Infer a `CollectionSchema` from the data of a set of content entries.\n *\n * The result is a best-effort approximation — string fields that look like\n * emails, URLs, or ISO dates get the correct semantic type. Everything else\n * falls back to `text`.\n */\nexport function inferSchema(entries: ContentEntry[], collectionName: string): CollectionSchema {\n const rows = entries.map((entry) => entry.data as Record<string, unknown>);\n return { collection: collectionName, fields: inferFields(rows) };\n}\n","/**\n * @context Core layer — content store at src/core/content-store.ts\n * @does Manages a singleton ContentIndex; exposes loadContent() and getStore() for consumers\n * @depends src/core/indexer.ts, src/shared/fs-adapter.interface.ts, src/shared/types.ts\n * @do Use this as the single access point for in-memory indexed content\n * @dont Import from CLI or UI; instantiate FsAdapter here; contain parsing or I/O logic\n */\n\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport type { StudioConfig } from \"../shared/types.js\";\nimport { ContentIndex } from \"./indexer.js\";\n\nlet store: ContentIndex | null = null;\n\nexport function getStore(): ContentIndex {\n if (!store) {\n throw new Error(\"Content not loaded. Call loadContent() before querying.\");\n }\n return store;\n}\n\nexport async function loadContent(\n fsAdapter: IFsAdapter,\n config?: StudioConfig,\n): Promise<ContentIndex> {\n const index = new ContentIndex(fsAdapter);\n await index.build(config);\n store = index;\n return index;\n}\n","/**\r\n * @context Core layer — studio initializer at src/core/init.ts\r\n * @does Convenience function to initialize the content store for Node.js environments\r\n * @depends src/core/content-store.ts, src/cli/adapters/fs-adapter.ts\r\n * @do Use this as the entry point for Next.js and other Node.js consumers\r\n * @dont Import from UI; use in browser environments\r\n */\r\n\r\nimport path from \"node:path\";\r\nimport { FsAdapter } from \"../cli/adapters/fs-adapter.js\";\r\nimport { loadContent, getStore } from \"./content-store.js\";\r\nimport type { StudioConfig } from \"../shared/types.js\";\r\n\r\n/**\r\n * Initialize the content store from the filesystem.\r\n * Call this once before using `queryCollection()`.\r\n *\r\n * @param contentsDir - Path to the contents directory. Defaults to `./contents` relative to cwd.\r\n * @param config - Optional studio config for schemas and scripts.\r\n *\r\n * @example\r\n * ```ts\r\n * import { initStudio, queryCollection } from \"nextjs-studio\";\r\n *\r\n * await initStudio();\r\n * const posts = queryCollection(\"posts\").all();\r\n * ```\r\n */\r\nexport async function initStudio(\r\n contentsDir?: string,\r\n config?: StudioConfig,\r\n): Promise<void> {\r\n const dir = contentsDir ?? path.join(process.cwd(), \"contents\");\r\n await loadContent(new FsAdapter(dir), config);\r\n}\r\n\r\n/**\r\n * Returns true if the content store has been initialized.\r\n */\r\nexport function isStudioInitialized(): boolean {\r\n try {\r\n getStore();\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n","/**\n * @context CLI layer — filesystem adapter at src/cli/adapters/fs-adapter.ts\n * @does Implements IFsAdapter; abstracts all file read/write/list operations behind a single interface\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts\n * @do Add new I/O operations here; all file access must go through this adapter\n * @dont Import UI components, run HTTP requests, or contain business logic\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Dirent } from \"node:fs\";\nimport type { FileInfo, DirectoryFileEntry } from \"../../shared/types.js\";\nimport type { IFsAdapter } from \"../../shared/fs-adapter.interface.js\";\nimport { SUPPORTED_EXTENSIONS } from \"../../shared/constants.js\";\n\nexport class FsAdapter implements IFsAdapter {\n private readonly basePath: string;\n\n constructor(basePath: string) {\n this.basePath = path.resolve(basePath);\n }\n\n private resolve(...segments: string[]): string {\n return path.resolve(this.basePath, ...segments);\n }\n\n async readFile(filePath: string): Promise<string> {\n return fs.readFile(this.resolve(filePath), \"utf-8\");\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, content, \"utf-8\");\n }\n\n async deleteFile(filePath: string): Promise<void> {\n await fs.unlink(this.resolve(filePath));\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n await fs.access(this.resolve(filePath));\n return true;\n } catch {\n return false;\n }\n }\n\n async getStats(filePath: string): Promise<FileInfo> {\n const fullPath = this.resolve(filePath);\n const stats = await fs.stat(fullPath);\n return { path: filePath, size: stats.size, modifiedAt: stats.mtime };\n }\n\n async listFiles(dirPath: string, extensions?: readonly string[]): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n const filterExts = extensions ?? SUPPORTED_EXTENSIONS;\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isFile() && filterExts.some((ext) => entry.name.endsWith(ext)))\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async listDirectories(dirPath: string): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async readBuffer(filePath: string): Promise<Buffer> {\n return fs.readFile(this.resolve(filePath));\n }\n\n async writeBuffer(filePath: string, data: Buffer): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, data);\n }\n\n async listAllFiles(dirPath: string): Promise<DirectoryFileEntry[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const results: DirectoryFileEntry[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const relativePath = this.join(dirPath, entry.name);\n const stats = await fs.stat(this.resolve(relativePath));\n results.push({ name: entry.name, relativePath, size: stats.size, modifiedAt: stats.mtime });\n }\n return results;\n }\n\n join(...segments: string[]): string {\n return path.join(...segments);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n extname(filePath: string): string {\n return path.extname(filePath);\n }\n\n relative(from: string, to: string): string {\n return path.relative(from, to);\n }\n\n normalizeSlug(relativePath: string, ext: string): string {\n return relativePath.replace(ext, \"\").split(path.sep).join(\"/\");\n }\n}\n","/**\n * @context Shared layer — field label utilities at src/shared/field-utils.ts\n * @does Resolves human-readable labels for field definitions and raw key strings\n * @depends src/shared/fields.ts\n * @do Add field-related utility functions here\n * @dont Import from CLI or UI; contain field type definitions or schema logic\n */\n\nimport type { BaseField } from \"./fields.js\";\n\n/**\n * Resolve the human-readable label for a field.\n *\n * When the field definition has an explicit `label`, that is returned as-is.\n * Otherwise the `name` (camelCase / kebab-case / snake_case) is converted to Title Case:\n *\n * @example\n * fieldLabel({ name: \"siteName\", type: \"text\" }) // \"Site Name\"\n * fieldLabel({ name: \"created_at\", type: \"date\" }) // \"Created At\"\n * fieldLabel({ name: \"bio\", type: \"long-text\", label: \"About\" }) // \"About\"\n */\nexport function fieldLabel(field: Pick<BaseField, \"name\" | \"label\">): string {\n if (field.label) return field.label;\n return field.name\n .replace(/[-_](.)/g, (_, c: string) => ` ${c.toUpperCase()}`)\n .replace(/([A-Z])/g, \" $1\")\n .trim()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Resolve the label for a raw key string (no field definition available).\n * Useful for dynamic keys that have no schema entry.\n */\nexport function keyLabel(name: string): string {\n return fieldLabel({ name });\n}\n"],"mappings":"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","names":["path","path"]}
|
|
1
|
+
{"version":3,"sources":["../../src/core/query-builder.ts","../../src/core/indexer.ts","../../src/shared/constants.ts","../../src/core/parsers/parser-mdx.ts","../../src/core/parsers/parser-json.ts","../../src/core/schema-inferrer.ts","../../src/core/content-store.ts","../../src/core/init.ts","../../src/cli/adapters/fs-adapter.ts","../../src/shared/field-utils.ts"],"sourcesContent":["/**\n * @context Core layer — query builder at src/core/query-builder.ts\n * @does Provides a fluent API to filter, sort, and paginate content entries from a collection\n * @depends src/shared/types.ts, src/core/content-store.ts\n * @do Add new query capabilities here (e.g. search, groupBy)\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport { filter, orderBy, get, slice } from \"lodash-es\";\nimport type { QueryOptions } from \"../shared/types.js\";\nimport type { CollectionTypeMap } from \"../shared/types.js\";\nimport { getStore } from \"./content-store.js\";\n\n/**\n * Fluent query builder for content collections.\n *\n * ```ts\n * const posts = queryCollection(\"blog\")\n * .where({ published: true })\n * .sort(\"date\", \"desc\")\n * .limit(10)\n * .all();\n * ```\n *\n * Supports dot notation for nested properties:\n * ```ts\n * queryCollection(\"pages\").where({ \"hero.title\": \"Welcome\" }).all();\n * ```\n */\nexport class QueryBuilder<T = Record<string, unknown>> {\n private readonly collectionName: string;\n private options: QueryOptions = {};\n\n constructor(collection: string) {\n this.collectionName = collection;\n }\n\n where(conditions: Record<string, unknown>): this {\n this.options.where = { ...this.options.where, ...conditions };\n return this;\n }\n\n sort(field: string, order: \"asc\" | \"desc\" = \"asc\"): this {\n this.options.sort = { field, order };\n return this;\n }\n\n limit(count: number): this {\n this.options.limit = count;\n return this;\n }\n\n offset(count: number): this {\n this.options.offset = count;\n return this;\n }\n\n all(): T[] {\n let entries = [...getStore().getCollection(this.collectionName)];\n\n if (this.options.where) {\n const conditions = this.options.where;\n entries = filter(entries, (entry) =>\n Object.entries(conditions).every(([key, value]) => get(entry.data, key) === value),\n );\n }\n\n if (this.options.sort) {\n const { field, order } = this.options.sort;\n entries = orderBy(entries, [(entry) => get(entry.data, field)], [order]);\n }\n\n const start = this.options.offset ?? 0;\n const end = this.options.limit ? start + this.options.limit : undefined;\n return slice(entries, start, end).map((e) => e.data as unknown as T);\n }\n\n first(): T | undefined {\n return this.limit(1).all()[0];\n }\n\n count(): number {\n return this.all().length;\n }\n}\n\n/**\n * Entry point for querying a content collection.\n */\nexport function queryCollection<K extends keyof CollectionTypeMap>(\n name: K,\n): QueryBuilder<CollectionTypeMap[K]>;\nexport function queryCollection(name: string): QueryBuilder<Record<string, unknown>>;\nexport function queryCollection(name: string): QueryBuilder {\n return new QueryBuilder(name);\n}\n","/**\n * @context Core layer — content indexer at src/core/indexer.ts\n * @does Scans the contents directory, parses MDX/JSON files, and builds an in-memory index\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts, src/core/parsers/, src/core/schema-inferrer.ts\n * @do Add new file type handling here; extend indexCollection for new collection behaviors\n * @dont Import from CLI or UI; instantiate FsAdapter; access the filesystem directly\n */\n\nimport slugify from \"@sindresorhus/slugify\";\nimport type { CollectionSchema } from \"../shared/fields.js\";\nimport type { ContentEntry, Collection, StudioConfig } from \"../shared/types.js\";\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport { COLLECTION_ORDER_FILE } from \"../shared/constants.js\";\nimport { parseMdx } from \"./parsers/parser-mdx.js\";\nimport { parseJson } from \"./parsers/parser-json.js\";\nimport { inferSchema } from \"./schema-inferrer.js\";\n\nexport class ContentIndex {\n private readonly entries = new Map<string, ContentEntry[]>();\n private readonly collections = new Map<string, Collection>();\n private readonly fs: IFsAdapter;\n\n constructor(fsAdapter: IFsAdapter) {\n this.fs = fsAdapter;\n }\n\n async build(config?: StudioConfig): Promise<void> {\n this.clear();\n const dirs = await this.fs.listDirectories(\".\");\n\n for (const dir of dirs) {\n const dirName = this.fs.basename(dir);\n const collectionName = slugify(dirName);\n const collectionConfig = config?.collections?.[collectionName];\n await this.indexCollection(dirName, collectionName, collectionConfig?.schema);\n }\n }\n\n getCollection(name: string): ContentEntry[] {\n return this.entries.get(name) ?? [];\n }\n\n getCollections(): Collection[] {\n return Array.from(this.collections.values());\n }\n\n clear(): void {\n this.entries.clear();\n this.collections.clear();\n }\n\n private async indexCollection(\n dirName: string,\n collectionName: string,\n manualSchema?: CollectionSchema,\n ): Promise<void> {\n const entries: ContentEntry[] = [];\n await this.scanDir(dirName, collectionName, dirName, entries);\n\n const orderPath = this.fs.join(dirName, COLLECTION_ORDER_FILE);\n const ordering = await this.readOrdering(orderPath);\n if (ordering) {\n this.applyOrdering(entries, ordering);\n }\n\n const schema = manualSchema ?? inferSchema(entries, collectionName);\n\n this.entries.set(collectionName, entries);\n this.collections.set(collectionName, {\n name: collectionName,\n type: this.detectCollectionType(entries),\n count: entries.length,\n basePath: dirName,\n schema,\n });\n }\n\n private async scanDir(\n dirName: string,\n collectionName: string,\n dirPath: string,\n entries: ContentEntry[],\n ): Promise<void> {\n const subDirs = await this.fs.listDirectories(dirPath);\n for (const subDir of subDirs) {\n await this.scanDir(dirName, collectionName, subDir, entries);\n }\n\n const files = await this.fs.listFiles(dirPath);\n for (const filePath of files) {\n const fileName = this.fs.basename(filePath);\n if (fileName === COLLECTION_ORDER_FILE) continue;\n\n const ext = this.fs.extname(fileName);\n const content = await this.fs.readFile(filePath);\n const relativePath = this.fs.relative(dirName, filePath);\n const slug = this.fs\n .normalizeSlug(relativePath, ext)\n .split(\"/\")\n .map((segment) => slugify(segment))\n .join(\"/\");\n\n if (ext === \".mdx\") {\n entries.push(this.buildMdxEntry(collectionName, slug, content));\n } else if (ext === \".json\") {\n entries.push(...this.buildJsonEntries(collectionName, slug, content));\n }\n }\n }\n\n private buildMdxEntry(collectionName: string, slug: string, content: string): ContentEntry {\n const parsed = parseMdx(content);\n return {\n collection: collectionName,\n slug,\n path: `/${collectionName}/${slug}`,\n body: parsed.body,\n data: parsed.data,\n };\n }\n\n private buildJsonEntries(collectionName: string, slug: string, content: string): ContentEntry[] {\n const parsed = parseJson(content);\n\n if (parsed.type === \"json-array\") {\n return parsed.entries.map((data, index) => {\n const entrySlug =\n typeof data[\"slug\"] === \"string\" ? slugify(data[\"slug\"]) : `${slug}/${index}`;\n return {\n collection: collectionName,\n slug: entrySlug,\n path: `/${collectionName}/${entrySlug}`,\n data,\n };\n });\n }\n\n return [{ collection: collectionName, slug, path: `/${collectionName}/${slug}`, data: parsed.data }];\n }\n\n private async readOrdering(orderPath: string): Promise<string[] | null> {\n if (!(await this.fs.exists(orderPath))) return null;\n\n try {\n const content = await this.fs.readFile(orderPath);\n const parsed: unknown = JSON.parse(content);\n if (Array.isArray(parsed)) return parsed as string[];\n } catch (error) {\n console.warn(`[Nextjs Studio] Failed to parse ordering file: ${orderPath}`, error);\n }\n return null;\n }\n\n private applyOrdering(entries: ContentEntry[], ordering: string[]): void {\n const orderMap = new Map(ordering.map((slug, index) => [slug, index]));\n entries.sort((a, b) => {\n const aIndex = orderMap.get(a.slug) ?? Infinity;\n const bIndex = orderMap.get(b.slug) ?? Infinity;\n return aIndex - bIndex;\n });\n }\n\n private detectCollectionType(entries: ContentEntry[]): Collection[\"type\"] {\n if (entries.length === 0) return \"mdx\";\n const first = entries[0];\n if (first.body !== undefined) return \"mdx\";\n if (entries.length === 1 && !first.slug.includes(\"/\")) return \"json-object\";\n return \"json-array\";\n }\n}\n","/**\n * @context Shared layer — constants at src/shared/constants.ts\n * @does Defines project-wide constants shared across core, CLI, and UI layers\n * @depends none\n * @do Add new shared constants here\n * @dont Import from CLI or UI; constants must be framework-agnostic\n */\n\nexport const CONTENTS_DIR = \"contents\";\nexport const CLI_PORT = 3030;\nexport const CONFIG_FILE = \"studio.config.ts\";\nexport const SUPPORTED_EXTENSIONS = [\".mdx\", \".json\"] as const;\nexport const COLLECTION_ORDER_FILE = \"collection.json\";\nexport const WATCHER_DEBOUNCE_MS = 5_000;\nexport const MEDIA_DIR = \"media\";\n\nexport const IMAGE_MIME_TYPES = [\n \"image/png\",\n \"image/jpeg\",\n \"image/gif\",\n \"image/webp\",\n \"image/svg+xml\",\n \"image/avif\",\n] as const;\n\nexport const VIDEO_MIME_TYPES = [\"video/mp4\", \"video/webm\", \"video/ogg\"] as const;\n\nexport const AUDIO_MIME_TYPES = [\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/wav\",\n \"audio/webm\",\n \"audio/aac\",\n \"audio/flac\",\n] as const;\n\nexport const MEDIA_MIME_TYPES = [...IMAGE_MIME_TYPES, ...VIDEO_MIME_TYPES, ...AUDIO_MIME_TYPES] as const;\n\nexport const IMAGE_EXTENSIONS = [\".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".avif\"] as const;\nexport const VIDEO_EXTENSIONS = [\".mp4\", \".webm\", \".ogv\"] as const;\nexport const AUDIO_EXTENSIONS = [\".mp3\", \".ogg\", \".wav\", \".m4a\", \".aac\", \".flac\"] as const;\n","/**\n * @context Core layer — MDX parser/serializer at src/core/parsers/parser-mdx.ts\n * @does Parses .mdx content into frontmatter + body, and serializes them back to MDX strings\n * @depends none (gray-matter is an external dep)\n * @do Add MDX transform steps here; both parse and serialize live here intentionally\n * @dont Access the filesystem; import from CLI or UI; handle JSON content\n */\n\nimport matter from \"gray-matter\";\n\nexport interface ParsedMdx {\n data: Record<string, unknown>;\n body: string;\n}\n\nexport function parseMdx(content: string): ParsedMdx {\n const { data, content: body } = matter(content);\n return { data, body: body.trim() };\n}\n\nexport function serializeMdx(data: Record<string, unknown>, body: string): string {\n return matter.stringify(body, data);\n}\n","/**\n * @context Core layer — JSON parser at src/core/parsers/parser-json.ts\n * @does Parses JSON content strings into typed ParsedJson results (array or object)\n * @depends none\n * @do Extend ParsedJson variants here if new JSON structures are supported\n * @dont Access the filesystem; import from CLI or UI; contain serialization logic\n */\n\nexport interface ParsedJsonArray {\n type: \"json-array\";\n entries: Record<string, unknown>[];\n}\n\nexport interface ParsedJsonObject {\n type: \"json-object\";\n data: Record<string, unknown>;\n}\n\nexport type ParsedJson = ParsedJsonArray | ParsedJsonObject;\n\nexport function parseJson(content: string): ParsedJson {\n const parsed: unknown = JSON.parse(content);\n\n if (Array.isArray(parsed)) {\n return {\n type: \"json-array\",\n entries: parsed as Record<string, unknown>[],\n };\n }\n\n if (typeof parsed === \"object\" && parsed !== null) {\n return {\n type: \"json-object\",\n data: parsed as Record<string, unknown>,\n };\n }\n\n throw new Error(\"JSON content must be an array or object\");\n}\n","/**\n * @context Core layer — schema inferrer at src/core/schema-inferrer.ts\n * @does Infers a CollectionSchema from actual content entries when no manual schema is defined\n * @depends src/shared/types.ts, src/shared/fields.ts\n * @do Add new type detection heuristics here (e.g. color, phone)\n * @dont Import from CLI or UI; access the filesystem; perform I/O\n */\n\nimport type { ContentEntry } from \"../shared/types.js\";\nimport type { CollectionSchema, FieldDefinition, SelectOption } from \"../shared/fields.js\";\n\n// Value detector patterns\nconst RE_ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst RE_ISO_DATETIME =\n /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?$/;\nconst RE_EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst RE_URL = /^https?:\\/\\/.+/;\nconst LONG_TEXT_THRESHOLD = 200;\n\nfunction isISODate(value: string): boolean {\n return RE_ISO_DATE.test(value);\n}\n\nfunction isISODateTime(value: string): boolean {\n return RE_ISO_DATETIME.test(value);\n}\n\nfunction isEmail(value: string): boolean {\n return RE_EMAIL.test(value);\n}\n\nfunction isUrl(value: string): boolean {\n return RE_URL.test(value);\n}\n\nfunction inferStringField(name: string, strings: string[]): FieldDefinition {\n if (strings.every(isEmail)) return { name, type: \"email\" };\n if (strings.every(isUrl)) return { name, type: \"url\" };\n if (strings.every(isISODateTime)) return { name, type: \"date\", includeTime: true };\n if (strings.every(isISODate)) return { name, type: \"date\" };\n\n const isLong = strings.some((s) => s.length > LONG_TEXT_THRESHOLD || s.includes(\"\\n\"));\n return { name, type: isLong ? \"long-text\" : \"text\" };\n}\n\nfunction inferArrayField(name: string, items: unknown[]): FieldDefinition {\n if (items.length === 0) return { name, type: \"array\", itemFields: [] };\n\n if (items.every((item) => typeof item === \"string\")) {\n const unique = [...new Set(items as string[])].slice(0, 50);\n const options: SelectOption[] = unique.map((v) => ({ label: v, value: v }));\n return { name, type: \"multi-select\", options };\n }\n\n if (items.every((item) => typeof item === \"object\" && item !== null && !Array.isArray(item))) {\n return { name, type: \"array\", itemFields: inferFields(items as Record<string, unknown>[]) };\n }\n\n return { name, type: \"array\", itemFields: [] };\n}\n\nfunction inferFieldDefinition(name: string, values: unknown[]): FieldDefinition {\n const present = values.filter((v) => v !== null && v !== undefined);\n\n if (present.length === 0) return { name, type: \"text\" };\n if (present.every((v) => typeof v === \"boolean\")) return { name, type: \"boolean\" };\n\n if (present.every((v) => typeof v === \"number\")) {\n const format = present.every((v) => Number.isInteger(v)) ? \"integer\" : \"decimal\";\n return { name, type: \"number\", format };\n }\n\n if (present.every((v) => typeof v === \"string\")) {\n return inferStringField(name, present as string[]);\n }\n\n if (present.every((v) => Array.isArray(v))) {\n return inferArrayField(name, (present as unknown[][]).flat());\n }\n\n if (present.every((v) => typeof v === \"object\" && v !== null && !Array.isArray(v))) {\n return { name, type: \"object\", fields: inferFields(present as Record<string, unknown>[]) };\n }\n\n return { name, type: \"text\" };\n}\n\nfunction inferFields(rows: Record<string, unknown>[]): FieldDefinition[] {\n const keySet = new Set<string>(rows.flatMap((row) => Object.keys(row)));\n return Array.from(keySet).map((key) => inferFieldDefinition(key, rows.map((row) => row[key])));\n}\n\n/**\n * Infer a `CollectionSchema` from the data of a set of content entries.\n *\n * The result is a best-effort approximation — string fields that look like\n * emails, URLs, or ISO dates get the correct semantic type. Everything else\n * falls back to `text`.\n */\nexport function inferSchema(entries: ContentEntry[], collectionName: string): CollectionSchema {\n const rows = entries.map((entry) => entry.data as Record<string, unknown>);\n return { collection: collectionName, fields: inferFields(rows) };\n}\n","/**\n * @context Core layer — content store at src/core/content-store.ts\n * @does Manages a singleton ContentIndex; exposes loadContent() and getStore() for consumers\n * @depends src/core/indexer.ts, src/shared/fs-adapter.interface.ts, src/shared/types.ts\n * @do Use this as the single access point for in-memory indexed content\n * @dont Import from CLI or UI; instantiate FsAdapter here; contain parsing or I/O logic\n */\n\nimport type { IFsAdapter } from \"../shared/fs-adapter.interface.js\";\nimport type { StudioConfig } from \"../shared/types.js\";\nimport { ContentIndex } from \"./indexer.js\";\n\nlet store: ContentIndex | null = null;\n\nexport function getStore(): ContentIndex {\n if (!store) {\n throw new Error(\"Content not loaded. Call loadContent() before querying.\");\n }\n return store;\n}\n\nexport async function loadContent(\n fsAdapter: IFsAdapter,\n config?: StudioConfig,\n): Promise<ContentIndex> {\n const index = new ContentIndex(fsAdapter);\n await index.build(config);\n store = index;\n return index;\n}\n","/**\r\n * @context Core layer — studio initializer at src/core/init.ts\r\n * @does Convenience function to initialize the content store for Node.js environments\r\n * @depends src/core/content-store.ts, src/cli/adapters/fs-adapter.ts\r\n * @do Use this as the entry point for Next.js and other Node.js consumers\r\n * @dont Import from UI; use in browser environments\r\n */\r\n\r\nimport path from \"node:path\";\r\nimport { FsAdapter } from \"../cli/adapters/fs-adapter.js\";\r\nimport { loadContent, getStore } from \"./content-store.js\";\r\nimport type { StudioConfig } from \"../shared/types.js\";\r\n\r\n/**\r\n * Initialize the content store from the filesystem.\r\n * Call this once before using `queryCollection()`.\r\n *\r\n * @param contentsDir - Path to the contents directory. Defaults to `./contents` relative to cwd.\r\n * @param config - Optional studio config for schemas and scripts.\r\n *\r\n * @example\r\n * ```ts\r\n * import { initStudio, queryCollection } from \"nextjs-studio\";\r\n *\r\n * await initStudio();\r\n * const posts = queryCollection(\"posts\").all();\r\n * ```\r\n */\r\nexport async function initStudio(\r\n contentsDir?: string,\r\n config?: StudioConfig,\r\n): Promise<void> {\r\n const dir = contentsDir ?? path.join(process.cwd(), \"contents\");\r\n await loadContent(new FsAdapter(dir), config);\r\n}\r\n\r\n/**\r\n * Returns true if the content store has been initialized.\r\n */\r\nexport function isStudioInitialized(): boolean {\r\n try {\r\n getStore();\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Ensures the content store is initialized. Safe to call multiple times — only\r\n * initializes once. Ideal for use at the top of Next.js server components and\r\n * `generateStaticParams` / `generateMetadata` functions.\r\n *\r\n * @param contentsDir - Path to the contents directory. Defaults to `./contents` relative to cwd.\r\n * @param config - Optional studio config for schemas and scripts.\r\n *\r\n * @example\r\n * ```ts\r\n * import { ensureContentLoaded, queryCollection } from \"nextjs-studio\";\r\n *\r\n * export default async function Page() {\r\n * await ensureContentLoaded();\r\n * const posts = queryCollection(\"posts\");\r\n * // ...\r\n * }\r\n * ```\r\n */\r\nexport async function ensureContentLoaded(\r\n contentsDir?: string,\r\n config?: StudioConfig,\r\n): Promise<void> {\r\n if (!isStudioInitialized()) {\r\n await initStudio(contentsDir, config);\r\n }\r\n}\r\n","/**\n * @context CLI layer — filesystem adapter at src/cli/adapters/fs-adapter.ts\n * @does Implements IFsAdapter; abstracts all file read/write/list operations behind a single interface\n * @depends src/shared/types.ts, src/shared/constants.ts, src/shared/fs-adapter.interface.ts\n * @do Add new I/O operations here; all file access must go through this adapter\n * @dont Import UI components, run HTTP requests, or contain business logic\n */\n\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Dirent } from \"node:fs\";\nimport type { FileInfo, DirectoryFileEntry } from \"../../shared/types.js\";\nimport type { IFsAdapter } from \"../../shared/fs-adapter.interface.js\";\nimport { SUPPORTED_EXTENSIONS } from \"../../shared/constants.js\";\n\nexport class FsAdapter implements IFsAdapter {\n private readonly basePath: string;\n\n constructor(basePath: string) {\n this.basePath = path.resolve(basePath);\n }\n\n private resolve(...segments: string[]): string {\n return path.resolve(this.basePath, ...segments);\n }\n\n async readFile(filePath: string): Promise<string> {\n return fs.readFile(this.resolve(filePath), \"utf-8\");\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, content, \"utf-8\");\n }\n\n async deleteFile(filePath: string): Promise<void> {\n await fs.unlink(this.resolve(filePath));\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n await fs.access(this.resolve(filePath));\n return true;\n } catch {\n return false;\n }\n }\n\n async getStats(filePath: string): Promise<FileInfo> {\n const fullPath = this.resolve(filePath);\n const stats = await fs.stat(fullPath);\n return { path: filePath, size: stats.size, modifiedAt: stats.mtime };\n }\n\n async listFiles(dirPath: string, extensions?: readonly string[]): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n const filterExts = extensions ?? SUPPORTED_EXTENSIONS;\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isFile() && filterExts.some((ext) => entry.name.endsWith(ext)))\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async listDirectories(dirPath: string): Promise<string[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n return entries\n .filter((entry) => entry.isDirectory())\n .map((entry) => this.join(dirPath, entry.name));\n }\n\n async readBuffer(filePath: string): Promise<Buffer> {\n return fs.readFile(this.resolve(filePath));\n }\n\n async writeBuffer(filePath: string, data: Buffer): Promise<void> {\n const fullPath = this.resolve(filePath);\n await fs.mkdir(path.dirname(fullPath), { recursive: true });\n await fs.writeFile(fullPath, data);\n }\n\n async listAllFiles(dirPath: string): Promise<DirectoryFileEntry[]> {\n const fullPath = this.resolve(dirPath);\n\n let entries: Dirent[];\n try {\n entries = await fs.readdir(fullPath, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const results: DirectoryFileEntry[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const relativePath = this.join(dirPath, entry.name);\n const stats = await fs.stat(this.resolve(relativePath));\n results.push({ name: entry.name, relativePath, size: stats.size, modifiedAt: stats.mtime });\n }\n return results;\n }\n\n join(...segments: string[]): string {\n return path.join(...segments);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n extname(filePath: string): string {\n return path.extname(filePath);\n }\n\n relative(from: string, to: string): string {\n return path.relative(from, to);\n }\n\n normalizeSlug(relativePath: string, ext: string): string {\n return relativePath.replace(ext, \"\").split(path.sep).join(\"/\");\n }\n}\n","/**\n * @context Shared layer — field label utilities at src/shared/field-utils.ts\n * @does Resolves human-readable labels for field definitions and raw key strings\n * @depends src/shared/fields.ts\n * @do Add field-related utility functions here\n * @dont Import from CLI or UI; contain field type definitions or schema logic\n */\n\nimport type { BaseField } from \"./fields.js\";\n\n/**\n * Resolve the human-readable label for a field.\n *\n * When the field definition has an explicit `label`, that is returned as-is.\n * Otherwise the `name` (camelCase / kebab-case / snake_case) is converted to Title Case:\n *\n * @example\n * fieldLabel({ name: \"siteName\", type: \"text\" }) // \"Site Name\"\n * fieldLabel({ name: \"created_at\", type: \"date\" }) // \"Created At\"\n * fieldLabel({ name: \"bio\", type: \"long-text\", label: \"About\" }) // \"About\"\n */\nexport function fieldLabel(field: Pick<BaseField, \"name\" | \"label\">): string {\n if (field.label) return field.label;\n return field.name\n .replace(/[-_](.)/g, (_, c: string) => ` ${c.toUpperCase()}`)\n .replace(/([A-Z])/g, \" $1\")\n .trim()\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n}\n\n/**\n * Resolve the label for a raw key string (no field definition available).\n * Useful for dynamic keys that have no schema entry.\n */\nexport function keyLabel(name: string): string {\n return fieldLabel({ name });\n}\n"],"mappings":"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","names":["path","path"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextjs-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A Git-based, local-first CMS for Next.js projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nextjs",
|
|
@@ -97,4 +97,4 @@
|
|
|
97
97
|
"gray-matter": "^4.0.3",
|
|
98
98
|
"lodash-es": "^4.17.23"
|
|
99
99
|
}
|
|
100
|
-
}
|
|
100
|
+
}
|