create-krantho 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/dist/export.d.ts +2 -0
- package/dist/export.js +113 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +103 -0
- package/dist/prompts.d.ts +14 -0
- package/dist/prompts.js +91 -0
- package/dist/scaffold.d.ts +2 -0
- package/dist/scaffold.js +21 -0
- package/dist/templates/common.d.ts +2 -0
- package/dist/templates/common.js +98 -0
- package/dist/templates/content.d.ts +2 -0
- package/dist/templates/content.js +99 -0
- package/dist/templates/nextjs.d.ts +2 -0
- package/dist/templates/nextjs.js +539 -0
- package/dist/templates/sveltekit.d.ts +2 -0
- package/dist/templates/sveltekit.js +472 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +5 -0
- package/package.json +32 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Descoped
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/export.d.ts
ADDED
package/dist/export.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Simple YAML serializer for frontmatter (flat key-value only).
|
|
6
|
+
* Values are JSON.stringify'd, which handles quoting and escaping for strings.
|
|
7
|
+
* Limitation: nested objects/arrays are serialized as inline JSON, not block YAML.
|
|
8
|
+
*/
|
|
9
|
+
function toYaml(data) {
|
|
10
|
+
return Object.entries(data)
|
|
11
|
+
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
|
12
|
+
.join('\n');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse CLI arguments, exiting with a usage message when required options are
|
|
16
|
+
* missing. Keeping argument parsing out of `main()` shrinks main's mixed
|
|
17
|
+
* responsibility list and makes arg validation testable in isolation.
|
|
18
|
+
*/
|
|
19
|
+
function parseExportArgs(args) {
|
|
20
|
+
const urlIndex = args.indexOf('--url');
|
|
21
|
+
const outputIndex = args.indexOf('--output');
|
|
22
|
+
const baseUrl = urlIndex >= 0 ? args[urlIndex + 1] : 'http://localhost:4002';
|
|
23
|
+
const outputDir = outputIndex >= 0 ? args[outputIndex + 1] : './export-bundle';
|
|
24
|
+
if (!baseUrl || !outputDir) {
|
|
25
|
+
console.error('Usage: krantho-export --url <server-url> --output <directory>');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return { baseUrl, outputDir };
|
|
29
|
+
}
|
|
30
|
+
/** Fetch the export bundle from the running krantho server. Exits on failure. */
|
|
31
|
+
async function fetchBundle(baseUrl) {
|
|
32
|
+
const res = await fetch(`${baseUrl}/api/export/bundle`);
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
console.error(`Export failed: ${res.status} ${res.statusText}`);
|
|
35
|
+
const body = await res.text();
|
|
36
|
+
console.error(body);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
return (await res.json());
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Write the content items from a bundle as `.md` files with reconstructed
|
|
43
|
+
* YAML frontmatter. Each item lands at `{outputDir}/content/{collection}/{slug}.md`.
|
|
44
|
+
*/
|
|
45
|
+
async function writeExportedContent(outputDir, bundle) {
|
|
46
|
+
for (const [collection, items] of Object.entries(bundle.content)) {
|
|
47
|
+
for (const [slug, item] of Object.entries(items)) {
|
|
48
|
+
const filePath = join(outputDir, 'content', collection, `${slug}.md`);
|
|
49
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
50
|
+
// The locale is always written: the file then lands in its own locale on
|
|
51
|
+
// any site, whatever default locale that site has.
|
|
52
|
+
const frontmatter = { ...item.fields, locale: item.locale };
|
|
53
|
+
if (item.status !== 'published')
|
|
54
|
+
frontmatter['status'] = item.status;
|
|
55
|
+
const yaml = toYaml(frontmatter);
|
|
56
|
+
const content = `---\n${yaml}---\n${item.body}`;
|
|
57
|
+
await writeFile(filePath, content, 'utf-8');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Write the bundle's media metadata and runtime schema extensions as JSON
|
|
63
|
+
* sidecars next to the exported content. Skipped when the bundle has none.
|
|
64
|
+
*/
|
|
65
|
+
async function writeExportedSidecars(outputDir, bundle) {
|
|
66
|
+
if (bundle.media.items.length > 0) {
|
|
67
|
+
const mediaPath = join(outputDir, 'media', 'metadata.json');
|
|
68
|
+
await mkdir(dirname(mediaPath), { recursive: true });
|
|
69
|
+
await writeFile(mediaPath, JSON.stringify(bundle.media, null, 2), 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
if (Object.keys(bundle.schemas).length > 0) {
|
|
72
|
+
const schemaPath = join(outputDir, 'config', 'schemas.json');
|
|
73
|
+
await mkdir(dirname(schemaPath), { recursive: true });
|
|
74
|
+
await writeFile(schemaPath, JSON.stringify(bundle.schemas, null, 2), 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Reconstruct and write the content manifest from the bundle's checksum map.
|
|
79
|
+
* The manifest is what the migration system uses on re-import to detect
|
|
80
|
+
* changed vs. unchanged files.
|
|
81
|
+
*/
|
|
82
|
+
async function writeManifest(outputDir, bundle) {
|
|
83
|
+
const manifest = {
|
|
84
|
+
version: bundle.version,
|
|
85
|
+
generatedAt: bundle.timestamp,
|
|
86
|
+
files: Object.fromEntries(Object.entries(bundle.checksums).map(([path, checksum]) => [
|
|
87
|
+
`${path}.md`,
|
|
88
|
+
{ checksum, size: 0 },
|
|
89
|
+
])),
|
|
90
|
+
};
|
|
91
|
+
await writeFile(join(outputDir, 'content', 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Export content from a running krantho server to local files.
|
|
95
|
+
*
|
|
96
|
+
* Usage: krantho-export --url http://localhost:4002 --output ./content
|
|
97
|
+
*/
|
|
98
|
+
async function main() {
|
|
99
|
+
const { baseUrl, outputDir } = parseExportArgs(process.argv.slice(2));
|
|
100
|
+
console.log(`Exporting from ${baseUrl} to ${outputDir}...`);
|
|
101
|
+
const bundle = await fetchBundle(baseUrl);
|
|
102
|
+
await writeExportedContent(outputDir, bundle);
|
|
103
|
+
await writeExportedSidecars(outputDir, bundle);
|
|
104
|
+
await writeManifest(outputDir, bundle);
|
|
105
|
+
const totalItems = Object.values(bundle.content).reduce((sum, col) => sum + Object.keys(col).length, 0);
|
|
106
|
+
console.log(`Exported ${totalItems} content items, ${bundle.media.items.length} media items, ${Object.keys(bundle.schemas).length} schemas`);
|
|
107
|
+
console.log(`Manifest version: ${bundle.version}`);
|
|
108
|
+
console.log(`Output: ${outputDir}`);
|
|
109
|
+
}
|
|
110
|
+
main().catch((err) => {
|
|
111
|
+
console.error('Export failed:', err);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
});
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { runPrompts } from './prompts.js';
|
|
6
|
+
import { generateFiles } from './scaffold.js';
|
|
7
|
+
/**
|
|
8
|
+
* Validate the target project directory. Exits the process with a message if
|
|
9
|
+
* the directory already exists and is non-empty — krantho refuses to scaffold
|
|
10
|
+
* on top of existing files to avoid clobbering the user's work.
|
|
11
|
+
*/
|
|
12
|
+
function validateProjectDir(projectDir, name) {
|
|
13
|
+
if (!existsSync(projectDir))
|
|
14
|
+
return;
|
|
15
|
+
let entries = [];
|
|
16
|
+
try {
|
|
17
|
+
entries = readdirSync(projectDir);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// Directory exists but isn't readable — treat as "empty" and continue.
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (entries.length > 0) {
|
|
24
|
+
console.error(`\nError: Directory "${name}" is not empty.`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Write every scaffold file to disk, creating parent directories as needed.
|
|
30
|
+
*/
|
|
31
|
+
function scaffoldProject(projectDir, options) {
|
|
32
|
+
mkdirSync(projectDir, { recursive: true });
|
|
33
|
+
const files = generateFiles(options);
|
|
34
|
+
for (const [filePath, content] of files) {
|
|
35
|
+
const fullPath = resolve(projectDir, filePath);
|
|
36
|
+
mkdirSync(resolve(fullPath, '..'), { recursive: true });
|
|
37
|
+
writeFileSync(fullPath, content, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Install dependencies using the user-selected package manager. Failures are
|
|
42
|
+
* non-fatal — print the manual command and continue.
|
|
43
|
+
*/
|
|
44
|
+
function installDependencies(projectDir, pm) {
|
|
45
|
+
console.log(`Installing dependencies with ${pm}...`);
|
|
46
|
+
try {
|
|
47
|
+
execSync(`${pm} install`, { cwd: projectDir, stdio: 'inherit' });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
console.log(`\nDependency install failed. Run "${pm} install" manually.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Initialize a fresh git repository in the project directory if one doesn't
|
|
55
|
+
* already exist. Silently skips when git is not installed or the commit fails.
|
|
56
|
+
*/
|
|
57
|
+
function initializeGit(projectDir) {
|
|
58
|
+
if (existsSync(resolve(projectDir, '.git')))
|
|
59
|
+
return;
|
|
60
|
+
try {
|
|
61
|
+
execSync('git init', { cwd: projectDir, stdio: 'ignore' });
|
|
62
|
+
execSync('git add -A', { cwd: projectDir, stdio: 'ignore' });
|
|
63
|
+
execSync('git commit -m "Initial commit from create-krantho"', {
|
|
64
|
+
cwd: projectDir,
|
|
65
|
+
stdio: 'ignore',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// git not available or commit failed — skip
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Print the "what's next" hint shown after scaffolding completes. */
|
|
73
|
+
function printSuccessMessage(options) {
|
|
74
|
+
console.log(`\nDone! Your krantho project is ready.\n`);
|
|
75
|
+
console.log(` cd ${options.name}`);
|
|
76
|
+
console.log(` ${options.packageManager} run dev\n`);
|
|
77
|
+
console.log(`Studio: http://localhost:${options.port}/studio`);
|
|
78
|
+
console.log(`API endpoint: http://localhost:${options.port}/api/content\n`);
|
|
79
|
+
}
|
|
80
|
+
async function main() {
|
|
81
|
+
const args = process.argv.slice(2);
|
|
82
|
+
// Support: create-krantho my-app
|
|
83
|
+
const targetArg = args.find((a) => !a.startsWith('-'));
|
|
84
|
+
const options = await runPrompts(targetArg);
|
|
85
|
+
const projectDir = resolve(process.cwd(), options.name);
|
|
86
|
+
validateProjectDir(projectDir, options.name);
|
|
87
|
+
console.log(`\nScaffolding ${options.name}...`);
|
|
88
|
+
console.log(` Framework: ${options.framework}`);
|
|
89
|
+
console.log(` UI: ${options.ui}`);
|
|
90
|
+
if (options.auth)
|
|
91
|
+
console.log(` Auth: token mode`);
|
|
92
|
+
if (options.seo)
|
|
93
|
+
console.log(` SEO: enabled`);
|
|
94
|
+
console.log();
|
|
95
|
+
scaffoldProject(projectDir, options);
|
|
96
|
+
installDependencies(projectDir, options.packageManager);
|
|
97
|
+
initializeGit(projectDir);
|
|
98
|
+
printSuccessMessage(options);
|
|
99
|
+
}
|
|
100
|
+
main().catch((err) => {
|
|
101
|
+
console.error(err);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Framework = 'nextjs' | 'sveltekit';
|
|
2
|
+
export type UI = 'tailwind' | 'mui' | 'plain';
|
|
3
|
+
export type PackageManager = 'pnpm' | 'npm' | 'yarn';
|
|
4
|
+
export interface ProjectOptions {
|
|
5
|
+
name: string;
|
|
6
|
+
framework: Framework;
|
|
7
|
+
ui: UI;
|
|
8
|
+
auth: boolean;
|
|
9
|
+
seo: boolean;
|
|
10
|
+
sampleContent: boolean;
|
|
11
|
+
packageManager: PackageManager;
|
|
12
|
+
port: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function runPrompts(targetArg?: string): Promise<ProjectOptions>;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
function detectPackageManager() {
|
|
3
|
+
const agent = process.env['npm_config_user_agent'] ?? '';
|
|
4
|
+
if (agent.startsWith('pnpm'))
|
|
5
|
+
return 'pnpm';
|
|
6
|
+
if (agent.startsWith('yarn'))
|
|
7
|
+
return 'yarn';
|
|
8
|
+
return 'npm';
|
|
9
|
+
}
|
|
10
|
+
export async function runPrompts(targetArg) {
|
|
11
|
+
const response = await prompts([
|
|
12
|
+
{
|
|
13
|
+
type: targetArg ? null : 'text',
|
|
14
|
+
name: 'name',
|
|
15
|
+
message: 'Project name:',
|
|
16
|
+
initial: 'my-krantho-site',
|
|
17
|
+
validate: (val) => /^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/.test(val) || 'Invalid project name',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'select',
|
|
21
|
+
name: 'framework',
|
|
22
|
+
message: 'Framework:',
|
|
23
|
+
choices: [
|
|
24
|
+
{ title: 'Next.js', description: 'App Router, React Server Components', value: 'nextjs' },
|
|
25
|
+
{
|
|
26
|
+
title: 'SvelteKit',
|
|
27
|
+
description: 'Svelte 5, server-side rendering',
|
|
28
|
+
value: 'sveltekit',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: (_, values) => (values['framework'] === 'nextjs' ? 'select' : null),
|
|
34
|
+
name: 'ui',
|
|
35
|
+
message: 'UI library:',
|
|
36
|
+
choices: [
|
|
37
|
+
{ title: 'Tailwind CSS', description: 'Utility-first CSS framework', value: 'tailwind' },
|
|
38
|
+
{ title: 'MUI', description: 'Material UI components', value: 'mui' },
|
|
39
|
+
{ title: 'Plain CSS', description: 'Semantic CSS, no framework', value: 'plain' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: (_, values) => (values['framework'] === 'sveltekit' ? 'select' : null),
|
|
44
|
+
name: 'ui',
|
|
45
|
+
message: 'UI library:',
|
|
46
|
+
choices: [
|
|
47
|
+
{ title: 'Tailwind CSS', description: 'Utility-first CSS framework', value: 'tailwind' },
|
|
48
|
+
{ title: 'Plain CSS', description: 'Semantic CSS, no framework', value: 'plain' },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'toggle',
|
|
53
|
+
name: 'auth',
|
|
54
|
+
message: 'Enable authentication (token mode)?',
|
|
55
|
+
initial: false,
|
|
56
|
+
active: 'yes',
|
|
57
|
+
inactive: 'no',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'toggle',
|
|
61
|
+
name: 'seo',
|
|
62
|
+
message: 'Include SEO components?',
|
|
63
|
+
initial: true,
|
|
64
|
+
active: 'yes',
|
|
65
|
+
inactive: 'no',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'toggle',
|
|
69
|
+
name: 'sampleContent',
|
|
70
|
+
message: 'Include sample content?',
|
|
71
|
+
initial: true,
|
|
72
|
+
active: 'yes',
|
|
73
|
+
inactive: 'no',
|
|
74
|
+
},
|
|
75
|
+
], {
|
|
76
|
+
onCancel: () => {
|
|
77
|
+
console.log('\nCancelled.');
|
|
78
|
+
process.exit(0);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
name: targetArg ?? response['name'],
|
|
83
|
+
framework: response['framework'],
|
|
84
|
+
ui: response['ui'] ?? 'tailwind',
|
|
85
|
+
auth: response['auth'] ?? false,
|
|
86
|
+
seo: response['seo'] ?? true,
|
|
87
|
+
sampleContent: response['sampleContent'] ?? true,
|
|
88
|
+
packageManager: detectPackageManager(),
|
|
89
|
+
port: 3000,
|
|
90
|
+
};
|
|
91
|
+
}
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { generateCommonFiles } from './templates/common.js';
|
|
2
|
+
import { generateContentFiles } from './templates/content.js';
|
|
3
|
+
import { generateNextjsFiles } from './templates/nextjs.js';
|
|
4
|
+
import { generateSveltekitFiles } from './templates/sveltekit.js';
|
|
5
|
+
export function generateFiles(options) {
|
|
6
|
+
const files = new Map();
|
|
7
|
+
// Common files (gitignore, tsconfig, env, README)
|
|
8
|
+
for (const [path, content] of generateCommonFiles(options)) {
|
|
9
|
+
files.set(path, content);
|
|
10
|
+
}
|
|
11
|
+
// Content files (pages, globals)
|
|
12
|
+
for (const [path, content] of generateContentFiles(options)) {
|
|
13
|
+
files.set(path, content);
|
|
14
|
+
}
|
|
15
|
+
// Framework-specific files
|
|
16
|
+
const frameworkFiles = options.framework === 'nextjs' ? generateNextjsFiles(options) : generateSveltekitFiles(options);
|
|
17
|
+
for (const [path, content] of frameworkFiles) {
|
|
18
|
+
files.set(path, content);
|
|
19
|
+
}
|
|
20
|
+
return files;
|
|
21
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export function generateCommonFiles(options) {
|
|
2
|
+
const files = new Map();
|
|
3
|
+
files.set('.gitignore', gitignore());
|
|
4
|
+
files.set('.env', envFile(options));
|
|
5
|
+
files.set('README.md', readme(options));
|
|
6
|
+
files.set('.claude/settings.json', claudeSettings());
|
|
7
|
+
return files;
|
|
8
|
+
}
|
|
9
|
+
function gitignore() {
|
|
10
|
+
return `node_modules/
|
|
11
|
+
dist/
|
|
12
|
+
.next/
|
|
13
|
+
.svelte-kit/
|
|
14
|
+
.krantho/
|
|
15
|
+
.env.local
|
|
16
|
+
*.db
|
|
17
|
+
*.db-journal
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
/** The GitHub repository that hosts the krantho plugin marketplace. */
|
|
21
|
+
const KRANTHO_MARKETPLACE_REPO = 'descoped/krantho';
|
|
22
|
+
/**
|
|
23
|
+
* Shared Claude Code project settings. Registering the krantho marketplace and
|
|
24
|
+
* enabling its plugin here means every Claude Code session opened in the new
|
|
25
|
+
* project has the krantho skills from the start. Claude Code honors
|
|
26
|
+
* `extraKnownMarketplaces` from a project file only after the folder is trusted.
|
|
27
|
+
*/
|
|
28
|
+
function claudeSettings() {
|
|
29
|
+
const settings = {
|
|
30
|
+
extraKnownMarketplaces: {
|
|
31
|
+
krantho: {
|
|
32
|
+
source: { source: 'github', repo: KRANTHO_MARKETPLACE_REPO },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
enabledPlugins: {
|
|
36
|
+
'krantho@krantho': true,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
return `${JSON.stringify(settings, null, 2)}\n`;
|
|
40
|
+
}
|
|
41
|
+
function envFile(options) {
|
|
42
|
+
const lines = [`# Krantho configuration`, `PORT=${options.port}`];
|
|
43
|
+
if (options.auth) {
|
|
44
|
+
lines.push('');
|
|
45
|
+
lines.push('# Auth — token mode is set in krantho.config.ts');
|
|
46
|
+
lines.push('# Set a secret token for Studio access');
|
|
47
|
+
lines.push('KRANTHO_API_TOKEN=change-me-to-a-secret');
|
|
48
|
+
}
|
|
49
|
+
return lines.join('\n') + '\n';
|
|
50
|
+
}
|
|
51
|
+
function readme(options) {
|
|
52
|
+
const fw = options.framework === 'nextjs' ? 'Next.js' : 'SvelteKit';
|
|
53
|
+
const pm = options.packageManager;
|
|
54
|
+
return `# ${options.name}
|
|
55
|
+
|
|
56
|
+
Built with [krantho](https://github.com/descoped/krantho) and ${fw}.
|
|
57
|
+
|
|
58
|
+
## Getting Started
|
|
59
|
+
|
|
60
|
+
\`\`\`bash
|
|
61
|
+
${pm} run dev
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
Open [http://localhost:${options.port}](http://localhost:${options.port}) to see your site.
|
|
65
|
+
|
|
66
|
+
## Studio
|
|
67
|
+
|
|
68
|
+
Open [http://localhost:${options.port}/studio](http://localhost:${options.port}/studio) to manage content.
|
|
69
|
+
|
|
70
|
+
## Project Structure
|
|
71
|
+
|
|
72
|
+
\`\`\`
|
|
73
|
+
content/ # Markdown content files
|
|
74
|
+
pages/ # Page content (frontmatter + markdown body)
|
|
75
|
+
globals/ # Site-wide data (navigation, footer)
|
|
76
|
+
${options.framework === 'nextjs' ? 'app/ # Next.js App Router pages' : 'src/routes/ # SvelteKit route pages'}
|
|
77
|
+
${options.framework === 'nextjs' ? 'lib/ # Krantho initialization' : 'src/lib/ # Krantho initialization'}
|
|
78
|
+
\`\`\`
|
|
79
|
+
|
|
80
|
+
## Content
|
|
81
|
+
|
|
82
|
+
Edit markdown files in \`content/\` or use Studio.
|
|
83
|
+
|
|
84
|
+
Each page has YAML frontmatter (metadata) and a markdown body:
|
|
85
|
+
|
|
86
|
+
\`\`\`markdown
|
|
87
|
+
---
|
|
88
|
+
title: My Page
|
|
89
|
+
description: A brief description
|
|
90
|
+
status: published
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Section Heading
|
|
94
|
+
|
|
95
|
+
Page content here...
|
|
96
|
+
\`\`\`
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function generateContentFiles(options) {
|
|
2
|
+
const files = new Map();
|
|
3
|
+
// Globals (always created)
|
|
4
|
+
files.set('content/globals/navigation.md', navigationContent(options));
|
|
5
|
+
files.set('content/globals/footer.md', footerContent(options));
|
|
6
|
+
if (options.sampleContent) {
|
|
7
|
+
files.set('content/pages/home.md', homePageContent(options));
|
|
8
|
+
files.set('content/pages/about.md', aboutPageContent());
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
// Minimal starter page
|
|
12
|
+
files.set('content/pages/home.md', minimalHomeContent(options));
|
|
13
|
+
}
|
|
14
|
+
return files;
|
|
15
|
+
}
|
|
16
|
+
function navigationContent(options) {
|
|
17
|
+
return `---
|
|
18
|
+
logo-name: ${options.name}
|
|
19
|
+
logo-subtitle: Built with krantho
|
|
20
|
+
nav-home: Home
|
|
21
|
+
nav-about: About
|
|
22
|
+
---
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
function footerContent(options) {
|
|
26
|
+
return `---
|
|
27
|
+
footer-title: ${options.name}
|
|
28
|
+
footer-description: A content-driven site powered by krantho.
|
|
29
|
+
footer-copyright-text: "\u00A9 ${new Date().getFullYear()} ${options.name}. All rights reserved."
|
|
30
|
+
---
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
function homePageContent(options) {
|
|
34
|
+
return `---
|
|
35
|
+
title: Welcome to ${options.name}
|
|
36
|
+
description: ${options.name} — a content-driven site built with krantho.
|
|
37
|
+
status: published
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Getting Started
|
|
41
|
+
|
|
42
|
+
This is your home page. Edit it in \`content/pages/home.md\` or use Studio at [/studio](/studio).
|
|
43
|
+
|
|
44
|
+
Krantho gives you:
|
|
45
|
+
|
|
46
|
+
- **Markdown content** with YAML frontmatter
|
|
47
|
+
- **Studio** for content management
|
|
48
|
+
- **API endpoints** for headless use
|
|
49
|
+
- **Schema validation** with Zod
|
|
50
|
+
- **SQLite persistence** with auto-import from markdown
|
|
51
|
+
|
|
52
|
+
## How It Works
|
|
53
|
+
|
|
54
|
+
Content lives in the \`content/\` directory as markdown files. The framework auto-imports these into a SQLite database on first run, then serves them through a REST API.
|
|
55
|
+
|
|
56
|
+
Studio at \`/studio\` provides a full editing experience with:
|
|
57
|
+
|
|
58
|
+
- Collection browsing and search
|
|
59
|
+
- Schema-driven form fields
|
|
60
|
+
- Split-view markdown editor
|
|
61
|
+
- Version history and restore
|
|
62
|
+
- Publishing workflow (draft/published/archived)
|
|
63
|
+
|
|
64
|
+
## Next Steps
|
|
65
|
+
|
|
66
|
+
1. Edit this page in Studio or in \`content/pages/home.md\`
|
|
67
|
+
2. Create new pages by adding markdown files to \`content/pages/\`
|
|
68
|
+
3. Define custom collections with Zod schemas
|
|
69
|
+
4. Customize the layout and components
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
function aboutPageContent() {
|
|
73
|
+
return `---
|
|
74
|
+
title: About
|
|
75
|
+
description: Learn more about this project.
|
|
76
|
+
status: published
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Our Story
|
|
80
|
+
|
|
81
|
+
This is a sample about page. Replace this content with your own.
|
|
82
|
+
|
|
83
|
+
## What We Do
|
|
84
|
+
|
|
85
|
+
We build great things with krantho — a full-site content framework with Studio, API, and schema validation.
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
function minimalHomeContent(options) {
|
|
89
|
+
return `---
|
|
90
|
+
title: ${options.name}
|
|
91
|
+
description: Welcome to ${options.name}.
|
|
92
|
+
status: published
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Welcome
|
|
96
|
+
|
|
97
|
+
Your krantho site is ready. Edit this page or create new content.
|
|
98
|
+
`;
|
|
99
|
+
}
|