create-krantho 0.0.0-stage → 1.0.1
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
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { KRANTHO_RANGE } from '../version.js';
|
|
2
|
+
export function generateSveltekitFiles(options) {
|
|
3
|
+
const files = new Map();
|
|
4
|
+
files.set('package.json', packageJson(options));
|
|
5
|
+
files.set('tsconfig.json', tsconfig());
|
|
6
|
+
files.set('svelte.config.js', svelteConfig());
|
|
7
|
+
files.set('vite.config.ts', viteConfig(options));
|
|
8
|
+
files.set('krantho.config.ts', kranthoConfig(options));
|
|
9
|
+
files.set('krantho.collections.ts', kranthoCollections(options));
|
|
10
|
+
files.set('src/app.html', appHtml(options));
|
|
11
|
+
files.set('src/app.css', appCss(options));
|
|
12
|
+
files.set('src/lib/cms.ts', cmsInit());
|
|
13
|
+
files.set('src/routes/api/[...path]/+server.ts', apiHandler());
|
|
14
|
+
files.set('src/routes/studio/[...path]/+server.ts', studioHandler());
|
|
15
|
+
files.set('src/routes/sitemap.xml/+server.ts', seoRoute('/api/seo/sitemap'));
|
|
16
|
+
files.set('src/routes/robots.txt/+server.ts', seoRoute('/api/seo/robots.txt'));
|
|
17
|
+
files.set('src/routes/+layout.server.ts', layoutServer());
|
|
18
|
+
files.set('src/routes/+layout.svelte', layoutSvelte(options));
|
|
19
|
+
files.set('src/routes/+page.server.ts', homePageServer());
|
|
20
|
+
files.set('src/routes/+page.svelte', homePage());
|
|
21
|
+
files.set('src/routes/[slug]/+page.server.ts', dynamicPageServer());
|
|
22
|
+
files.set('src/routes/[slug]/+page.svelte', dynamicPage());
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
function packageJson(options) {
|
|
26
|
+
// `@krantho/sveltekit` is the one-install umbrella — it bundles
|
|
27
|
+
// server-sveltekit, ui-svelte, studio, media-local, and seo so a single
|
|
28
|
+
// dependency provisions the full stack. The packages the generated source
|
|
29
|
+
// imports *by name* (server-sveltekit, core, schema, persistence-sqlite) are
|
|
30
|
+
// also listed explicitly so the project resolves under a strict, non-hoisted
|
|
31
|
+
// node_modules layout where transitive deps are not directly importable.
|
|
32
|
+
const deps = {
|
|
33
|
+
'@krantho/config': KRANTHO_RANGE,
|
|
34
|
+
'@krantho/sveltekit': KRANTHO_RANGE,
|
|
35
|
+
'@krantho/server-sveltekit': KRANTHO_RANGE,
|
|
36
|
+
'@krantho/core': KRANTHO_RANGE,
|
|
37
|
+
'@krantho/schema': KRANTHO_RANGE,
|
|
38
|
+
'@krantho/persistence-sqlite': KRANTHO_RANGE,
|
|
39
|
+
'@sveltejs/adapter-auto': '^7.0.1',
|
|
40
|
+
'@sveltejs/kit': '^2.55.0',
|
|
41
|
+
svelte: '^5.55.0',
|
|
42
|
+
zod: '^4.4.3',
|
|
43
|
+
};
|
|
44
|
+
if (options.ui === 'tailwind') {
|
|
45
|
+
deps['tailwindcss'] = '^4.2.2';
|
|
46
|
+
deps['@tailwindcss/vite'] = '^4.2.2';
|
|
47
|
+
}
|
|
48
|
+
const devDeps = {
|
|
49
|
+
'@types/node': '^25.5.0',
|
|
50
|
+
typescript: '^6.0.2',
|
|
51
|
+
vite: '^8.0.3',
|
|
52
|
+
};
|
|
53
|
+
const pkg = {
|
|
54
|
+
name: options.name,
|
|
55
|
+
version: '0.1.0',
|
|
56
|
+
private: true,
|
|
57
|
+
type: 'module',
|
|
58
|
+
scripts: {
|
|
59
|
+
// Vite does not load .env into process.env for server code; the
|
|
60
|
+
// config resolver reads KRANTHO_* from process.env, so load it here.
|
|
61
|
+
dev: `node --env-file-if-exists=.env node_modules/vite/bin/vite.js dev --port ${options.port}`,
|
|
62
|
+
build: 'vite build',
|
|
63
|
+
preview: 'vite preview',
|
|
64
|
+
},
|
|
65
|
+
dependencies: sortObject(deps),
|
|
66
|
+
devDependencies: sortObject(devDeps),
|
|
67
|
+
};
|
|
68
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
69
|
+
}
|
|
70
|
+
function tsconfig() {
|
|
71
|
+
return (JSON.stringify({
|
|
72
|
+
extends: './.svelte-kit/tsconfig.json',
|
|
73
|
+
compilerOptions: {
|
|
74
|
+
allowJs: true,
|
|
75
|
+
checkJs: true,
|
|
76
|
+
esModuleInterop: true,
|
|
77
|
+
forceConsistentCasingInFileNames: true,
|
|
78
|
+
resolveJsonModule: true,
|
|
79
|
+
skipLibCheck: true,
|
|
80
|
+
sourceMap: true,
|
|
81
|
+
strict: true,
|
|
82
|
+
moduleResolution: 'bundler',
|
|
83
|
+
},
|
|
84
|
+
}, null, 2) + '\n');
|
|
85
|
+
}
|
|
86
|
+
function svelteConfig() {
|
|
87
|
+
return `import adapter from '@sveltejs/adapter-auto'
|
|
88
|
+
|
|
89
|
+
export default {
|
|
90
|
+
kit: { adapter: adapter() }
|
|
91
|
+
}
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
function viteConfig(options) {
|
|
95
|
+
if (options.ui === 'tailwind') {
|
|
96
|
+
return `import { sveltekit } from '@sveltejs/kit/vite'
|
|
97
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
98
|
+
import { defineConfig } from 'vite'
|
|
99
|
+
|
|
100
|
+
export default defineConfig({
|
|
101
|
+
plugins: [tailwindcss(), sveltekit()]
|
|
102
|
+
})
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
return `import { sveltekit } from '@sveltejs/kit/vite'
|
|
106
|
+
import { defineConfig } from 'vite'
|
|
107
|
+
|
|
108
|
+
export default defineConfig({
|
|
109
|
+
plugins: [sveltekit()]
|
|
110
|
+
})
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
function appHtml(options) {
|
|
114
|
+
return `<!doctype html>
|
|
115
|
+
<html lang="en">
|
|
116
|
+
<head>
|
|
117
|
+
<meta charset="utf-8" />
|
|
118
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
119
|
+
<title>${options.name}</title>
|
|
120
|
+
%sveltekit.head%
|
|
121
|
+
</head>
|
|
122
|
+
<body data-sveltekit-preload-data="hover">
|
|
123
|
+
<div style="display: contents">%sveltekit.body%</div>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
function appCss(options) {
|
|
129
|
+
if (options.ui === 'tailwind') {
|
|
130
|
+
return `@import 'tailwindcss';
|
|
131
|
+
|
|
132
|
+
:root {
|
|
133
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
134
|
+
--color-primary: #2563eb;
|
|
135
|
+
--color-text: #1a1a2e;
|
|
136
|
+
--color-muted: #6b7280;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
body {
|
|
140
|
+
font-family: var(--font-sans);
|
|
141
|
+
color: var(--color-text);
|
|
142
|
+
margin: 0;
|
|
143
|
+
line-height: 1.6;
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
return `:root {
|
|
148
|
+
--font-sans: system-ui, -apple-system, sans-serif;
|
|
149
|
+
--color-primary: #2563eb;
|
|
150
|
+
--color-text: #1a1a2e;
|
|
151
|
+
--color-muted: #6b7280;
|
|
152
|
+
--color-border: #e5e7eb;
|
|
153
|
+
--max-width: 48rem;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
157
|
+
|
|
158
|
+
body {
|
|
159
|
+
font-family: var(--font-sans);
|
|
160
|
+
color: var(--color-text);
|
|
161
|
+
line-height: 1.6;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
header {
|
|
165
|
+
border-bottom: 1px solid var(--color-border);
|
|
166
|
+
padding: 1rem 1.5rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
header nav {
|
|
170
|
+
max-width: var(--max-width);
|
|
171
|
+
margin: 0 auto;
|
|
172
|
+
display: flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: space-between;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
header a { color: inherit; text-decoration: none; }
|
|
178
|
+
header nav .links { display: flex; gap: 1rem; }
|
|
179
|
+
header nav .links a:hover { color: var(--color-primary); }
|
|
180
|
+
|
|
181
|
+
main {
|
|
182
|
+
max-width: var(--max-width);
|
|
183
|
+
margin: 0 auto;
|
|
184
|
+
padding: 3rem 1.5rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
h1 { font-size: 1.875rem; font-weight: 700; margin-bottom: 1rem; }
|
|
188
|
+
h2 { font-size: 1.25rem; font-weight: 600; margin-top: 2rem; margin-bottom: 0.75rem; }
|
|
189
|
+
|
|
190
|
+
.intro { font-size: 1.125rem; color: var(--color-muted); margin-bottom: 2rem; }
|
|
191
|
+
|
|
192
|
+
footer {
|
|
193
|
+
border-top: 1px solid var(--color-border);
|
|
194
|
+
padding: 2rem 1.5rem;
|
|
195
|
+
text-align: center;
|
|
196
|
+
font-size: 0.875rem;
|
|
197
|
+
color: var(--color-muted);
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
201
|
+
function cmsInit() {
|
|
202
|
+
return `import { renderContent } from '@krantho/core'
|
|
203
|
+
import { createContentLoader } from '@krantho/server-sveltekit'
|
|
204
|
+
import config from '../../krantho.config'
|
|
205
|
+
import { collections } from '../../krantho.collections'
|
|
206
|
+
|
|
207
|
+
// Pages read the live (published) version of each document from the database
|
|
208
|
+
// the API route opens: both resolve this app's configuration.
|
|
209
|
+
const loader = createContentLoader({ config, collections })
|
|
210
|
+
|
|
211
|
+
/** A published document, rendered for a page */
|
|
212
|
+
export interface PageContent {
|
|
213
|
+
fields: Record<string, unknown>
|
|
214
|
+
html: string
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** The published version of a document; null when it has none */
|
|
218
|
+
export async function loadContent(collection: string, slug: string): Promise<PageContent | null> {
|
|
219
|
+
const doc = await loader.getPublished({ collection, slug })
|
|
220
|
+
if (!doc) return null
|
|
221
|
+
const { html } = await renderContent(doc.fields, doc.body)
|
|
222
|
+
return { fields: doc.fields, html }
|
|
223
|
+
}
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
/** With SEO chosen, a page's own structured data is a group of its metadata, edited in Data mode */
|
|
227
|
+
function kranthoCollections(options) {
|
|
228
|
+
const seoSlot = options.seo ? ', seo: { archetype: structuredDataMetadata }' : '';
|
|
229
|
+
const imports = options.seo
|
|
230
|
+
? `import {
|
|
231
|
+
defineCollection,
|
|
232
|
+
defineDocumentArchetype,
|
|
233
|
+
sharingMetadata,
|
|
234
|
+
structuredDataMetadata,
|
|
235
|
+
} from '@krantho/schema'`
|
|
236
|
+
: "import { defineCollection, defineDocumentArchetype, sharingMetadata } from '@krantho/schema'";
|
|
237
|
+
return `import { join } from 'path'
|
|
238
|
+
${imports}
|
|
239
|
+
import { z } from 'zod'
|
|
240
|
+
|
|
241
|
+
// The collections this site serves — shared by the API route and the page loader,
|
|
242
|
+
// so whichever a fresh deployment serves first imports the same content.
|
|
243
|
+
|
|
244
|
+
const pages = defineCollection({
|
|
245
|
+
name: 'pages',
|
|
246
|
+
source: 'filesystem',
|
|
247
|
+
directory: join(process.cwd(), 'content', 'pages'),
|
|
248
|
+
archetype: defineDocumentArchetype({
|
|
249
|
+
id: 'site/pages', label: 'Page',
|
|
250
|
+
fields: z.object({
|
|
251
|
+
title: z.string(),
|
|
252
|
+
description: z.string(),
|
|
253
|
+
|
|
254
|
+
}),
|
|
255
|
+
metadata: { sharing: { archetype: sharingMetadata }${seoSlot} },
|
|
256
|
+
}),
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const globals = defineCollection({
|
|
260
|
+
name: 'globals',
|
|
261
|
+
source: 'filesystem',
|
|
262
|
+
directory: join(process.cwd(), 'content', 'globals'),
|
|
263
|
+
archetype: defineDocumentArchetype({
|
|
264
|
+
id: 'site/globals', label: 'Shared content',
|
|
265
|
+
fields: z.object({
|
|
266
|
+
'logo-name': z.string().optional(),
|
|
267
|
+
'logo-subtitle': z.string().optional(),
|
|
268
|
+
'nav-home': z.string().optional(),
|
|
269
|
+
'nav-about': z.string().optional(),
|
|
270
|
+
'footer-title': z.string().optional(),
|
|
271
|
+
'footer-description': z.string().optional(),
|
|
272
|
+
'footer-copyright-text': z.string().optional(),
|
|
273
|
+
}),
|
|
274
|
+
metadata: { sharing: { archetype: sharingMetadata } },
|
|
275
|
+
}),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
export const collections = { pages, globals }
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A root route search engines look for (/sitemap.xml, /robots.txt), answered
|
|
283
|
+
* by the krantho API, which builds it from the published content and site.baseUrl
|
|
284
|
+
*/
|
|
285
|
+
function seoRoute(apiPath) {
|
|
286
|
+
return `import type { RequestHandler } from '@sveltejs/kit'
|
|
287
|
+
|
|
288
|
+
// Search engines look for this file at the site root; the krantho API builds it.
|
|
289
|
+
export const GET: RequestHandler = ({ fetch }) => fetch('${apiPath}')
|
|
290
|
+
`;
|
|
291
|
+
}
|
|
292
|
+
function apiHandler() {
|
|
293
|
+
return `import { createApiHandlers } from '@krantho/server-sveltekit'
|
|
294
|
+
import config from '../../../../krantho.config'
|
|
295
|
+
import { collections } from '../../../../krantho.collections'
|
|
296
|
+
|
|
297
|
+
export const { GET, POST, PUT, DELETE } = createApiHandlers({
|
|
298
|
+
config,
|
|
299
|
+
collections,
|
|
300
|
+
})
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
/** Studio, which the project tells its owner to open at /studio */
|
|
304
|
+
function studioHandler() {
|
|
305
|
+
return `import { createStudioHandler } from '@krantho/server-sveltekit'
|
|
306
|
+
import config from '../../../../krantho.config'
|
|
307
|
+
|
|
308
|
+
export const { GET } = createStudioHandler({ config })
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
function kranthoConfig(options) {
|
|
312
|
+
const authLines = options.auth
|
|
313
|
+
? `
|
|
314
|
+
// Token mode — the secret comes from KRANTHO_API_TOKEN in the environment.
|
|
315
|
+
auth: { mode: 'token' },`
|
|
316
|
+
: '';
|
|
317
|
+
return `import { defineKranthoConfig } from '@krantho/config'
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Site configuration — data only. Every value here can be overridden from the
|
|
321
|
+
* environment; \`KRANTHO_SITE_BASE_URL\` overrides \`site.baseUrl\`, and so on.
|
|
322
|
+
* Collections live in krantho.collections.ts; plugins are wiring in the route
|
|
323
|
+
* file. The media file store is chosen by \`media.provider\`.
|
|
324
|
+
*/
|
|
325
|
+
export default defineKranthoConfig({
|
|
326
|
+
site: {
|
|
327
|
+
domain: 'localhost',
|
|
328
|
+
name: '${options.name}',
|
|
329
|
+
language: 'en',
|
|
330
|
+
baseUrl: 'http://localhost:${options.port}',
|
|
331
|
+
supportedLocales: ['en'],
|
|
332
|
+
defaultLocale: 'en',
|
|
333
|
+
},
|
|
334
|
+
// Uploads go to static/uploads, which SvelteKit serves at /uploads
|
|
335
|
+
media: { provider: 'local', local: { dir: 'static/uploads' } },${authLines}
|
|
336
|
+
})
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
function layoutServer() {
|
|
340
|
+
return `import { loadContent } from '$lib/cms'
|
|
341
|
+
|
|
342
|
+
export async function load() {
|
|
343
|
+
const nav = await loadContent('globals', 'navigation')
|
|
344
|
+
const footer = await loadContent('globals', 'footer')
|
|
345
|
+
return {
|
|
346
|
+
nav: nav?.fields ?? {},
|
|
347
|
+
footer: footer?.fields ?? {},
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
`;
|
|
351
|
+
}
|
|
352
|
+
function layoutSvelte(options) {
|
|
353
|
+
const tailwindImport = options.ui === 'tailwind' ? ` import '../app.css'\n` : ` import '../app.css'\n`;
|
|
354
|
+
return `<script lang="ts">
|
|
355
|
+
${tailwindImport} import type { Snippet } from 'svelte'
|
|
356
|
+
|
|
357
|
+
interface Props {
|
|
358
|
+
data: {
|
|
359
|
+
nav: Record<string, unknown>
|
|
360
|
+
footer: Record<string, unknown>
|
|
361
|
+
}
|
|
362
|
+
children: Snippet
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let { data, children }: Props = $props()
|
|
366
|
+
const nav = $derived(data.nav)
|
|
367
|
+
const footer = $derived(data.footer)
|
|
368
|
+
</script>
|
|
369
|
+
|
|
370
|
+
<header>
|
|
371
|
+
<nav>
|
|
372
|
+
<a href="/">
|
|
373
|
+
<strong>{nav['logo-name'] ?? '${options.name}'}</strong>
|
|
374
|
+
{#if nav['logo-subtitle']}
|
|
375
|
+
<span> — {nav['logo-subtitle']}</span>
|
|
376
|
+
{/if}
|
|
377
|
+
</a>
|
|
378
|
+
<div class="links">
|
|
379
|
+
<a href="/">{nav['nav-home'] ?? 'Home'}</a>
|
|
380
|
+
<a href="/about">{nav['nav-about'] ?? 'About'}</a>
|
|
381
|
+
</div>
|
|
382
|
+
</nav>
|
|
383
|
+
</header>
|
|
384
|
+
|
|
385
|
+
<main>
|
|
386
|
+
{@render children()}
|
|
387
|
+
</main>
|
|
388
|
+
|
|
389
|
+
<footer>
|
|
390
|
+
<p>{footer['footer-title'] ?? '${options.name}'}</p>
|
|
391
|
+
{#if footer['footer-description']}
|
|
392
|
+
<p>{footer['footer-description']}</p>
|
|
393
|
+
{/if}
|
|
394
|
+
<p>{footer['footer-copyright-text'] ?? ''}</p>
|
|
395
|
+
</footer>
|
|
396
|
+
`;
|
|
397
|
+
}
|
|
398
|
+
function homePageServer() {
|
|
399
|
+
return `import { loadContent } from '$lib/cms'
|
|
400
|
+
import { error } from '@sveltejs/kit'
|
|
401
|
+
|
|
402
|
+
export async function load() {
|
|
403
|
+
const page = await loadContent('pages', 'home')
|
|
404
|
+
if (!page) error(404, 'Page not found')
|
|
405
|
+
return {
|
|
406
|
+
fields: page.fields,
|
|
407
|
+
html: page.html,
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
`;
|
|
411
|
+
}
|
|
412
|
+
function homePage() {
|
|
413
|
+
return `<script lang="ts">
|
|
414
|
+
interface Props {
|
|
415
|
+
data: {
|
|
416
|
+
fields: Record<string, unknown>
|
|
417
|
+
html: string
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let { data }: Props = $props()
|
|
422
|
+
const fm = $derived(data.fields)
|
|
423
|
+
</script>
|
|
424
|
+
|
|
425
|
+
<article>
|
|
426
|
+
<h1>{fm['title'] ?? 'Home'}</h1>
|
|
427
|
+
<div class="prose">{@html data.html}</div>
|
|
428
|
+
</article>
|
|
429
|
+
`;
|
|
430
|
+
}
|
|
431
|
+
function dynamicPageServer() {
|
|
432
|
+
return `import { loadContent } from '$lib/cms'
|
|
433
|
+
import { error } from '@sveltejs/kit'
|
|
434
|
+
|
|
435
|
+
export async function load({ params }: { params: { slug: string } }) {
|
|
436
|
+
const page = await loadContent('pages', params.slug)
|
|
437
|
+
if (!page) error(404, 'Page not found')
|
|
438
|
+
return {
|
|
439
|
+
fields: page.fields,
|
|
440
|
+
html: page.html,
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
`;
|
|
444
|
+
}
|
|
445
|
+
function dynamicPage() {
|
|
446
|
+
return `<script lang="ts">
|
|
447
|
+
interface Props {
|
|
448
|
+
data: {
|
|
449
|
+
fields: Record<string, unknown>
|
|
450
|
+
html: string
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
let { data }: Props = $props()
|
|
455
|
+
const fm = $derived(data.fields)
|
|
456
|
+
</script>
|
|
457
|
+
|
|
458
|
+
<article>
|
|
459
|
+
<h1>{fm['title'] ?? 'Page'}</h1>
|
|
460
|
+
<div class="prose">{@html data.html}</div>
|
|
461
|
+
</article>
|
|
462
|
+
`;
|
|
463
|
+
}
|
|
464
|
+
function sortObject(obj) {
|
|
465
|
+
const sorted = {};
|
|
466
|
+
for (const key of Object.keys(obj).sort()) {
|
|
467
|
+
const value = obj[key];
|
|
468
|
+
if (value !== undefined)
|
|
469
|
+
sorted[key] = value;
|
|
470
|
+
}
|
|
471
|
+
return sorted;
|
|
472
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The krantho release this scaffolder belongs to; every `@krantho/*` package carries the same version */
|
|
2
|
+
export declare const KRANTHO_VERSION: string;
|
|
3
|
+
/** The range a new project depends on: this release and the compatible ones after it */
|
|
4
|
+
export declare const KRANTHO_RANGE: string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/** The krantho release this scaffolder belongs to; every `@krantho/*` package carries the same version */
|
|
3
|
+
export const KRANTHO_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8')).version;
|
|
4
|
+
/** The range a new project depends on: this release and the compatible ones after it */
|
|
5
|
+
export const KRANTHO_RANGE = `^${KRANTHO_VERSION}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-krantho",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Scaffold a new krantho project",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://krantho.com",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/descoped/krantho.git",
|
|
10
|
+
"directory": "packages/create-krantho"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/descoped/krantho/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "oranheim",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"bin": {
|
|
18
|
+
"create-krantho": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"prompts": "^2.4.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.9.9",
|
|
28
|
+
"@types/prompts": "^2.4.9",
|
|
29
|
+
"typescript": "^6.0.3"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
34
|
+
}
|
|
6
35
|
}
|
package/README.md
DELETED