create-ginko 0.0.0-stage → 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/README.md +55 -2
  4. package/index.mjs +128 -0
  5. package/node-version.mjs +10 -0
  6. package/package.json +31 -3
  7. package/presets/docs/app/app.vue +5 -0
  8. package/presets/docs/content/docs/1.getting-started.md +12 -0
  9. package/presets/docs/content/docs/2.writing-pages.md +14 -0
  10. package/presets/docs/ginko.config.ts +21 -0
  11. package/presets/docs/nuxt.config.ts +9 -0
  12. package/presets/docs-de/app/app.vue +5 -0
  13. package/presets/docs-de/content/docs/1.einstieg.md +12 -0
  14. package/presets/docs-de/content/docs/2.seiten-schreiben.md +14 -0
  15. package/presets/docs-de/ginko.config.ts +21 -0
  16. package/presets/docs-de/nuxt.config.ts +9 -0
  17. package/presets/gastro/app/pages/index.vue +35 -0
  18. package/presets/gastro/content/hours.md +12 -0
  19. package/presets/gastro/content/menu/1.tomato-soup.md +7 -0
  20. package/presets/gastro/content/menu/2.house-burger.md +7 -0
  21. package/presets/gastro/content/menu/3.garden-salad.md +7 -0
  22. package/presets/gastro/content/menu/4.apple-tart.md +7 -0
  23. package/presets/gastro/ginko.config.ts +23 -0
  24. package/presets/gastro-de/app/pages/index.vue +35 -0
  25. package/presets/gastro-de/content/hours.md +12 -0
  26. package/presets/gastro-de/content/menu/1.tomatensuppe.md +7 -0
  27. package/presets/gastro-de/content/menu/2.hausburger.md +7 -0
  28. package/presets/gastro-de/content/menu/3.gartensalat.md +7 -0
  29. package/presets/gastro-de/content/menu/4.apfelkuchen.md +7 -0
  30. package/presets/gastro-de/ginko.config.ts +25 -0
  31. package/template/app/app.vue +3 -0
  32. package/template/app/pages/index.vue +12 -0
  33. package/template/content/start.md +5 -0
  34. package/template/ginko.config.ts +24 -0
  35. package/template/gitignore +6 -0
  36. package/template/nuxt.config.ts +8 -0
  37. package/template/package.json +28 -0
  38. package/template/pnpm-workspace.yaml +4 -0
  39. package/template/tsconfig.json +9 -0
  40. package/template-de/content/start.md +5 -0
  41. package/template-de/ginko.config.ts +23 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,70 @@
1
+ # create-ginko
2
+
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`ea2ea13`](https://github.com/lupinum-dev/ginko/commit/ea2ea133997f2011ad0d43f77a0f1dc75605923e) Thanks [@Mat4m0](https://github.com/Mat4m0)! - Add Ginko 0.4, the first release: one content system for Nuxt websites, from Markdown files in Git to a hosted platform.
8
+
9
+ Owners, staff and AI agents edit the same content. All seven packages release together under one version. It starts at 0.4 because `@lupinum/ginko-content` and `@lupinum/ginko-docs` already published an older, unrelated line up to 0.3. Until 1.0, a minor version (0.4 → 0.5) may contain breaking changes; the changelog names each one and how to update.
10
+
11
+ Requirements: Node 22.18+, 24 or 26, and Nuxt 4.6+ or Nuxt 5 (tested on its nightly until it is released). Start a site with `npm create ginko@latest my-site`; the documentation explains every step.
12
+
13
+ **Public API**
14
+
15
+ - `@lupinum/ginko`: `defineSite`, `single`, `collection`, `f`, `defineComponents`, `formatMoney`, `plainText`, `safeHref`, `startOfWeek` and the documented types. Imported, not auto-imported.
16
+ - `@lupinum/ginko/nuxt`: the module, the `useGinko*` composables (everything that needs a Nuxt app starts with `useGinko`), `<GinkoMarkdown>` and `<GinkoImage>`. `@lupinum/ginko/vue`: `<GinkoMarkdown>` for any Vue app. `@lupinum/ginko/search`: `loadIndex` and `search`.
17
+ - `@lupinum/ginko/internal` and `@lupinum/ginko/internal/search` are for Ginko's own packages and have no stability promise.
18
+ - The `ginko` CLI: `check [--offline]`, `release` and `export [folder]`, plus `--version`. Every command run prints one JSON line, `{ "ok": true, … }` or `{ "ok": false, "error": { code, message } }` (`--help` and `--version` print plain text), and ends with exit code 0 (ok), 1 (fix the code), 2 (usage), 3 (platform unreachable, retry) or 4 (key not allowed). CI deploys the site only when `release` prints `"deploy": true` (a superseded run exits 0 with `"deploy": false`). The CLI reads `GINKO_PLATFORM` (or `NUXT_PUBLIC_GINKO_PLATFORM`), `GINKO_KEY`, `GINKO_COMMIT` (or `GITHUB_SHA`), `GINKO_BRANCH` and `GINKO_REMOTE`; it does not read `.env` files. `ginko-i18n --json` uses the same output shape, error codes (`USAGE`, `INVALID`) and exit codes.
19
+ - Config names: `f.ref` (one entry) and `f.refs` (several); `visible: { mode: 'until', field, days?, deleteExpired? }`; prefill tokens `@today`, `@workWeek`, `@nextWorkWeek`; `firstDayOfWeek`, `linkCheck`, `untranslated`, `maxBytes`, `Migration.compute`, `retired` rule `{ drop: true }`. Options and defaults are typed per field, and an unknown top-level key fails the build.
20
+ - Markdown components: `embed`, `frame` (`none`, `card`, `border`, …), `background` (`none`, `muted`, `tint`), `appearance` (`quiet`, `tint`), `quiz-question{mode}`, `code-tree{file}`.
21
+
22
+ **Content (`@lupinum/ginko`, `@lupinum/ginko-content`)**
23
+
24
+ - One text format: Markdown (comark 0.7) everywhere, with field profiles that limit what a field may contain. Footnotes, emoji, alerts, Shiki code, task lists, route links (`$blog.my-post`), reusable snippets and heading anchors.
25
+ - Data files: YAML and JSON (one file, one entry) and CSV (one file, many entries), checked against the field schema; every error names the file and field, and the line for CSV rows and unreadable files. Entries with `draft: true` stay out of production builds.
26
+ - Query engine in the site, over the loaded bundle: `where`, `order`, `limit`, pagination with totals, field selection, `include` for references, previous/next and facet counts.
27
+ - References (`f.ref`, `f.refs`), navigation trees and breadcrumbs, and a URL for every entry in every language; two entries on one URL fail the build.
28
+ - Link check before any HTML exists: route links, anchors, snippets and redirected paths, in the build, the dev terminal, `ginko check` and the agent tools. On the platform a save, a new entry or a scheduled publish that adds a broken link is stopped, and History says why. External links are checked on request (`linkCheck.external`), at build and in `ginko check` (never in dev), with a 7-day cache.
29
+ - Images: WebP copies at 480, 960 and 1600 px, made at upload on the platform and at build for files. `<GinkoImage>` and Markdown images get `srcset` and `sizes`.
30
+ - Redirects from `redirectFrom` (301) and site rules in `redirects` (301 by default, `status` 302/307/308 per rule).
31
+ - SEO from the bundle, with no other module: title template, canonical, Open Graph with `og:url`, `<html lang>`, hreflang with `x-default`, sitemap with `lastmod`, images and alternates, robots.txt with `noindex` on preview hosts, JSON-LD. With `nuxt-og-image` (in every starter), every page gets a generated share card in the brand colour `seo.color`, with the site's logo and the page's image.
32
+ - Works with the Nuxt SEO modules (`@nuxtjs/seo` or single ones, in any order): Ginko leaves them sitemap, robots.txt and JSON-LD defaults and hands them its pages with hreflang, the site's address, name and languages, and `noindex` on preview hosts. `nuxt-ai-ready` stays off while Ginko serves `/llms.txt`. Requires `nuxt-og-image` 7.
33
+ - Search index built in plain JS at build time, one record per heading section, loaded lazily per language.
34
+ - Agent output: `/llms.txt`, `/llms-full.txt`, a `.md` twin for every entry page (collections with a `route`), and a Markdown 404 that suggests pages. The normal address always answers with HTML.
35
+ - Render modes: Git-only sites prerender everything; platform sites refresh changed pages after a publish; previews render with `noindex`.
36
+ - In dev, `nuxt dev` restarts when `ginko.config.ts` changes, and a brick that cannot reach the platform names itself and the fix.
37
+ - CLI `ginko`: `check` (schema, links and the share image, for every site; `--offline` without the platform) and `release` for CI, `export` for backups; `pnpm local:dev|import|pull` run only in the Ginko monorepo.
38
+
39
+ **Languages (`@lupinum/ginko-i18n`)**
40
+
41
+ - The only locale authority: routes, alternates, hreflang, messages with plurals, and formatters for dates, ranges, relative time, numbers, currency and lists, each in the site's time zone.
42
+ - Works on its own in any Nuxt 4.6+ or Nuxt 5 app, without the rest of Ginko.
43
+
44
+ **Components and docs (`@lupinum/ginko-components`, `@lupinum/ginko-docs`)**
45
+
46
+ - Content components with props defined as Ginko fields: callouts, tabs, code groups, steps, cards, accordion, figures, embeds (click to load), timeline, quiz and more. Each one can also be copied into an app with the shadcn-vue CLI, from a registry the docs build generates.
47
+ - Docs layer (`@lupinum/ginko-docs`; `npm create ginko@latest my-docs -- --preset docs` starts a site with it): navigation, section tabs, breadcrumbs, table of contents with scroll spy, previous/next, ⌘K search with heading results, blog with authors and RSS, changelog, copy page as Markdown, dark mode. A guide for switching from Nuxt Content and `@nuxtjs/i18n`. Math (KaTeX) and mermaid diagrams render and load only on pages that use them.
48
+
49
+ **Platform and Studio (hosted by Lupinum)**
50
+
51
+ The platform is a hosted service, not an npm package. A site connects to it with one setting; without it, the site reads its content from files.
52
+
53
+ - Quick tasks for the jobs owners do most ("Heute geschlossen", "Preis ändern"), phone first, with the full form behind "Alles bearbeiten".
54
+ - Files library: All, Recent, Unused, Missing alt text, Duplicates and folders; icon and list views; "used in" with links; replace a file and keep its references; a used file cannot be removed by mistake. Alt text set in the library is the default for every use, per language.
55
+ - Approvals, off by default: a task or agent key can need a yes. Reviewers see the change, approve, approve for a time, or reject with a sentence; the author sees the reason. Releases publish several changes together.
56
+ - History with "show changes", "back to this version" and undo; drafts and scheduled publishing; trash with restore; translation status per language; "why is this not on the website?".
57
+ - Team and invitations with per-task rights, sign-in with an emailed code, AI agent keys with an MCP tool catalog derived from the operations, and weekly reminder mails.
58
+ - Long forms group their fields under headings or in tabs (`group` on a field, `layout: 'tabs'` on a brick); phones always show headings.
59
+ - A rich text editor (TipTap 3) that writes Markdown, where untouched text stays byte for byte. Headings, lists, task lists, quotes, code blocks, tables, images from the library, embeds, registry components with prop forms, slash and bubble menus, Markdown shortcuts and paste, source mode, find and replace (⌘F), a drag handle and Alt+↑/↓ to move blocks, character counter, a link card for pasted URLs, uploads with progress, and a formatting bar above the phone keyboard.
60
+ - Delete a site, with seven days to cancel, and delete your own account. The last owner of a site must hand it over or delete it first.
61
+ - Studio in German, English and Spanish (beta), light and dark.
62
+
63
+ **Presets (`@lupinum/ginko-preset-gastro`, `create-ginko`)**
64
+
65
+ - Gastro: opening hours with exceptions, seasons, holidays and new hours from a date; lunch week with planning ahead and an optional PDF; menu with prices from a date; notices that expire; events; JSON-LD for Restaurant, Menu, opening hours and Event; a printable lunch sheet.
66
+ - `create-ginko` starts a new site, plain or with `--preset gastro|docs`. Starters install with npm, pnpm 11 and bun, carry a `typecheck` script, a page description and share cards (`nuxt-og-image` with Takumi, Nuxt 4.6.1 or later; set `NUXT_APP_SECRET` where the site runs, `.env` has one), and create-ginko warns on an unsupported Node.
67
+
68
+ **Not in 0.4**
69
+
70
+ Real-time co-editing comes next. Also not included: an agency preset, the editor UI as a shadcn-vue registry, OAuth for agent connectors, and a Google Business sync.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Lupinum OG
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/README.md CHANGED
@@ -1,3 +1,56 @@
1
- # Temporary Holding Version
1
+ # create-ginko
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Start a Nuxt site with [Ginko](https://github.com/lupinum-dev/ginko/tree/main/packages/ginko):
4
+
5
+ ```bash
6
+ npm create ginko@latest my-site
7
+ cd my-site
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ The new site reads its content from Markdown in `content/` and needs no platform. To let an owner
13
+ edit a brick on the Ginko platform instead, remove `source: 'files'` from it in `ginko.config.ts`,
14
+ and give the single's required fields a `default` or make them optional (`f.markdown`, `f.image` and `f.file` cannot have a default, so set `optional: true` on them). See
15
+ [Platform and CLI](https://github.com/lupinum-dev/ginko/blob/main/sites/docs/content/docs/12.plattform-und-cli.en.md).
16
+
17
+ The sample site is English. `--locale de` creates it in German.
18
+
19
+ The site id (`site` in `ginko.config.ts`) comes from the folder name: lower case, every run of other characters
20
+ becomes `-`, at most 40 characters, and it must start with a letter. When the id differs from the folder name, the
21
+ command says so. Change `site` in `ginko.config.ts` if you want another id.
22
+
23
+ ## Options
24
+
25
+ Write the options after `--`. npm keeps options before `--` for itself and passes on only their values; the
26
+ command then stops and shows the right form. pnpm, yarn and bun accept the same command.
27
+
28
+ ```bash
29
+ npm create ginko@latest my-site -- --locale de
30
+ npm create ginko@latest my-restaurant -- --preset gastro
31
+ npm create ginko@latest my-docs -- --preset docs
32
+ ```
33
+
34
+ | Option | Values | Default |
35
+ |---|---|---|
36
+ | `--preset <name>` (or `--preset=<name>`) | `gastro`, `docs` | the plain starter |
37
+ | `--locale <code>` (or `--locale=<code>`) | `en`, `de` | `en` |
38
+ | `-h`, `--help` | | |
39
+
40
+ The command always says which starter and language it used. An unknown option, preset or locale stops
41
+ with exit code 2 (a usage error, as in the `ginko` CLI) and creates nothing. A folder that exists and is not
42
+ empty stops with exit code 1.
43
+
44
+ ## Presets
45
+
46
+ | Preset | Package | What the starter shows | Platform needed? |
47
+ |---|---|---|---|
48
+ | `gastro` | `@lupinum/ginko-preset-gastro` | Opening hours and a menu, both read from files | Only for the preset's other bricks (notices, lunch, events, gallery) and the owner's quick tasks. The starter itself runs without it. |
49
+ | `docs` | `@lupinum/ginko-docs` | The docs layer (navigation, search) with two pages in `content/docs/` | No |
50
+
51
+ Each preset is a small folder in `presets/` that is copied over `template/`; `presets/<name>-de` is its German
52
+ version. `template-de/` holds the German sample text of the plain starter.
53
+
54
+ ## License
55
+
56
+ MIT © Lupinum OG
package/index.mjs ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ // npm create ginko <folder> [-- --preset gastro|docs] [--locale en|de]: copies template/ into a new folder and sets
3
+ // the site id and the Ginko versions. The Ginko packages release together, so they get this package's own version.
4
+ // A preset is a small folder in presets/ that is copied over the base template and adds the preset's packages.
5
+ // German: template-de/ goes over the base template; a preset has its own folder `<preset>-de`.
6
+ import { randomBytes } from 'node:crypto'
7
+ import { cpSync, existsSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'
8
+ import { basename, join, resolve } from 'node:path'
9
+ import { supportsNode } from './node-version.mjs'
10
+
11
+ // preset name -> the packages it adds (its own package, then the Ginko peers of that package that the base template
12
+ // lacks; test/create-ginko.test.ts checks this against the packages' metadata), and what the next steps say about it
13
+ const presets = {
14
+ gastro: { packages: ['@lupinum/ginko-preset-gastro'], note: 'Hours and menu read from files. The preset\'s other bricks (notices, lunch, events, gallery) and the owner\'s quick tasks need the Ginko platform.' },
15
+ docs: { packages: ['@lupinum/ginko-docs'], note: 'Pages are Markdown files in content/docs. No platform needed.' },
16
+ }
17
+ const langs = { en: 'English', de: 'German' }
18
+ const command = 'npm create ginko@latest'
19
+ const usage = `Usage: ${command} <folder> -- [--preset gastro|docs] [--locale en|de]
20
+
21
+ Creates a Nuxt site with Ginko that reads its content from Markdown files.
22
+
23
+ --preset <name> start from a preset: gastro (restaurant) or docs (documentation)
24
+ --locale <code> locale code of the sample site: en (default) or de
25
+ -h, --help show this help
26
+
27
+ With npm, write -- before the options. Without it, npm keeps them for itself.`
28
+
29
+ /** Prints the problem and the usage, and stops without creating anything. Exit 2 is a usage error, as in `ginko`. */
30
+ function fail(message) {
31
+ console.error(`${message}\n\n${usage}`)
32
+ process.exit(2)
33
+ }
34
+
35
+ const args = process.argv.slice(2)
36
+ let target
37
+ let preset
38
+ let lang = 'en'
39
+ for (let i = 0; i < args.length; i++) {
40
+ const arg = args[i]
41
+ // pnpm and yarn may pass the `--` of `pnpm create ginko my-site -- --preset gastro` through.
42
+ if (arg === '--') continue
43
+ if (arg === '-h' || arg === '--help') { console.log(usage); process.exit(0) }
44
+ if (arg === '--preset' || arg === '--locale') {
45
+ const value = args[++i]
46
+ if (!value || value.startsWith('-')) fail(`${arg} needs a value.`)
47
+ if (arg === '--preset') preset = value
48
+ else lang = value
49
+ }
50
+ else if (arg.startsWith('--preset=')) preset = arg.slice('--preset='.length)
51
+ else if (arg.startsWith('--locale=')) lang = arg.slice('--locale='.length)
52
+ else if (arg.startsWith('-')) fail(`Unknown option "${arg}".`)
53
+ else if (target === undefined) target = arg
54
+ // npm drops `--preset` written without `--` and passes only its value, so a second name is almost always that.
55
+ else if (Object.hasOwn(presets, arg)) fail(`Unexpected argument "${arg}". npm drops --preset when it comes before --. Write:\n ${command} ${target} -- --preset ${arg}`)
56
+ else if (Object.hasOwn(langs, arg)) fail(`Unexpected argument "${arg}". npm drops --locale when it comes before --. Write:\n ${command} ${target} -- --locale ${arg}`)
57
+ else fail(`Unexpected argument "${arg}". Give one folder name.`)
58
+ }
59
+ if (preset !== undefined && !Object.hasOwn(presets, preset)) {
60
+ fail(`Unknown preset "${preset}". Choose one of: ${Object.keys(presets).join(', ')}.`)
61
+ }
62
+ if (!Object.hasOwn(langs, lang)) fail(`Unknown language "${lang}". Choose one of: ${Object.keys(langs).join(', ')}.`)
63
+ const presetDir = preset && (lang === 'en' ? preset : `${preset}-${lang}`)
64
+ if (presetDir && !existsSync(new URL(`./presets/${presetDir}`, import.meta.url))) {
65
+ fail(`The ${preset} preset has no ${langs[lang]} version yet. Leave out --locale ${lang}.`)
66
+ }
67
+ if (!target) fail('Give the name of the folder to create.')
68
+ const dir = resolve(target)
69
+ if (existsSync(dir) && readdirSync(dir).length) {
70
+ console.error(`The folder ${target} exists and is not empty. Choose another name.`)
71
+ process.exit(1)
72
+ }
73
+ const site = basename(dir).toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'my-site'
74
+ if (!/^[a-z]/.test(site)) {
75
+ console.error(`The site id "${site}" must start with a letter. Choose another folder name.`)
76
+ process.exit(1)
77
+ }
78
+ const own = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'))
79
+ // A warning, not an error: the site is still created, and the user may run it on another Node.
80
+ if (!supportsNode(process.versions.node, own.engines.node)) {
81
+ console.warn(`Warning: Ginko needs Node ${own.engines.node}, but this is Node ${process.versions.node}. The site is created, but install and dev may fail or print warnings. Switch to a supported Node version before you run "install".\n`)
82
+ }
83
+ cpSync(new URL('./template', import.meta.url), dir, { recursive: true })
84
+ // npm drops .gitignore from published packages, so the template carries it as `gitignore`.
85
+ renameSync(join(dir, 'gitignore'), join(dir, '.gitignore'))
86
+ if (presetDir) {
87
+ // The base page and sample text give way to the preset's own.
88
+ rmSync(join(dir, 'content/start.md'))
89
+ rmSync(join(dir, 'app/pages/index.vue'))
90
+ cpSync(new URL(`./presets/${presetDir}`, import.meta.url), dir, { recursive: true })
91
+ }
92
+ else if (lang !== 'en') cpSync(new URL(`./template-${lang}`, import.meta.url), dir, { recursive: true })
93
+ for (const file of ['package.json', 'ginko.config.ts']) {
94
+ const path = join(dir, file)
95
+ writeFileSync(path, readFileSync(path, 'utf8').replaceAll('{{SITE}}', site).replaceAll('{{VERSION}}', own.version))
96
+ }
97
+ if (preset) {
98
+ const path = join(dir, 'package.json')
99
+ const pkg = JSON.parse(readFileSync(path, 'utf8'))
100
+ for (const name of presets[preset].packages) pkg.dependencies[name] = `^${own.version}`
101
+ pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)))
102
+ writeFileSync(path, `${JSON.stringify(pkg, null, 2)}\n`)
103
+ }
104
+
105
+ // The share cards (nuxt-og-image) sign their URLs with this secret; `nuxt dev` and `nuxt preview` read .env.
106
+ writeFileSync(join(dir, '.env'), `# Signs the share card URLs. Set the same variable where the site runs.\nNUXT_APP_SECRET=${randomBytes(32).toString('hex')}\n`)
107
+
108
+ // The next steps name the package manager that ran us (npm, pnpm, yarn or bun), and quote a folder a shell would split.
109
+ const agent = (process.env.npm_config_user_agent ?? '').split('/')[0]
110
+ const pm = ['pnpm', 'yarn', 'bun'].includes(agent) ? agent : 'npm'
111
+ const cd = /^[\w./-]+$/.test(target) ? target : `'${target.replaceAll('\'', '\'\\\'\'')}'`
112
+ console.log(`Created ${target} from the ${preset ? `${preset} preset` : 'plain starter'} in ${langs[lang]}.`)
113
+ if (site !== basename(dir)) console.log(`The site id is "${site}": the folder name in lower case, with other characters replaced by "-", at most 40 characters. Change \`site\` in ginko.config.ts to use another one.`)
114
+ if (preset) console.log(presets[preset].note)
115
+ // npm keeps options for itself unless they come after --; pnpm, yarn and bun pass them on.
116
+ const example = pm === 'npm' ? `${command} my-site -- --preset gastro` : `${pm} create ginko my-site --preset gastro`
117
+ console.log(`Other starters: --preset gastro, --preset docs; German sample: --locale de.${pm === 'npm' ? ' With npm, put -- before them:' : ''}
118
+ ${example}
119
+
120
+ Next:
121
+
122
+ cd ${cd}
123
+ ${pm} install
124
+ ${pm} run dev
125
+
126
+ Before you deploy, set NUXT_APP_SECRET where the site runs (any 32 or more random characters, .env has one).
127
+ Without it, the share cards of a server-rendered site answer with an error; a static build needs none.
128
+ `)
@@ -0,0 +1,10 @@
1
+ // Is this Node version inside an `engines.node` range of caret alternatives ("^22.18.0 || ^24.0.0")?
2
+ // Only that shape is supported: it is what all Ginko packages declare.
3
+ export function supportsNode(version, range) {
4
+ const [major, minor, patch] = version.split('.').map(Number)
5
+ return range.split('||').some((alternative) => {
6
+ const [wantMajor, wantMinor = 0, wantPatch = 0] = alternative.trim().replace(/^\^/, '').split('.').map(Number)
7
+ if (major !== wantMajor) return false
8
+ return minor > wantMinor || (minor === wantMinor && patch >= wantPatch)
9
+ })
10
+ }
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "create-ginko",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.4.0",
4
+ "type": "module",
5
+ "description": "Start a Ginko site: npm create ginko my-site",
6
+ "bin": {
7
+ "create-ginko": "./index.mjs"
8
+ },
9
+ "files": [
10
+ "index.mjs",
11
+ "node-version.mjs",
12
+ "template",
13
+ "template-de",
14
+ "presets",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Lupinum OG <info@lupinum.com> (https://lupinum.com)",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/lupinum-dev/ginko.git",
22
+ "directory": "packages/create-ginko"
23
+ },
24
+ "homepage": "https://github.com/lupinum-dev/ginko",
25
+ "bugs": {
26
+ "url": "https://github.com/lupinum-dev/ginko/issues"
27
+ },
28
+ "engines": {
29
+ "node": "^22.18.0 || ^24.0.0 || ^26.0.0"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
6
34
  }
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <NuxtLayout>
3
+ <NuxtPage />
4
+ </NuxtLayout>
5
+ </template>
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: Getting started
3
+ description: What this site is and how to add a page.
4
+ ---
5
+
6
+ ## Add a page
7
+
8
+ Every page is a Markdown file in `content/docs/`. The number before the name sets the order in the side navigation.
9
+
10
+ ## Next
11
+
12
+ Read [Writing pages]($docs.writing-pages) to see the frontmatter fields.
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Writing pages
3
+ description: The frontmatter fields of a docs page.
4
+ ---
5
+
6
+ ## Frontmatter
7
+
8
+ - `title` and `description` appear at the top of the page and in search.
9
+ - `section` groups pages in the side navigation.
10
+ - `parent` nests a page under another one.
11
+
12
+ ::callout
13
+ Change a file, save, and the page updates.
14
+ ::
@@ -0,0 +1,21 @@
1
+ // A documentation site: the layout, side navigation and search come from @lupinum/ginko-docs.
2
+ // Every page is a Markdown file in content/docs/.
3
+ import { defineSite } from '@lupinum/ginko'
4
+ import { components, docsCollection } from '@lupinum/ginko-docs/config'
5
+
6
+ const locales = ['en'] as const
7
+
8
+ export default defineSite({
9
+ site: '{{SITE}}',
10
+ name: '{{SITE}}',
11
+ url: 'https://example.com',
12
+ timeZone: 'Europe/Vienna', // change for your country
13
+ locales,
14
+ currency: 'EUR', // change for your country
15
+ pages: { home: '/' },
16
+ // Shown in search results and link previews. Replace it with one sentence about your site.
17
+ // `color`: the brand colour of the generated share cards.
18
+ seo: { description: 'Documentation for our product.', color: '#047857' },
19
+ components, // the Markdown components (::callout, ::tabs …) and their checked props
20
+ bricks: { docs: docsCollection({ locales }) },
21
+ })
@@ -0,0 +1,9 @@
1
+ import ginko from './ginko.config.ts'
2
+
3
+ export default defineNuxtConfig({
4
+ compatibilityDate: '2026-10-01',
5
+ extends: ['@lupinum/ginko-docs'],
6
+ // nuxt-og-image draws a share card for every page (Ginko passes title, image, logo and `seo.color`).
7
+ modules: ['@lupinum/ginko/nuxt', 'nuxt-og-image'],
8
+ ginko,
9
+ })
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <NuxtLayout>
3
+ <NuxtPage />
4
+ </NuxtLayout>
5
+ </template>
@@ -0,0 +1,12 @@
1
+ ---
2
+ title: Einstieg
3
+ description: Was diese Seite ist und wie du eine Seite ergänzt.
4
+ ---
5
+
6
+ ## Eine Seite ergänzen
7
+
8
+ Jede Seite ist eine Markdown-Datei in `content/docs/`. Die Zahl vor dem Namen legt die Reihenfolge in der Seitennavigation fest.
9
+
10
+ ## Weiter
11
+
12
+ Lies [Seiten schreiben]($docs.seiten-schreiben), um die Felder im Frontmatter zu sehen.
@@ -0,0 +1,14 @@
1
+ ---
2
+ title: Seiten schreiben
3
+ description: Die Felder im Frontmatter einer Doku-Seite.
4
+ ---
5
+
6
+ ## Frontmatter
7
+
8
+ - `title` und `description` stehen oben auf der Seite und in der Suche.
9
+ - `section` fasst Seiten in der Seitennavigation zu Gruppen zusammen.
10
+ - `parent` hängt eine Seite unter eine andere.
11
+
12
+ ::callout
13
+ Ändere eine Datei, speichere sie, und die Seite aktualisiert sich.
14
+ ::
@@ -0,0 +1,21 @@
1
+ // Eine Doku-Seite: Layout, Seitennavigation und Suche kommen aus @lupinum/ginko-docs.
2
+ // Jede Seite ist eine Markdown-Datei in content/docs/.
3
+ import { defineSite } from '@lupinum/ginko'
4
+ import { components, docsCollection } from '@lupinum/ginko-docs/config'
5
+
6
+ const locales = ['de'] as const
7
+
8
+ export default defineSite({
9
+ site: '{{SITE}}',
10
+ name: '{{SITE}}',
11
+ url: 'https://example.com',
12
+ timeZone: 'Europe/Vienna', // für dein Land ändern
13
+ locales,
14
+ currency: 'EUR', // für dein Land ändern
15
+ pages: { home: '/' },
16
+ // Steht in Suchergebnissen und Link-Vorschauen. Ersetze es durch einen Satz über deine Website.
17
+ // `color`: the brand colour of the generated share cards.
18
+ seo: { description: 'Dokumentation zu unserem Produkt.', color: '#047857' },
19
+ components, // die Markdown-Komponenten (::callout, ::tabs …) und ihre geprüften Props
20
+ bricks: { docs: docsCollection({ locales }) },
21
+ })
@@ -0,0 +1,9 @@
1
+ import ginko from './ginko.config.ts'
2
+
3
+ export default defineNuxtConfig({
4
+ compatibilityDate: '2026-10-01',
5
+ extends: ['@lupinum/ginko-docs'],
6
+ // nuxt-og-image draws a share card for every page (Ginko passes title, image, logo and `seo.color`).
7
+ modules: ['@lupinum/ginko/nuxt', 'nuxt-og-image'],
8
+ ginko,
9
+ })
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import { formatMoney } from '@lupinum/ginko'
3
+ import { priceOn, timesOf, weekdayNames, type Weekday } from '@lupinum/ginko-preset-gastro'
4
+
5
+ const hours = useGinko('hours')
6
+ const menu = useGinko('menu')
7
+ const today = useGinkoToday()
8
+ const { language } = useI18n()
9
+ const categories = [['starters', 'Starters'], ['soups', 'Soups'], ['mains', 'Mains'], ['desserts', 'Desserts'], ['drinks', 'Drinks']] as const
10
+ useGinkoSeo({ title: 'Menu and opening hours' })
11
+ </script>
12
+
13
+ <template>
14
+ <main>
15
+ <h1>Opening hours</h1>
16
+ <table>
17
+ <tbody>
18
+ <tr v-for="d in hours.data.week" :key="d.day">
19
+ <th scope="row">{{ weekdayNames.en[d.day as Weekday] }}</th>
20
+ <td>{{ d.closed ? 'Closed' : timesOf(d, 'and') }}</td>
21
+ </tr>
22
+ </tbody>
23
+ </table>
24
+ <h1>Menu</h1>
25
+ <section v-for="[key, title] in categories.filter(([k]) => menu.items.some((i) => i.data.category === k))" :key="key">
26
+ <h2>{{ title }}</h2>
27
+ <ul>
28
+ <li v-for="dish in menu.items.filter((i) => i.data.category === key)" :key="dish.id">
29
+ <strong>{{ dish.data.name }}</strong>, {{ formatMoney(priceOn(dish.data, today), language) }}
30
+ <br>{{ dish.data.description }}
31
+ </li>
32
+ </ul>
33
+ </section>
34
+ </main>
35
+ </template>
@@ -0,0 +1,12 @@
1
+ ---
2
+ week:
3
+ - { day: mo, closed: true }
4
+ - { day: tu, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "22:00" }
5
+ - { day: we, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "22:00" }
6
+ - { day: th, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "22:00" }
7
+ - { day: fr, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "23:00" }
8
+ - { day: sa, closed: false, from1: "11:30", to1: "23:00" }
9
+ - { day: su, closed: false, from1: "11:30", to1: "20:00" }
10
+ exceptions: []
11
+ holidayNoticeDays: 14
12
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: soups
3
+ name: Tomato soup
4
+ description: Roasted tomatoes, basil and a spoon of cream.
5
+ price: 6.5
6
+ veg: vegetarian
7
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: mains
3
+ name: House burger
4
+ description: Beef patty, cheddar, pickles and fries.
5
+ price: 14.9
6
+ veg: none
7
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: starters
3
+ name: Garden salad
4
+ description: Seasonal leaves, seeds and lemon dressing.
5
+ price: 8.5
6
+ veg: vegan
7
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: desserts
3
+ name: Apple tart
4
+ description: Warm apple tart with vanilla ice cream.
5
+ price: 5.9
6
+ veg: vegetarian
7
+ ---
@@ -0,0 +1,23 @@
1
+ // Opening hours and a menu from the gastro preset, both read from Markdown files in content/.
2
+ // The preset's other bricks (notices, lunch, events, gallery) and the owner's quick tasks need the Ginko platform.
3
+ import { defineSite } from '@lupinum/ginko'
4
+ import { hours, menu } from '@lupinum/ginko-preset-gastro'
5
+
6
+ export default defineSite({
7
+ site: '{{SITE}}',
8
+ name: '{{SITE}}',
9
+ url: 'https://example.com',
10
+ timeZone: 'Europe/Vienna', // change for your country
11
+ locales: ['en'],
12
+ currency: 'EUR', // change for your country
13
+ pages: { home: '/' },
14
+ // Shown in search results and link previews. Replace it with one sentence about your site.
15
+ // `color`: the brand colour of the generated share cards.
16
+ seo: { description: 'Opening hours and menu of our restaurant.', color: '#1e4032' },
17
+ bricks: {
18
+ // content/hours.md: weekly hours. The hours rules (hoursOn, todayState) count Austrian public holidays; pass them calendar: 'DE', 'SG' or 'none' to change that.
19
+ hours: hours({ source: 'files' }),
20
+ // content/menu/: one file per dish. allergens: 'none' leaves out the allergen field.
21
+ menu: menu({ allergens: 'none', source: 'files' }),
22
+ },
23
+ })
@@ -0,0 +1,35 @@
1
+ <script setup lang="ts">
2
+ import { formatMoney } from '@lupinum/ginko'
3
+ import { priceOn, timesOf, weekdayNames, type Weekday } from '@lupinum/ginko-preset-gastro'
4
+
5
+ const hours = useGinko('hours')
6
+ const menu = useGinko('menu')
7
+ const today = useGinkoToday()
8
+ const { language } = useI18n()
9
+ const categories = [['starters', 'Vorspeisen'], ['soups', 'Suppen'], ['mains', 'Hauptspeisen'], ['desserts', 'Nachspeisen'], ['drinks', 'Getränke']] as const
10
+ useGinkoSeo({ title: 'Speisekarte und Öffnungszeiten' })
11
+ </script>
12
+
13
+ <template>
14
+ <main>
15
+ <h1>Öffnungszeiten</h1>
16
+ <table>
17
+ <tbody>
18
+ <tr v-for="d in hours.data.week" :key="d.day">
19
+ <th scope="row">{{ weekdayNames.de[d.day as Weekday] }}</th>
20
+ <td>{{ d.closed ? 'Ruhetag' : timesOf(d, 'und') }}</td>
21
+ </tr>
22
+ </tbody>
23
+ </table>
24
+ <h1>Speisekarte</h1>
25
+ <section v-for="[key, title] in categories.filter(([k]) => menu.items.some((i) => i.data.category === k))" :key="key">
26
+ <h2>{{ title }}</h2>
27
+ <ul>
28
+ <li v-for="dish in menu.items.filter((i) => i.data.category === key)" :key="dish.id">
29
+ <strong>{{ dish.data.name }}</strong>, {{ formatMoney(priceOn(dish.data, today), language) }}
30
+ <br>{{ dish.data.description }}
31
+ </li>
32
+ </ul>
33
+ </section>
34
+ </main>
35
+ </template>
@@ -0,0 +1,12 @@
1
+ ---
2
+ week:
3
+ - { day: mo, closed: true }
4
+ - { day: tu, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "22:00" }
5
+ - { day: we, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "22:00" }
6
+ - { day: th, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "22:00" }
7
+ - { day: fr, closed: false, from1: "11:30", to1: "14:00", from2: "17:30", to2: "23:00" }
8
+ - { day: sa, closed: false, from1: "11:30", to1: "23:00" }
9
+ - { day: su, closed: false, from1: "11:30", to1: "20:00" }
10
+ exceptions: []
11
+ holidayNoticeDays: 14
12
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: soups
3
+ name: Tomatensuppe
4
+ description: Geröstete Tomaten, Basilikum und ein Löffel Rahm.
5
+ price: 6.5
6
+ veg: vegetarian
7
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: mains
3
+ name: Hausburger
4
+ description: Rindfleisch-Patty, Cheddar, Essiggurken und Pommes.
5
+ price: 14.9
6
+ veg: none
7
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: starters
3
+ name: Gartensalat
4
+ description: Blattsalate der Saison, Kerne und Zitronendressing.
5
+ price: 8.5
6
+ veg: vegan
7
+ ---
@@ -0,0 +1,7 @@
1
+ ---
2
+ category: desserts
3
+ name: Apfelkuchen
4
+ description: Warmer Apfelkuchen mit Vanilleeis.
5
+ price: 5.9
6
+ veg: vegetarian
7
+ ---
@@ -0,0 +1,25 @@
1
+ // Öffnungszeiten und Speisekarte aus dem Gastro-Preset, beide aus Markdown-Dateien in content/.
2
+ // Die anderen Bausteine des Presets (Hinweise, Mittagsmenü, Veranstaltungen, Galerie) und die Schnellaufgaben
3
+ // für Inhaber brauchen die Ginko-Plattform.
4
+ import { defineSite } from '@lupinum/ginko'
5
+ import { hours, menu } from '@lupinum/ginko-preset-gastro'
6
+
7
+ export default defineSite({
8
+ site: '{{SITE}}',
9
+ name: '{{SITE}}',
10
+ url: 'https://example.com',
11
+ timeZone: 'Europe/Vienna', // für dein Land ändern
12
+ locales: ['de'],
13
+ currency: 'EUR', // für dein Land ändern
14
+ pages: { home: '/' },
15
+ // Steht in Suchergebnissen und Link-Vorschauen. Ersetze es durch einen Satz über deine Website.
16
+ // `color`: the brand colour of the generated share cards.
17
+ seo: { description: 'Öffnungszeiten und Speisekarte unseres Restaurants.', color: '#1e4032' },
18
+ bricks: {
19
+ // content/hours.md: die Öffnungszeiten der Woche. Die Regeln dazu (hoursOn, todayState) zählen die österreichischen
20
+ // Feiertage; mit calendar: 'DE', 'SG' oder 'none' änderst du das.
21
+ hours: hours({ source: 'files' }),
22
+ // content/menu/: eine Datei pro Gericht. allergens: 'none' lässt das Feld für Allergene weg.
23
+ menu: menu({ allergens: 'none', source: 'files' }),
24
+ },
25
+ })
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <NuxtPage />
3
+ </template>
@@ -0,0 +1,12 @@
1
+ <script setup lang="ts">
2
+ const start = useGinko('start')
3
+ // The page title and description come from `name` and `seo` in ginko.config.ts. Pass `{ title }` to name a page.
4
+ useGinkoSeo({})
5
+ </script>
6
+
7
+ <template>
8
+ <main>
9
+ <h1>{{ start.data.title }}</h1>
10
+ <GinkoMarkdown :value="start.data.body" />
11
+ </main>
12
+ </template>
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Hello Ginko
3
+ ---
4
+
5
+ This text lives in `content/start.md`. Change it, and the page shows the new text.
@@ -0,0 +1,24 @@
1
+ // What content this site has. Bricks with `source: 'files'` read Markdown from content/.
2
+ // To let an owner edit a brick on the Ginko platform instead, remove `source`; f.markdown then stores the text on the platform.
3
+ // Then give the single's required fields a `default` or make them optional (`f.markdown`, `f.image` and `f.file` cannot have a default, so set `optional: true` on them).
4
+ import { defineSite, f, single } from '@lupinum/ginko'
5
+
6
+ export default defineSite({
7
+ site: '{{SITE}}',
8
+ name: '{{SITE}}',
9
+ url: 'https://example.com',
10
+ timeZone: 'Europe/Vienna', // change for your country
11
+ locales: ['en'],
12
+ currency: 'EUR', // change for your country
13
+ pages: { home: '/' },
14
+ // Shown in search results and link previews. Replace it with one sentence about your site.
15
+ // `color`: the brand colour of the generated share cards.
16
+ seo: { description: 'A new website built with Ginko.', color: '#1f2937' },
17
+ bricks: {
18
+ start: single({
19
+ label: 'Start page',
20
+ source: 'files',
21
+ fields: { title: f.text('Title'), body: f.markdown('Text', { profile: 'basic' }) },
22
+ }),
23
+ },
24
+ })
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ .env
3
+ .nuxt
4
+ .output
5
+ .data
6
+ dist
@@ -0,0 +1,8 @@
1
+ import ginko from './ginko.config.ts'
2
+
3
+ export default defineNuxtConfig({
4
+ compatibilityDate: '2026-10-01',
5
+ // nuxt-og-image draws a share card for every page (Ginko passes title, image, logo and `seo.color`).
6
+ modules: ['@lupinum/ginko/nuxt', 'nuxt-og-image'],
7
+ ginko,
8
+ })
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "{{SITE}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "engines": {
6
+ "node": "^22.18.0 || ^24.0.0 || ^26.0.0"
7
+ },
8
+ "scripts": {
9
+ "dev": "nuxt dev",
10
+ "build": "nuxt build",
11
+ "preview": "nuxt preview",
12
+ "typecheck": "nuxt typecheck",
13
+ "postinstall": "nuxt prepare"
14
+ },
15
+ "dependencies": {
16
+ "@lupinum/ginko": "^{{VERSION}}",
17
+ "@lupinum/ginko-content": "^{{VERSION}}",
18
+ "@lupinum/ginko-i18n": "^{{VERSION}}",
19
+ "@takumi-rs/core": "^2.15.0",
20
+ "nuxt": "^4.6.1",
21
+ "nuxt-og-image": "^7.0.0",
22
+ "vue": "^3.5.0"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^6.0.3",
26
+ "vue-tsc": "^3.1.5"
27
+ }
28
+ }
@@ -0,0 +1,4 @@
1
+ # pnpm 11 stops `pnpm install` with an error until you say which packages may run install scripts.
2
+ # esbuild (used by Nuxt and Vite) downloads its binary this way. npm and bun ignore this file.
3
+ allowBuilds:
4
+ esbuild: true
@@ -0,0 +1,9 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./.nuxt/tsconfig.app.json" },
5
+ { "path": "./.nuxt/tsconfig.server.json" },
6
+ { "path": "./.nuxt/tsconfig.shared.json" },
7
+ { "path": "./.nuxt/tsconfig.node.json" }
8
+ ]
9
+ }
@@ -0,0 +1,5 @@
1
+ ---
2
+ title: Hallo Ginko
3
+ ---
4
+
5
+ Dieser Text liegt in `content/start.md`. Ändere ihn, und die Seite zeigt den neuen Text.
@@ -0,0 +1,23 @@
1
+ // What content this site has. Bricks with `source: 'files'` read Markdown from content/.
2
+ // To let an owner edit a brick on the Ginko platform instead, remove `source`; f.markdown then stores the text on the platform.
3
+ // Then give the single's required fields a `default` or make them optional (`f.markdown`, `f.image` and `f.file` cannot have a default, so set `optional: true` on them).
4
+ import { defineSite, f, single } from '@lupinum/ginko'
5
+
6
+ export default defineSite({
7
+ site: '{{SITE}}',
8
+ name: '{{SITE}}',
9
+ url: 'https://example.com',
10
+ timeZone: 'Europe/Vienna', // für dein Land ändern
11
+ locales: ['de'],
12
+ currency: 'EUR', // für dein Land ändern
13
+ pages: { home: '/' },
14
+ // Steht in Suchergebnissen und Link-Vorschauen. Ersetze es durch einen Satz über deine Website.
15
+ seo: { description: 'Eine neue Website mit Ginko.' },
16
+ bricks: {
17
+ start: single({
18
+ label: 'Startseite',
19
+ source: 'files',
20
+ fields: { title: f.text('Titel'), body: f.markdown('Text', { profile: 'basic' }) },
21
+ }),
22
+ },
23
+ })