@riebeckite/honox 0.0.1 → 0.0.3

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/index.js CHANGED
@@ -154,424 +154,1034 @@ function pathToFileUrl(filePath) {
154
154
  return `file:///${filePath.replace(/\\/g, "/").replace(/^([A-Za-z]):/, "$1:")}`;
155
155
  }
156
156
 
157
- // src/ssg_plugin.ts
158
- import { defaultExtensionMap, toSSG } from "hono/ssg";
159
- import { relative } from "node:path";
160
- import { createServer } from "vite";
161
- var defaultEntry = "./src/index.tsx";
162
- function riebeckiteSsg(options = {}) {
163
- const virtualId = "virtual:riebeckite-ssg-void-entry";
164
- const resolvedVirtualId = `\0${virtualId}`;
165
- const entry = options.entry ?? defaultEntry;
166
- let resolvedConfig;
157
+ // src/honox_islands.ts
158
+ import fs3 from "node:fs/promises";
159
+ import path3 from "node:path";
160
+ import { parse } from "@babel/parser";
161
+ var honoxIslandDependencyPluginName = "inject-importing-islands";
162
+ var importingIslandsExport = "__importing_islands";
163
+ function replaceHonoxIslandDependencyPlugin(plugins) {
164
+ return [
165
+ ...plugins.map(disableHonoxIslandDependencyPlugin),
166
+ scopedIslandDependencyPlugin()
167
+ ];
168
+ }
169
+ function disableHonoxIslandDependencyPlugin(plugin) {
170
+ if (plugin instanceof Promise) {
171
+ return plugin.then(disableHonoxIslandDependencyPlugin);
172
+ }
173
+ if (plugin.name !== honoxIslandDependencyPluginName) return plugin;
174
+ return { ...plugin, transform: void 0 };
175
+ }
176
+ function scopedIslandDependencyPlugin() {
177
+ let appDirectory = "";
178
+ let islandDirectory = "";
167
179
  return {
168
- name: "riebeckite-ssg",
169
- apply: "build",
170
- enforce: "post",
171
- config() {
172
- return {
173
- build: {
174
- rollupOptions: {
175
- input: [virtualId]
176
- }
177
- }
178
- };
179
- },
180
+ name: "riebeckite-inject-importing-islands",
180
181
  configResolved(config) {
181
- resolvedConfig = config;
182
- },
183
- resolveId(id) {
184
- return id === virtualId ? resolvedVirtualId : void 0;
182
+ appDirectory = path3.resolve(config.root, "app");
183
+ islandDirectory = path3.join(appDirectory, "islands");
185
184
  },
186
- load(id) {
187
- return id === resolvedVirtualId ? 'console.log("suppress empty chunk message")' : void 0;
188
- },
189
- async generateBundle(_outputOptions, bundle) {
190
- const config = resolvedConfig;
191
- if (!config) {
192
- throw new Error("Riebeckite SSG could not resolve the Vite configuration.");
193
- }
194
- removeVirtualEntryChunk(bundle, resolvedVirtualId);
195
- const server2 = await createServer({
196
- root: config.root,
197
- define: config.define,
198
- resolve: {
199
- ...config.resolve,
200
- builtins: [...config.resolve.builtins, /^node:/]
201
- },
202
- plugins: [],
203
- build: { ssr: true },
204
- mode: config.mode
205
- });
206
- try {
207
- const module = await server2.ssrLoadModule(entry);
208
- const app = module.default;
209
- if (!app) {
210
- throw new Error(`Failed to find a default export from ${entry}.`);
211
- }
212
- const generatedHtml = [];
213
- const result = await toSSG(
214
- app,
215
- {
216
- writeFile: async (filePath, data) => {
217
- const fileName = relative(config.build.outDir, filePath).replaceAll(
218
- "\\",
219
- "/"
220
- );
221
- if (fileName.endsWith(".html") && typeof data === "string") {
222
- generatedHtml.push({ path: fileName, html: data });
223
- }
224
- this.emitFile({
225
- type: "asset",
226
- fileName,
227
- source: data
228
- });
229
- },
230
- async mkdir() {
231
- }
232
- },
233
- {
234
- dir: config.build.outDir,
235
- plugins: options.plugins ?? [],
236
- extensionMap: options.extensionMap ?? defaultExtensionMap
237
- }
238
- );
239
- if (!result.success) throw result.error;
240
- await emitGeneratedOutputs(
241
- module,
242
- (asset) => this.emitFile(asset)
243
- );
244
- inspectGeneratedHtmlPages(module, generatedHtml, {
245
- warn: (message) => this.warn(message),
246
- info: (message) => this.info(message)
247
- });
248
- } finally {
249
- await server2.close();
185
+ async transform(sourceCode, id) {
186
+ const modulePath = toFilePath(id);
187
+ if (!isInDirectory(modulePath, appDirectory) || !isJavaScriptModule(modulePath)) {
188
+ return;
250
189
  }
190
+ if (!await importsIsland(modulePath, sourceCode, this)) return;
191
+ return {
192
+ code: `${sourceCode}
193
+ export const ${importingIslandsExport} = true;
194
+ `,
195
+ map: null
196
+ };
251
197
  }
252
198
  };
253
- }
254
- async function emitGeneratedOutputs(module, emit) {
255
- const content = module.content;
256
- if (!content) return;
257
- const manifest = await content.getManifest();
258
- for (const output of manifest.generatedOutputs ?? []) {
259
- emit({
260
- type: "asset",
261
- fileName: output.path,
262
- source: output.content
263
- });
264
- }
265
- }
266
- function inspectGeneratedHtmlPages(module, pages, log) {
267
- const plugins = module.config?.plugins ?? [];
268
- for (const plugin of plugins) {
269
- const inspector = plugin.inspectGeneratedHtml;
270
- if (typeof inspector !== "function") continue;
271
- for (const page of pages) {
272
- for (const diagnostic of inspector(page) ?? []) {
273
- const label = `[${plugin.name ?? "plugin"}] ${page.path}: ${diagnostic.message}`;
274
- if (diagnostic.severity === "info") log.info(label);
275
- else log.warn(label);
199
+ async function importsIsland(modulePath, sourceCode, pluginContext) {
200
+ const visited = /* @__PURE__ */ new Set();
201
+ const visit = async (currentModulePath, currentSourceCode) => {
202
+ if (visited.has(currentModulePath)) return false;
203
+ visited.add(currentModulePath);
204
+ if (isInDirectory(currentModulePath, islandDirectory)) return true;
205
+ const importSources = getStaticImportSources(currentSourceCode);
206
+ for (const importSource of importSources) {
207
+ const resolved = await pluginContext.resolve(
208
+ importSource,
209
+ currentModulePath
210
+ );
211
+ if (!resolved) continue;
212
+ const dependencyPath = toFilePath(resolved.id);
213
+ if (!isInDirectory(dependencyPath, appDirectory) || !isJavaScriptModule(dependencyPath)) {
214
+ continue;
215
+ }
216
+ try {
217
+ const dependencySourceCode = await fs3.readFile(
218
+ dependencyPath,
219
+ "utf8"
220
+ );
221
+ if (await visit(dependencyPath, dependencySourceCode)) return true;
222
+ } catch {
223
+ }
276
224
  }
277
- }
225
+ return false;
226
+ };
227
+ return visit(modulePath, sourceCode);
278
228
  }
279
229
  }
280
- function removeVirtualEntryChunk(bundle, virtualEntryId) {
281
- for (const chunk of Object.values(bundle)) {
282
- if (chunk.type === "chunk" && chunk.moduleIds?.includes(virtualEntryId)) {
283
- delete bundle[chunk.fileName];
230
+ function getStaticImportSources(sourceCode) {
231
+ const ast = parse(sourceCode, {
232
+ sourceType: "module",
233
+ plugins: ["typescript", "jsx"]
234
+ });
235
+ return ast.program.body.flatMap((statement) => {
236
+ if (statement.type === "ImportDeclaration" || statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") {
237
+ return statement.source ? [statement.source.value] : [];
284
238
  }
285
- }
239
+ return [];
240
+ });
286
241
  }
287
-
288
- // src/ssg.ts
289
- var defaultSsgEntry = "./app/server.ts";
290
- function riebeckiteSsgExtensionMap() {
291
- return {
292
- "application/atom+xml": "xml",
293
- "application/json": "json",
294
- "application/rss+xml": "xml",
295
- "application/xml": "xml",
296
- "text/html": "html",
297
- "text/plain": "txt",
298
- "text/xml": "xml"
299
- };
242
+ function toFilePath(id) {
243
+ return id.split("?", 1)[0] ?? id;
300
244
  }
301
- function createRiebeckiteSsg(options = {}) {
302
- return riebeckiteSsg({
303
- ...options,
304
- entry: options.entry ?? defaultSsgEntry,
305
- extensionMap: {
306
- ...riebeckiteSsgExtensionMap(),
307
- ...options.extensionMap
308
- }
309
- });
245
+ function isInDirectory(filePath, directory) {
246
+ const relativePath = path3.relative(directory, filePath);
247
+ return relativePath !== "" && !relativePath.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relativePath);
248
+ }
249
+ function isJavaScriptModule(filePath) {
250
+ return /\.[cm]?[jt]sx?$/.test(filePath);
310
251
  }
311
252
 
312
- // src/vite_plugin.ts
313
- import path5 from "node:path";
253
+ // src/scaffold/index.ts
254
+ import fs4 from "node:fs/promises";
255
+ import path4 from "node:path";
314
256
 
315
- // src/asset_entries.ts
316
- import fs3 from "node:fs";
317
- import path3 from "node:path";
318
- import { uniqueStrings } from "@riebeckite/core";
319
- function writeRiebeckiteAssetEntries(config, paths2) {
320
- writeGeneratedFile(paths2.pluginStyles, createPluginStylesEntry(config));
321
- writeGeneratedFile(paths2.themeStyles, createThemeStylesEntry(config));
322
- }
323
- function createPluginStylesEntry(config) {
324
- return createStylesheetEntry(
325
- collectPluginStyleSpecifiers(config),
326
- "Plugin default styles, in resolved plugin order. Generated by @riebeckite/honox; do not edit. Import this before the theme stylesheet so a theme can override plugin defaults."
327
- );
328
- }
329
- function createThemeStylesEntry(config) {
330
- return createStylesheetEntry(
331
- config.theme.styles.map((style2) => style2.moduleSpecifier),
332
- "Theme styles. Generated by @riebeckite/honox; do not edit. Import this after the plugin stylesheet so the theme wins the plugin/theme cascade."
333
- );
257
+ // src/scaffold/localized-content.ts
258
+ var SCAFFOLD_LANGUAGES = [
259
+ "en",
260
+ "ja",
261
+ "zh-CN",
262
+ "es",
263
+ "de",
264
+ "fr",
265
+ "ko"
266
+ ];
267
+ function defaultLanguageForLocale(locale) {
268
+ const normalized = locale.toLowerCase();
269
+ if (normalized.startsWith("ja")) return "ja";
270
+ if (normalized.startsWith("zh")) return "zh-CN";
271
+ if (normalized.startsWith("es")) return "es";
272
+ if (normalized.startsWith("de")) return "de";
273
+ if (normalized.startsWith("fr")) return "fr";
274
+ if (normalized.startsWith("ko")) return "ko";
275
+ return "en";
276
+ }
277
+ var HELPERS = {
278
+ en: "https://github.com/Rerurate514/riebeckite/blob/main/docs/en/README.md",
279
+ ja: "https://github.com/Rerurate514/riebeckite/blob/main/docs/ja/README.md"
280
+ };
281
+ var PLUGIN_INDEX_URL = "https://github.com/Rerurate514/riebeckite/tree/main/packages/plugins";
282
+ var THEMES_INDEX_URL = "https://github.com/Rerurate514/riebeckite/tree/main/packages/themes";
283
+ function pluginReadmeUrl(slug) {
284
+ return `https://github.com/Rerurate514/riebeckite/blob/main/packages/plugins/${slug}/README.md`;
285
+ }
286
+ function themeReadmeUrl(slug) {
287
+ return `https://github.com/Rerurate514/riebeckite/blob/main/packages/themes/${slug}/README.md`;
288
+ }
289
+ function localizedContentFiles(variables) {
290
+ const defaultLang = defaultLanguageForLocale(variables.locale);
291
+ const pages = [
292
+ { basePath: "index", build: (language) => indexContent(variables, language) },
293
+ { basePath: "framework/plugins", build: (language) => pluginsContent(language) },
294
+ { basePath: "framework/themes", build: (language) => themesContent(language) }
295
+ ];
296
+ const files = [];
297
+ for (const page of pages) {
298
+ for (const language of SCAFFOLD_LANGUAGES) {
299
+ const suffix = language === defaultLang ? "" : `.${language}`;
300
+ files.push({
301
+ path: `content/${page.basePath}${suffix}.md`,
302
+ content: page.build(language)
303
+ });
304
+ }
305
+ }
306
+ return files;
334
307
  }
335
- function collectPluginStyleSpecifiers(config) {
336
- return config.plugins.flatMap(
337
- (plugin) => (plugin.assets ?? []).filter((asset) => asset.kind === "style").map((asset) => asset.moduleSpecifier)
338
- );
308
+ function read(text, language) {
309
+ return text[language];
339
310
  }
340
- function createStylesheetEntry(moduleSpecifiers, banner) {
341
- const imports = uniqueStrings(moduleSpecifiers).map((specifier) => `@import ${JSON.stringify(specifier)};`).join("\n");
342
- if (!banner) return imports;
343
- const header = `/* ${banner.replace(/\*\//g, "* /")} */`;
344
- return imports ? `${header}
345
- ${imports}
346
- ` : `${header}
347
- `;
311
+ function frontmatter() {
312
+ return ["---", "publish: true", "---", ""].join("\n");
348
313
  }
349
- function writeGeneratedFile(filePath, contents) {
350
- fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
351
- fs3.writeFileSync(filePath, contents);
314
+ function heading(level, text) {
315
+ return `${"#".repeat(level)} ${text}`;
352
316
  }
353
-
354
- // src/client_module.ts
355
- var clientModuleId = "virtual:riebeckite/client";
356
- var resolvedClientModuleId = `\0${clientModuleId}`;
357
- function riebeckiteClientModule(getConfig) {
358
- return {
359
- name: "riebeckite-client-module",
360
- resolveId(id) {
361
- if (id === clientModuleId) return resolvedClientModuleId;
362
- return null;
363
- },
364
- load(id) {
365
- if (id !== resolvedClientModuleId) return null;
366
- return createClientModule(getConfig());
367
- }
368
- };
317
+ function codeBlock(language, code) {
318
+ return ["```" + language, code, "```", ""].join("\n");
369
319
  }
370
- function createClientModule(config) {
371
- const imports = [];
372
- const initializers = [];
373
- for (const [index, entry] of config.plugins.flatMap((plugin) => plugin.clientEntries ?? []).entries()) {
374
- const localName = `pluginClientInitializer${index}`;
375
- const importTarget = entry.exportName ? `{ ${entry.exportName} as ${localName} }` : localName;
376
- imports.push(
377
- `import ${importTarget} from ${JSON.stringify(entry.moduleSpecifier)};`
378
- );
379
- initializers.push(localName);
320
+ var INDEX = {
321
+ lead: {
322
+ en: "Welcome to your Riebeckite site.",
323
+ ja: "あなたの Riebeckite サイトへようこそ。",
324
+ "zh-CN": "欢迎来到你的 Riebeckite 站点。",
325
+ es: "Bienvenido a tu sitio Riebeckite.",
326
+ de: "Willkommen auf deiner Riebeckite-Seite.",
327
+ fr: "Bienvenue sur votre site Riebeckite.",
328
+ ko: "Riebeckite 사이트에 오신 것을 환영합니다."
329
+ },
330
+ translatedNote: {
331
+ en: "Every page of this starter is available in seven languages. Switch with the selector below the page title.",
332
+ ja: "このスターターのすべてのページは 7 言語で利用できます。ページタイトル下のセレクターで切り替えられます。",
333
+ "zh-CN": "本模板的每个页面均提供七种语言版本,可用页面标题下方的选择器切换。",
334
+ es: "Cada página de este starter está disponible en siete idiomas. Cámbialos con el selector bajo el título.",
335
+ de: "Jede Seite dieses Starters ist in sieben Sprachen verfügbar. Wechsel mit dem Auswahlfeld unter dem Seitentitel.",
336
+ fr: "Chaque page de ce starter est disponible en sept langues. Changez avec le sélecteur sous le titre.",
337
+ ko: "이 스타터의 모든 페이지는 7개 언어로 제공됩니다. 페이지 제목 아래 선택기로 전환하세요."
338
+ },
339
+ whatHeading: {
340
+ en: "What is Riebeckite?",
341
+ ja: "Riebeckite とは",
342
+ "zh-CN": "什么是 Riebeckite?",
343
+ es: "¿Qué es Riebeckite?",
344
+ de: "Was ist Riebeckite?",
345
+ fr: "Qu'est-ce que Riebeckite ?",
346
+ ko: "Riebeckite란?"
347
+ },
348
+ whatBody: {
349
+ en: "Riebeckite is an extensible, content-first framework that builds fast static sites from plain Markdown — the same notes you keep in Obsidian. The ecosystem includes 50+ plugins and six themes, and this site demos both.",
350
+ ja: "Riebeckite は、プレーンな Markdown(Obsidian で管理しているのと同じノート)から高速な静的サイトを生成する、拡張性のあるコンテンツファーストのフレームワークです。エコシステムには 50 以上のプラグインと 6 つのテーマがあり、このサイトはその両方をデモしています。",
351
+ "zh-CN": "Riebeckite 是一个可扩展、内容优先的框架,可从纯 Markdown(即你在 Obsidian 中保存的笔记)构建快速的静态站点。生态包含 50+ 插件与六个主题,本站点同时演示两者。",
352
+ es: "Riebeckite es un framework extensible y orientado al contenido que construye sitios estáticos rápidos desde Markdown simple — las mismas notas que guardas en Obsidian. El ecosistema incluye más de 50 plugins y seis temas; este sitio demuestra ambos.",
353
+ de: "Riebeckite ist ein erweiterbares, inhaltsorientiertes Framework, das schnelle statische Seiten aus einfachem Markdown baut — denselben Notizen, die du in Obsidian führst. Das Ökosystem umfasst 50+ Plugins und sechs Themes; diese Seite demonstriert beides.",
354
+ fr: "Riebeckite est un framework extensible et centré sur le contenu qui construit des sites statiques rapides à partir de Markdown simple — les mêmes notes que vous gardez dans Obsidian. L'écosystème inclut plus de 50 plugins et six thèmes ; ce site en démontre les deux.",
355
+ ko: "Riebeckite는 일반 Markdown(Obsidian에서 관리하는 노트)에서 빠른 정적 사이트를 만드는 확장 가능한 콘텐츠 우선 프레임워크입니다. 생태계에는 50개 이상의 플러그인과 6개 테마가 있으며, 이 사이트가 그 두 가지를 보여줍니다."
356
+ },
357
+ exploreHeading: {
358
+ en: "Explore",
359
+ ja: "さらに深く",
360
+ "zh-CN": "继续探索",
361
+ es: "Explorar",
362
+ de: "Weiter erkunden",
363
+ fr: "Explorer",
364
+ ko: "더 살펴보기"
365
+ },
366
+ explorePlugins: {
367
+ en: "Plugins — representative packages grouped by capability",
368
+ ja: "プラグイン — 機能別の代表的なパッケージ",
369
+ "zh-CN": "插件 — 按能力分组的代表性包",
370
+ es: "Plugins — paquetes representativos por capacidad",
371
+ de: "Plugins — repräsentative Pakete nach Fähigkeit",
372
+ fr: "Plugins — paquets représentatifs par capacité",
373
+ ko: "플러그인 — 기능별 대표 패키지"
374
+ },
375
+ exploreThemes: {
376
+ en: "Themes — built-in design packages and how to switch",
377
+ ja: "テーマ — 同梱のデザインパッケージと切り替え方",
378
+ "zh-CN": "主题 — 内置设计包与切换方法",
379
+ es: "Temas — paquetes de diseño incluidos y cómo cambiar",
380
+ de: "Themes — mitgelieferte Design-Pakete und wie man wechselt",
381
+ fr: "Thèmes — paquets de design inclus et comment changer",
382
+ ko: "테마 — 내장 디자인 패키지와 전환 방법"
383
+ },
384
+ editHeading: {
385
+ en: "Edit this site",
386
+ ja: "サイトの編集",
387
+ "zh-CN": "编辑本站点",
388
+ es: "Editar este sitio",
389
+ de: "Diese Seite bearbeiten",
390
+ fr: "Éditer ce site",
391
+ ko: "사이트 편집"
392
+ },
393
+ editLead: {
394
+ en: "Content lives in `content/` as plain Markdown. Add a file, give it `publish: true` in the frontmatter, and it appears in the built site.",
395
+ ja: "コンテンツは `content/` にプレーンな Markdown として置きます。ファイルを追加してフロントマターに `publish: true` を書けば、ビルドされたサイトに反映されます。",
396
+ "zh-CN": "内容以纯 Markdown 存于 `content/`。新建文件并在 frontmatter 中写入 `publish: true`,它就会出现在构建后的站点中。",
397
+ es: "El contenido vive en `content/` como Markdown simple. Añade un archivo, pon `publish: true` en el frontmatter y aparecerá en el sitio compilado.",
398
+ de: "Inhalte liegen als einfaches Markdown in `content/`. Füge eine Datei hinzu, setze `publish: true` ins Frontmatter, und sie erscheint in der gebauten Seite.",
399
+ fr: "Le contenu vit dans `content/` en Markdown simple. Ajoutez un fichier, donnez-lui `publish: true` dans le frontmatter, et il apparaîtra dans le site construit.",
400
+ ko: "콘텐츠는 `content/`에 일반 Markdown으로 저장됩니다. 파일을 추가하고 프론트매터에 `publish: true`를 쓰면 빌드된 사이트에 나타납니다."
401
+ },
402
+ editL10n: {
403
+ en: "Localized pages use the `<base>.<lang>.md` convention next to the default file — for example `about.ja.md`. The l10n plugin serves them under `/lang/` paths and links them automatically.",
404
+ ja: "翻訳ページは既定ファイルの隣に `<base>.<lang>.md` の命名規則で置きます(例:`about.ja.md`)。l10n プラグインが `/lang/` パスの配下で配信し、自動的にリンクします。",
405
+ "zh-CN": "本地化页面采用默认文件旁的 `<base>.<lang>.md` 命名约定(例如 `about.ja.md`)。l10n 插件会在 `/lang/` 路径下提供服务并自动互链。",
406
+ es: "Las páginas localizadas usan la convención `<base>.<lang>.md` junto al archivo por defecto (p. ej. `about.ja.md`). El plugin l10n las sirve bajo rutas `/lang/` y las enlaza automáticamente.",
407
+ de: "Übersetzte Seiten folgen der Konvention `<base>.<lang>.md` neben der Standarddatei (z. B. `about.ja.md`). Das l10n-Plugin liefert sie unter `/lang/`-Pfaden und verlinkt sie automatisch.",
408
+ fr: "Les pages localisées suivent la convention `<base>.<lang>.md` à côté du fichier par défaut (ex. `about.ja.md`). Le plugin l10n les sert sous des chemins `/lang/` et les relie automatiquement.",
409
+ ko: "번역 페이지는 기본 파일 옆에 `<base>.<lang>.md` 규칙으로 둡니다(예: `about.ja.md`). l10n 플러그인이 `/lang/` 경로로 서빙하며 자동으로 링크합니다."
380
410
  }
381
- return `${imports.join("\n")}
382
-
383
- export function initRiebeckiteClient() {
384
- ${initializers.map((name) => ` ${name}();`).join("\n")}
385
- }
386
- `;
411
+ };
412
+ function indexContent(variables, language) {
413
+ const title = variables.title;
414
+ return [
415
+ frontmatter(),
416
+ heading(1, title),
417
+ read(INDEX.lead, language),
418
+ read(INDEX.translatedNote, language),
419
+ heading(2, read(INDEX.whatHeading, language)),
420
+ read(INDEX.whatBody, language),
421
+ heading(2, read(INDEX.exploreHeading, language)),
422
+ `- [${read(INDEX.explorePlugins, language)}](/framework/plugins)`,
423
+ `- [${read(INDEX.exploreThemes, language)}](/framework/themes)`,
424
+ heading(2, read(INDEX.editHeading, language)),
425
+ read(INDEX.editLead, language),
426
+ read(INDEX.editL10n, language),
427
+ ""
428
+ ].join("\n");
387
429
  }
388
-
389
- // src/vite_runner.ts
390
- import fs4 from "node:fs/promises";
391
- import path4 from "node:path";
392
- import { build, createServer as createServer2 } from "vite";
393
- var viteConfigFileNames = [
394
- "vite.config.ts",
395
- "vite.config.js",
396
- "vite.config.mjs"
397
- ];
398
- async function resolveHonoxApplicationRoot(configRoot) {
399
- const roots = await findViteApplicationRoots(configRoot);
400
- if (roots.length === 1) return roots[0];
401
- if (roots.length === 0) {
402
- throw new HonoxApplicationRootError(
403
- `Could not find a Vite application under ${configRoot}.`
404
- );
405
- }
406
- throw new HonoxApplicationRootError(
407
- `Found multiple Vite applications under ${configRoot}: ${roots.join(", ")}.`
408
- );
409
- }
410
- var HonoxApplicationRootError = class extends Error {
411
- constructor(message) {
412
- super(message);
413
- this.name = "HonoxApplicationRootError";
430
+ var THEMES_COPY = {
431
+ title: {
432
+ en: "Themes",
433
+ ja: "テーマ",
434
+ "zh-CN": "主题",
435
+ es: "Temas",
436
+ de: "Themen",
437
+ fr: "Thèmes",
438
+ ko: "테마"
439
+ },
440
+ intro: {
441
+ en: "A theme changes the whole look and feel of a site — colors, typography, and layout — without touching your content or routes. Riebeckite ships with six themes; switch by installing a package and changing one line.",
442
+ ja: "テーマを変えるだけで、色・文字組み・レイアウトといったサイト全体の見た目が変わります。コンテンツやルートには一切触れる必要はありません。Riebeckite には 6 つのテーマが用意されており、パッケージをインストールして 1 行書き換えるだけで切り替えられます。",
443
+ "zh-CN": "主题能整体改变站点的观感——配色、排版与布局——而无需改动你的内容或路由。Riebeckite 内置六个主题,安装一个包、改一行配置即可切换。",
444
+ es: "Un tema cambia toda la apariencia del sitio — colores, tipografía y diseño — sin tocar tu contenido ni tus rutas. Riebeckite incluye seis temas: instala un paquete y cambia una línea.",
445
+ de: "Ein Theme verändert das gesamte Erscheinungsbild einer Seite — Farben, Typografie, Layout — ohne dass du Inhalte oder Routen anfasst. Riebeckite bringt sechs Themes mit; der Wechsel ist eine Paketinstallation plus eine Zeile.",
446
+ fr: "Un thème change tout l'aspect d'un site — couleurs, typographie, mise en page — sans toucher au contenu ni aux routes. Riebeckite fournit six thèmes ; installez un paquet et changez une ligne.",
447
+ ko: "테마를 바꾸면 색상·타이포그래피·레이아웃 등 사이트 전체 분위기가 달라집니다. 콘텐츠나 라우트는 건드릴 필요가 없습니다. Riebeckite는 6개 테마를 제공하며 패키지 설치 후 한 줄만 바꾸면 전환됩니다."
448
+ },
449
+ switchHeading: {
450
+ en: "Switching themes",
451
+ ja: "テーマの切り替え",
452
+ "zh-CN": "切换主题",
453
+ es: "Cambiar de tema",
454
+ de: "Theme wechseln",
455
+ fr: "Changer de thème",
456
+ ko: "테마 전환"
457
+ },
458
+ switchCurrent: {
459
+ en: "This starter uses `@riebeckite/theme-default`. To try another theme:",
460
+ ja: "このスターターは `@riebeckite/theme-default` を使っています。別のテーマを試すには:",
461
+ "zh-CN": "本模板使用 `@riebeckite/theme-default`。想尝试其他主题:",
462
+ es: "Este starter usa `@riebeckite/theme-default`. Para probar otro tema:",
463
+ de: "Dieser Starter verwendet `@riebeckite/theme-default`. Um ein anderes Theme zu testen:",
464
+ fr: "Ce starter utilise `@riebeckite/theme-default`. Pour essayer un autre thème :",
465
+ ko: "이 스타터는 `@riebeckite/theme-default`를 사용합니다. 다른 테마를 시도하려면:"
466
+ },
467
+ switchStep1: {
468
+ en: "Install the package:",
469
+ ja: "パッケージをインストール:",
470
+ "zh-CN": "安装该包:",
471
+ es: "Instala el paquete:",
472
+ de: "Installiere das Paket:",
473
+ fr: "Installez le paquet :",
474
+ ko: "패키지를 설치합니다:"
475
+ },
476
+ switchStep2: {
477
+ en: "Point `theme` at the new factory in `riebeckite.config.ts`:",
478
+ ja: "`riebeckite.config.ts` の `theme` を新しいファクトリに変更:",
479
+ "zh-CN": "在 `riebeckite.config.ts` 中将 `theme` 指向新工厂函数:",
480
+ es: "Apunta `theme` al nuevo factory en `riebeckite.config.ts`:",
481
+ de: "Setze `theme` in `riebeckite.config.ts` auf den neuen Factory:",
482
+ fr: "Pointez `theme` vers le nouveau factory dans `riebeckite.config.ts` :",
483
+ ko: "`riebeckite.config.ts`의 `theme`를 새 팩토리로 변경합니다:"
484
+ },
485
+ tableHeading: {
486
+ en: "The bundled themes",
487
+ ja: "同梱テーマ一覧",
488
+ "zh-CN": "内置主题一览",
489
+ es: "Los temas incluidos",
490
+ de: "Die mitgelieferten Themes",
491
+ fr: "Les thèmes inclus",
492
+ ko: "내장 테마 목록"
493
+ },
494
+ columnTheme: {
495
+ en: "Theme",
496
+ ja: "テーマ",
497
+ "zh-CN": "主题",
498
+ es: "Tema",
499
+ de: "Theme",
500
+ fr: "Thème",
501
+ ko: "테마"
502
+ },
503
+ columnDescription: {
504
+ en: "Description",
505
+ ja: "説明",
506
+ "zh-CN": "说明",
507
+ es: "Descripción",
508
+ de: "Beschreibung",
509
+ fr: "Description",
510
+ ko: "설명"
511
+ },
512
+ note: {
513
+ en: "Every theme supports light/dark color modes, typography, and article layouts; see each README for the full option list.",
514
+ ja: "すべてのテーマでライト/ダーク切り替え、文字組み、記事レイアウトをサポートしています。詳しいオプションは各 README を参照してください。",
515
+ "zh-CN": "所有主题都支持明暗配色、排版与文章布局,完整选项请见各 README。",
516
+ es: "Todos los temas admiten modos claro/oscuro, tipografía y diseño de artículo; consulta cada README para la lista completa de opciones.",
517
+ de: "Jedes Theme unterstützt Hell/Dunkel-Modi, Typografie und Artikel-Layouts; die vollständige Optionsliste steht im jeweiligen README.",
518
+ fr: "Chaque thème prend en charge les modes clair/sombre, la typographie et les mises en page d'article ; voir chaque README pour la liste complète des options.",
519
+ ko: "모든 테마가 라이트/다크 모드, 타이포그래피, 아티클 레이아웃을 지원합니다. 전체 옵션은 각 README를 참조하세요."
414
520
  }
415
521
  };
416
- async function resolveHonoxApplication(options) {
417
- const configRoot = path4.resolve(options.configRoot);
418
- const appRoot = options.appRoot ? path4.resolve(options.appRoot) : await resolveHonoxApplicationRoot(configRoot);
419
- const workspaceRoot = options.workspaceRoot ?? await findWorkspaceRoot(configRoot);
420
- const rawConfig = await loadRiebeckiteConfig({
421
- configRoot,
422
- configFile: options.configFile,
423
- workspaceRoot
424
- });
425
- const config = resolveHonoxConfig(rawConfig, appRoot);
426
- return {
427
- config,
428
- configRoot,
429
- appRoot,
430
- contentRoot: config.content.directory
431
- };
432
- }
433
- async function findWorkspaceRoot(configRoot) {
434
- try {
435
- const workspaceConfig = path4.join(configRoot, "pnpm-workspace.yaml");
436
- return (await fs4.stat(workspaceConfig)).isFile() ? configRoot : void 0;
437
- } catch {
438
- return void 0;
522
+ var THEMES = [
523
+ {
524
+ slug: "default",
525
+ factory: "defaultTheme()",
526
+ desc: {
527
+ en: "The default theme: clean design tokens, light/dark/system color modes, and article layouts.",
528
+ ja: "デフォルトテーマ:クリーンなデザイントークン、ライト/ダーク/システムのカラーモード、記事レイアウト。",
529
+ "zh-CN": "默认主题:简洁的设计令牌、浅色/深色/系统配色与文章布局。",
530
+ es: "El tema por defecto: tokens limpios, modos claro/oscuro/sistema y distintos diseños de artículo.",
531
+ de: "Das Standard-Theme: klare Design-Tokens, Licht/Dunkel/System-Modi und Artikel-Layouts.",
532
+ fr: "Le thème par défaut : tokens épurés, modes clair/sombre/système et mises en page d'article.",
533
+ ko: "기본 테마: 깔끔한 디자인 토큰, 라이트/다크/시스템 컬러 모드, 아티클 레이아웃."
534
+ }
535
+ },
536
+ {
537
+ slug: "minimal",
538
+ factory: "minimalTheme()",
539
+ desc: {
540
+ en: "A quiet, typography-first theme.",
541
+ ja: "文字組みを重視した静かなテーマ。",
542
+ "zh-CN": "以排版为先的简洁主题。",
543
+ es: "Un tema sobrio, centrado en la tipografía.",
544
+ de: "Ein ruhiges, typografieorientiertes Theme.",
545
+ fr: "Un thème sobre, axé sur la typographie.",
546
+ ko: "타이포그래피 우선의 차분한 테마."
547
+ }
548
+ },
549
+ {
550
+ slug: "sakura",
551
+ factory: "sakuraTheme()",
552
+ desc: {
553
+ en: "A soft pink palette with warm accents.",
554
+ ja: "やわらかいピンクと温かみのあるアクセントのテーマ。",
555
+ "zh-CN": "柔和的粉色配色与温暖点缀。",
556
+ es: "Una paleta rosa suave con acentos cálidos.",
557
+ de: "Eine weiche rosa Palette mit warmen Akzenten.",
558
+ fr: "Une palette rose douce aux accents chaleureux.",
559
+ ko: "부드러운 핑크 팔레트와 따뜻한 액센트."
560
+ }
561
+ },
562
+ {
563
+ slug: "gruvbox",
564
+ factory: "gruvboxTheme()",
565
+ desc: {
566
+ en: "A warm, retro palette inspired by Gruvbox.",
567
+ ja: "Gruvbox 風の温かみのあるレトロパレット。",
568
+ "zh-CN": "受 Gruvbox 启发的温暖复古配色。",
569
+ es: "Una paleta retro cálida inspirada en Gruvbox.",
570
+ de: "Eine warme, von Gruvbox inspirierte Retro-Palette.",
571
+ fr: "Une palette rétro chaleureuse inspirée de Gruvbox.",
572
+ ko: "Gruvbox에서 영감을 받은 따뜻한 레트로 팔레트."
573
+ }
574
+ },
575
+ {
576
+ slug: "tokyonight",
577
+ factory: "tokyonightTheme()",
578
+ desc: {
579
+ en: "A modern night palette with an optional neon accent.",
580
+ ja: "ネオンオプション付きのモダンなナイトパレット。",
581
+ "zh-CN": "现代夜间配色,可选霓虹点缀。",
582
+ es: "Una paleta nocturna moderna con acento neón opcional.",
583
+ de: "Eine moderne Night-Palette mit optionalem Neon-Akzent.",
584
+ fr: "Une palette nocturne moderne avec accent néon optionnel.",
585
+ ko: "네온 액센트를 선택할 수 있는 모던한 나이트 팔레트."
586
+ }
587
+ },
588
+ {
589
+ slug: "rerurate",
590
+ factory: "rerurateTheme()",
591
+ desc: {
592
+ en: "The author's personal design language.",
593
+ ja: "作者自身のデザインをテーマにしたテーマ。",
594
+ "zh-CN": "作者个人的设计风格主题。",
595
+ es: "El lenguaje de diseño personal del autor.",
596
+ de: "Die persönliche Designsprache des Autors.",
597
+ fr: "Le langage de design personnel de l'auteur.",
598
+ ko: "저자의 개인 디자인 언어 테마."
599
+ }
439
600
  }
440
- }
441
- async function startHonoxDevServer(options) {
442
- const server2 = await createServer2({ root: options.root });
443
- await server2.listen();
444
- server2.printUrls();
445
- await waitForShutdown(server2);
446
- }
447
- async function buildHonoxApplication(options) {
448
- await runTraced(
449
- options.tracer,
450
- "integration.honox.client_build",
451
- () => build({ root: options.root, mode: "client" })
601
+ ];
602
+ function themesContent(language) {
603
+ const copy = THEMES_COPY;
604
+ const lines = [frontmatter()];
605
+ lines.push(heading(1, read(copy.title, language)), "");
606
+ lines.push(read(copy.intro, language), "");
607
+ lines.push(heading(2, read(copy.switchHeading, language)), "");
608
+ lines.push(read(copy.switchCurrent, language), "");
609
+ lines.push(read(copy.switchStep1, language), "");
610
+ lines.push(codeBlock("sh", "pnpm add @riebeckite/theme-sakura"));
611
+ lines.push(read(copy.switchStep2, language), "");
612
+ lines.push(
613
+ codeBlock(
614
+ "ts",
615
+ [
616
+ 'import { sakuraTheme } from "@riebeckite/theme-sakura";',
617
+ "",
618
+ "export default defineConfig({",
619
+ " theme: sakuraTheme(),",
620
+ "});"
621
+ ].join("\n")
622
+ )
452
623
  );
453
- await runTraced(
454
- options.tracer,
455
- "integration.honox.server_build",
456
- () => build({ root: options.root })
624
+ lines.push(heading(2, read(copy.tableHeading, language)), "");
625
+ lines.push(
626
+ `| ${read(copy.columnTheme, language)} | ${read(copy.columnDescription, language)} |`
457
627
  );
458
- }
459
- async function runTraced(tracer, name, buildTask) {
460
- return tracer ? await tracer.span(name, {}, buildTask) : await buildTask();
461
- }
462
- async function waitForShutdown(server2) {
463
- await new Promise((resolve) => {
464
- const close = () => {
465
- void server2.close().finally(resolve);
466
- };
467
- process.once("SIGINT", close);
468
- process.once("SIGTERM", close);
469
- });
470
- }
471
- async function findViteApplicationRoots(root) {
472
- const directories = [root];
473
- const applicationRoots = [];
474
- while (directories.length > 0) {
475
- const directory = directories.pop();
476
- if (!directory) continue;
477
- if (await hasViteConfig(directory)) applicationRoots.push(directory);
478
- const entries = await fs4.readdir(directory, { withFileTypes: true });
479
- for (const entry of entries) {
480
- if (!entry.isDirectory() || entry.name === "node_modules" || entry.name === ".git" || entry.name === "tests") {
481
- continue;
482
- }
483
- directories.push(path4.join(directory, entry.name));
484
- }
628
+ lines.push("| --- | --- |");
629
+ for (const theme of THEMES) {
630
+ const packageName = `@riebeckite/theme-${theme.slug}`;
631
+ lines.push(
632
+ `| [\`${packageName}\`](${themeReadmeUrl(theme.slug)}) · \`${theme.factory}\` | ${read(theme.desc, language)} |`
633
+ );
485
634
  }
486
- return applicationRoots;
487
- }
488
- async function hasViteConfig(directory) {
489
- for (const fileName of viteConfigFileNames) {
490
- try {
491
- if ((await fs4.stat(path4.join(directory, fileName))).isFile()) return true;
492
- } catch {
493
- }
635
+ lines.push("");
636
+ lines.push(heading(2, read(copy.note, language)), "");
637
+ lines.push("");
638
+ lines.push(`[Riebeckite themes on GitHub](${THEMES_INDEX_URL})`, "");
639
+ lines.push("");
640
+ return lines.join("\n");
641
+ }
642
+ var PLUGINS_COPY = {
643
+ title: {
644
+ en: "Plugins",
645
+ ja: "プラグイン",
646
+ "zh-CN": "插件",
647
+ es: "Plugins",
648
+ de: "Plugins",
649
+ fr: "Plugins",
650
+ ko: "플러그인"
651
+ },
652
+ intro: {
653
+ en: "Riebeckite's power comes from its plugin ecosystem — over fifty packages that extend Markdown, rendering, search, SEO, and more. Below are representative examples grouped by capability; every entry links to its full README.",
654
+ ja: "Riebeckite の力はプラグインエコシステムにあります——50 以上のパッケージが Markdown・描画・検索・SEO などを拡張します。ここでは代表的なプラグインを機能別に紹介します。各項目のリンクから詳細な README を参照できます。",
655
+ "zh-CN": "Riebeckite 的强大来自其插件生态——五十多个扩展 Markdown、渲染、搜索、SEO 等的包。下面按能力分组展示代表性示例,每一项都链接到完整 README。",
656
+ es: "El poder de Riebeckite viene de su ecosistema de plugins — más de cincuenta paquetes que extienden Markdown, renderizado, búsqueda, SEO y más. Aquí tienes ejemplos representativos agrupados por capacidad; cada uno enlaza a su README completo.",
657
+ de: "Riebeckites Stärke kommt aus seinem Plugin-Ökosystem — über fünfzig Pakete, die Markdown, Rendering, Suche, SEO und mehr erweitern. Hier finden sich repräsentative Beispiele nach Fähigkeit gruppiert; jeder Eintrag verlinkt auf sein volles README.",
658
+ fr: "La puissance de Riebeckite vient de son écosystème de plugins — plus de cinquante paquets qui étendent Markdown, le rendu, la recherche, le SEO et plus encore. Voici des exemples représentatifs groupés par capacité ; chaque entrée renvoie vers son README complet.",
659
+ ko: "Riebeckite의 힘은 플러그인 생태계에 있습니다 — Markdown·렌더링·검색·SEO 등을 확장하는 50개 이상의 패키지. 아래는 기능별로 대표적인 예를 소개하며, 각 항목은 전체 README로 연결됩니다."
660
+ },
661
+ installHeading: {
662
+ en: "Adding a plugin",
663
+ ja: "プラグインの追加",
664
+ "zh-CN": "添加插件",
665
+ es: "Añadir un plugin",
666
+ de: "Ein Plugin hinzufügen",
667
+ fr: "Ajouter un plugin",
668
+ ko: "플러그인 추가"
669
+ },
670
+ installStep1: {
671
+ en: "Install the package:",
672
+ ja: "パッケージをインストール:",
673
+ "zh-CN": "安装该包:",
674
+ es: "Instala el paquete:",
675
+ de: "Installiere das Paket:",
676
+ fr: "Installez le paquet :",
677
+ ko: "패키지를 설치합니다:"
678
+ },
679
+ installStep2: {
680
+ en: "Register it in the `plugins` array of `riebeckite.config.ts`:",
681
+ ja: "`riebeckite.config.ts` の `plugins` 配列に登録:",
682
+ "zh-CN": "在 `riebeckite.config.ts` 的 `plugins` 数组中注册:",
683
+ es: "Regístralo en el array `plugins` de `riebeckite.config.ts`:",
684
+ de: "Registriere es im `plugins`-Array der `riebeckite.config.ts`:",
685
+ fr: "Enregistrez-le dans le tableau `plugins` de `riebeckite.config.ts` :",
686
+ ko: "`riebeckite.config.ts`의 `plugins` 배열에 등록합니다:"
687
+ },
688
+ fullList: {
689
+ en: "The complete plugin index lives in the repository:",
690
+ ja: "全プラグインの一覧はリポジトリにあります:",
691
+ "zh-CN": "完整的插件索引位于仓库中:",
692
+ es: "El índice completo de plugins vive en el repositorio:",
693
+ de: "Der vollständige Plugin-Index liegt im Repository:",
694
+ fr: "L'index complet des plugins se trouve dans le dépôt :",
695
+ ko: "전체 플러그인 목록은 저장소에 있습니다:"
696
+ },
697
+ fullListLabel: {
698
+ en: "Riebeckite plugins on GitHub",
699
+ ja: "GitHub の Riebeckite プラグイン",
700
+ "zh-CN": "GitHub 上的 Riebeckite 插件",
701
+ es: "Plugins de Riebeckite en GitHub",
702
+ de: "Riebeckite-Plugins auf GitHub",
703
+ fr: "Plugins Riebeckite sur GitHub",
704
+ ko: "GitHub의 Riebeckite 플러그인"
705
+ },
706
+ columnPlugin: {
707
+ en: "Plugin",
708
+ ja: "プラグイン",
709
+ "zh-CN": "插件",
710
+ es: "Plugin",
711
+ de: "Plugin",
712
+ fr: "Plugin",
713
+ ko: "플러그인"
714
+ },
715
+ columnDescription: {
716
+ en: "What it does",
717
+ ja: "できること",
718
+ "zh-CN": "作用",
719
+ es: "Qué hace",
720
+ de: "Funktion",
721
+ fr: "Rôle",
722
+ ko: "기능"
494
723
  }
495
- return false;
496
- }
497
-
498
- // src/vite_plugin.ts
499
- var defaultSsrExternals = [
500
- "extend",
501
- "debug",
502
- "node:fs/promises",
503
- "node:path",
504
- "parse-numeric-range",
505
- "slugify",
506
- "vfile-matter"
507
- ];
508
- function riebeckite(options = {}) {
509
- let resolvedConfig;
510
- const getConfig = () => {
511
- if (!resolvedConfig) {
512
- throw new Error("Riebeckite config has not been loaded yet.");
513
- }
514
- return resolvedConfig;
515
- };
516
- return [
517
- {
518
- name: "riebeckite-host-integration",
519
- async config(userConfig) {
520
- const root = userConfig.root ? path5.resolve(userConfig.root) : process.cwd();
521
- const appRoot = options.appRoot ?? root;
522
- const application = await resolveHonoxApplication({
523
- configRoot: options.configRoot ?? appRoot,
524
- configFile: options.configFile,
525
- appRoot,
526
- workspaceRoot: options.workspaceRoot
527
- });
528
- resolvedConfig = application.config;
529
- writeRiebeckiteAssetEntries(resolvedConfig, {
530
- pluginStyles: path5.join(appRoot, "app/.riebeckite/plugin-styles.css"),
531
- themeStyles: path5.join(appRoot, "app/.riebeckite/theme-styles.css")
532
- });
533
- return {
534
- define: {
535
- "process.env.RIEBECKITE_APP_ROOT": JSON.stringify(appRoot)
536
- },
537
- resolve: {
538
- alias: options.workspaceRoot ? createWorkspacePackageAliases(options.workspaceRoot) : []
539
- }
540
- };
724
+ };
725
+ var PLUGIN_CATEGORIES = [
726
+ {
727
+ title: {
728
+ en: "Markdown and notes",
729
+ ja: "マークダウンとノート",
730
+ "zh-CN": "Markdown 与笔记",
731
+ es: "Markdown y notas",
732
+ de: "Markdown und Notizen",
733
+ fr: "Markdown et notes",
734
+ ko: "마크다운과 노트"
735
+ },
736
+ summary: {
737
+ en: "Everyday note-taking tuned for Obsidian vaults.",
738
+ ja: "Obsidian ボールト向けの日常ノート機能。",
739
+ "zh-CN": "为 Obsidian 库打造的日常笔记功能。",
740
+ es: "Toma de notas diaria afinada para bóvedas Obsidian.",
741
+ de: "Alltägliche Notizfunktionen, optimiert für Obsidian-Vaults.",
742
+ fr: "Prise de notes quotidienne pensée pour les coffres Obsidian.",
743
+ ko: "Obsidian 볼트에 맞춘 일상 노트 기능."
744
+ },
745
+ plugins: [
746
+ {
747
+ slug: "obsidian-markdown",
748
+ desc: {
749
+ en: "Obsidian-flavored Markdown: wikilinks, embeds, callouts, and tags.",
750
+ ja: "Obsidian 風マークダウン:ウィキリンク・埋め込み・コールアウト・タグ。",
751
+ "zh-CN": "Obsidian 风格 Markdown:双链、嵌入、标注与标签。",
752
+ es: "Markdown estilo Obsidian: wikilinks, embeds, callouts y etiquetas.",
753
+ de: "Obsidian-Markdown: Wikilinks, Einbettungen, Callouts und Tags.",
754
+ fr: "Markdown façon Obsidian : wikilinks, embeds, callouts et tags.",
755
+ ko: "Obsidian 스타일 마크다운: 위키링크, 임베드, 콜아웃, 태그."
756
+ }
757
+ },
758
+ {
759
+ slug: "attachment",
760
+ desc: {
761
+ en: "Renders attached files and embeds assets via wikilinks.",
762
+ ja: "ウィキリンクによるファイル添付とアセットの埋め込み表示。",
763
+ "zh-CN": "通过双链渲染附件并嵌入资源。",
764
+ es: "Renderiza archivos adjuntos e incrusta assets con wikilinks.",
765
+ de: "Rendert Dateianhänge und bettet Assets per Wikilinks ein.",
766
+ fr: "Rend les fichiers joints et embarque les ressources via wikilinks.",
767
+ ko: "위키링크로 첨부 파일을 렌더링하고 자산을 임베드."
768
+ }
769
+ },
770
+ {
771
+ slug: "media",
772
+ desc: {
773
+ en: "Audio and video embeds from plain links.",
774
+ ja: "プレーンなリンクから音声・動画を埋め込み。",
775
+ "zh-CN": "从普通链接嵌入音频和视频。",
776
+ es: "Incrusta audio y vídeo desde enlaces simples.",
777
+ de: "Audio- und Video-Embeds aus einfachen Links.",
778
+ fr: "Embeds audio et vidéo depuis de simples liens.",
779
+ ko: "일반 링크에서 오디오·비디오 임베드."
780
+ }
541
781
  }
782
+ ]
783
+ },
784
+ {
785
+ title: {
786
+ en: "Diagrams and presentation",
787
+ ja: "図とプレゼンテーション",
788
+ "zh-CN": "图表与演示",
789
+ es: "Diagramas y presentación",
790
+ de: "Diagramme und Präsentation",
791
+ fr: "Diagrammes et présentation",
792
+ ko: "다이어그램과 프레젠테이션"
542
793
  },
543
- riebeckiteClientModule(getConfig)
544
- ];
545
- }
546
- function riebeckiteVite(options = {}) {
547
- const { ssg, ssrExternals, ...integration } = options;
548
- return [
549
- ...riebeckite(integration),
550
- riebeckiteSsrExternals([...defaultSsrExternals, ...ssrExternals ?? []]),
551
- createRiebeckiteSsg(ssg)
552
- ];
553
- }
554
- function riebeckiteSsrExternals(externals) {
555
- return {
556
- name: "riebeckite-ssr-externals",
557
- config() {
558
- return {
559
- environments: {
560
- ssr: {
561
- resolve: {
562
- external: [...externals]
563
- }
564
- }
794
+ summary: {
795
+ en: "Turn fenced code blocks into diagrams, charts, and slide decks.",
796
+ ja: "フェンスコードブロックを図やチャート、スライドに変換。",
797
+ "zh-CN": "将围栏代码块变成图表与幻灯片。",
798
+ es: "Convierte bloques de código en diagramas, gráficas y diapositivas.",
799
+ de: "Verwandle fenced Code-Blöcke in Diagramme, Charts und Folien.",
800
+ fr: "Transformez des blocs de code en diagrammes, graphiques et diapositives.",
801
+ ko: "fenced 코드 블록을 다이어그램·차트·슬라이드로 전환."
802
+ },
803
+ plugins: [
804
+ {
805
+ slug: "mermaid",
806
+ desc: {
807
+ en: "Mermaid diagrams from fenced code blocks.",
808
+ ja: "フェンスコードブロックから Mermaid 図を描画。",
809
+ "zh-CN": "从围栏代码块渲染 Mermaid 图表。",
810
+ es: "Diagramas Mermaid desde bloques de código delimitados.",
811
+ de: "Mermaid-Diagramme aus fenced Code-Blöcken.",
812
+ fr: "Diagrammes Mermaid depuis des blocs de code délimités.",
813
+ ko: "fenced 코드 블록에서 Mermaid 다이어그램 렌더링."
565
814
  }
566
- };
815
+ },
816
+ {
817
+ slug: "graphviz",
818
+ desc: {
819
+ en: "DOT / Graphviz diagrams.",
820
+ ja: "DOT / Graphviz 図。",
821
+ "zh-CN": "DOT / Graphviz 图表。",
822
+ es: "Diagramas DOT / Graphviz.",
823
+ de: "DOT / Graphviz-Diagramme.",
824
+ fr: "Diagrammes DOT / Graphviz.",
825
+ ko: "DOT / Graphviz 다이어그램."
826
+ }
827
+ },
828
+ {
829
+ slug: "d2",
830
+ desc: {
831
+ en: "Diagrams in the D2 language.",
832
+ ja: "D2 言語によるダイアグラム。",
833
+ "zh-CN": "使用 D2 语言绘制图表。",
834
+ es: "Diagramas en lenguaje D2.",
835
+ de: "Diagramme in der Sprache D2.",
836
+ fr: "Diagrammes en langage D2.",
837
+ ko: "D2 언어 다이어그램."
838
+ }
839
+ },
840
+ {
841
+ slug: "excalidraw",
842
+ desc: {
843
+ en: "Renders Excalidraw sketch files.",
844
+ ja: "Excalidraw スケッチファイルを描画。",
845
+ "zh-CN": "渲染 Excalidraw 草图文件。",
846
+ es: "Renderiza bocetos de Excalidraw.",
847
+ de: "Rendert Excalidraw-Skizzen.",
848
+ fr: "Rend les croquis Excalidraw.",
849
+ ko: "Excalidraw 스케치 파일 렌더링."
850
+ }
851
+ }
852
+ ]
853
+ },
854
+ {
855
+ title: {
856
+ en: "Code and reading experience",
857
+ ja: "コードと読書体験",
858
+ "zh-CN": "代码与阅读体验",
859
+ es: "Código y experiencia de lectura",
860
+ de: "Code und Leseerlebnis",
861
+ fr: "Code et expérience de lecture",
862
+ ko: "코드와 읽기 경험"
863
+ },
864
+ summary: {
865
+ en: "Better code blocks and a comfortable reading experience.",
866
+ ja: "読みやすいコードブロックと快適な読書体験。",
867
+ "zh-CN": "更好的代码块与舒适的阅读体验。",
868
+ es: "Mejores bloques de código y una lectura cómoda.",
869
+ de: "Bessere Code-Blöcke und ein angenehmes Leseerlebnis.",
870
+ fr: "De meilleurs blocs de code et une lecture confortable.",
871
+ ko: "더 나은 코드 블록과 편안한 읽기 경험."
872
+ },
873
+ plugins: [
874
+ {
875
+ slug: "code-enhance",
876
+ desc: {
877
+ en: "Syntax highlighting, line numbers, and code toolbars.",
878
+ ja: "シンタックスハイライト・行番号・コードツールバー。",
879
+ "zh-CN": "语法高亮、行号与代码工具栏。",
880
+ es: "Resaltado de sintaxis, números de línea y barras de código.",
881
+ de: "Syntax-Highlighting, Zeilennummern und Code-Symbolleisten.",
882
+ fr: "Coloration syntaxique, numéros de ligne et barres de code.",
883
+ ko: "구문 강조, 줄 번호, 코드 도구 모음."
884
+ }
885
+ },
886
+ {
887
+ slug: "code-tabs",
888
+ desc: {
889
+ en: "Accessible tabbed code blocks.",
890
+ ja: "アクセシブルなタブ式コードブロック。",
891
+ "zh-CN": "无障碍的标签式代码块。",
892
+ es: "Bloques de código con pestañas accesibles.",
893
+ de: "Barrierefreie Code-Blöcke mit Tabs.",
894
+ fr: "Blocs de code à onglets accessibles.",
895
+ ko: "접근성 있는 탭식 코드 블록."
896
+ }
897
+ },
898
+ {
899
+ slug: "toc",
900
+ desc: {
901
+ en: "A scroll-aware table of contents.",
902
+ ja: "スクロール追従の目次。",
903
+ "zh-CN": "随滚动高亮的目录。",
904
+ es: "Una tabla de contenidos que sigue el scroll.",
905
+ de: "Ein scrollgesteuertes Inhaltsverzeichnis.",
906
+ fr: "Une table des matières qui suit le défilement.",
907
+ ko: "스크롤을 따라가는 목차."
908
+ }
909
+ },
910
+ {
911
+ slug: "backlinks",
912
+ desc: {
913
+ en: "Lists notes that link to the current one.",
914
+ ja: "現在のノートを参照するノートを一覧表示。",
915
+ "zh-CN": "列出链接到当前笔记的笔记。",
916
+ es: "Lista las notas que enlazan con la actual.",
917
+ de: "Listet Notizen, die auf die aktuelle verlinken.",
918
+ fr: "Liste les notes qui pointent vers la note courante.",
919
+ ko: "현재 노트를 링크한 노트 목록."
920
+ }
921
+ }
922
+ ]
923
+ },
924
+ {
925
+ title: {
926
+ en: "Search and navigation",
927
+ ja: "検索とナビゲーション",
928
+ "zh-CN": "搜索与导航",
929
+ es: "Búsqueda y navegación",
930
+ de: "Suche und Navigation",
931
+ fr: "Recherche et navigation",
932
+ ko: "검색과 탐색"
933
+ },
934
+ summary: {
935
+ en: "Find and move through your notes quickly.",
936
+ ja: "ノートをすばやく探して移動する。",
937
+ "zh-CN": "快速查找并在笔记之间穿梭。",
938
+ es: "Encuentra y recorre tus notas rápidamente.",
939
+ de: "Notizen schnell finden und durch sie navigieren.",
940
+ fr: "Retrouvez et parcourez vos notes rapidement.",
941
+ ko: "노트를 빠르게 찾고 이동하기."
942
+ },
943
+ plugins: [
944
+ {
945
+ slug: "search",
946
+ desc: {
947
+ en: "Client-side full-text search with a Ctrl+K modal.",
948
+ ja: "Ctrl+K モーダル付きのクライアント側全文検索。",
949
+ "zh-CN": "带 Ctrl+K 弹窗的客户端全文搜索。",
950
+ es: "Búsqueda de texto completo en el cliente con modal Ctrl+K.",
951
+ de: "Clientseitige Volltextsuche mit Ctrl+K-Modal.",
952
+ fr: "Recherche plein texte côté client avec modal Ctrl+K.",
953
+ ko: "Ctrl+K 모달이 있는 클라이언트 전체 텍스트 검색."
954
+ }
955
+ },
956
+ {
957
+ slug: "garden-explorer",
958
+ desc: {
959
+ en: "An interactive graph and search explorer.",
960
+ ja: "ノートのグラフと検索を探索するインタラクティブビュー。",
961
+ "zh-CN": "交互式图谱与搜索探索器。",
962
+ es: "Un explorador interactivo de grafo y búsqueda.",
963
+ de: "Ein interaktiver Graph- und Such-Explorer.",
964
+ fr: "Un explorateur interactif de graphe et de recherche.",
965
+ ko: "노트 그래프와 검색을 탐색하는 대화형 뷰."
966
+ }
967
+ },
968
+ {
969
+ slug: "local-graph",
970
+ desc: {
971
+ en: "A link graph around the current note.",
972
+ ja: "ノート周辺のリンクグラフ。",
973
+ "zh-CN": "当前笔记周边的链接图谱。",
974
+ es: "Un grafo de enlaces alrededor de la nota actual.",
975
+ de: "Ein Link-Graph rund um die aktuelle Notiz.",
976
+ fr: "Un graphe de liens autour de la note courante.",
977
+ ko: "현재 노트 주변의 링크 그래프."
978
+ }
979
+ },
980
+ {
981
+ slug: "permalink",
982
+ desc: {
983
+ en: "Stable, configurable permalinks.",
984
+ ja: "安定したカスタマイズ可能なパーマリンク。",
985
+ "zh-CN": "稳定、可配置的永久链接。",
986
+ es: "Permalinks estables y configurables.",
987
+ de: "Stabile, konfigurierbare Permalinks.",
988
+ fr: "Permaliens stables et configurables.",
989
+ ko: "안정적이고 설정 가능한 고유 주소."
990
+ }
991
+ }
992
+ ]
993
+ },
994
+ {
995
+ title: {
996
+ en: "Publishing and SEO",
997
+ ja: "公開と SEO",
998
+ "zh-CN": "发布与 SEO",
999
+ es: "Publicación y SEO",
1000
+ de: "Veröffentlichung und SEO",
1001
+ fr: "Publication et SEO",
1002
+ ko: "배포와 SEO"
1003
+ },
1004
+ summary: {
1005
+ en: "Ship a site that search engines and readers both understand.",
1006
+ ja: "検索エンジンにも読者にも伝わるサイトを公開する。",
1007
+ "zh-CN": "发布搜索引擎和读者都能理解的站点。",
1008
+ es: "Publica un sitio que buscan buscadores y lectores por igual.",
1009
+ de: "Veröffentliche eine Seite, die Suchmaschinen und Leser gleichermaßen verstehen.",
1010
+ fr: "Publiez un site que moteurs de recherche et lecteurs comprennent.",
1011
+ ko: "검색 엔진과 독자 모두가 이해하는 사이트를 배포."
1012
+ },
1013
+ plugins: [
1014
+ {
1015
+ slug: "seo",
1016
+ desc: {
1017
+ en: "SEO metadata, sitemaps, RSS/Atom/JSON feeds, and robots.txt.",
1018
+ ja: "SEO メタデータ・サイトマップ・RSS/Atom/JSON フィード・robots.txt。",
1019
+ "zh-CN": "SEO 元数据、站点地图、RSS/Atom/JSON 订阅与 robots.txt。",
1020
+ es: "Metadatos SEO, sitemaps, feeds RSS/Atom/JSON y robots.txt.",
1021
+ de: "SEO-Metadaten, Sitemaps, RSS/Atom/JSON-Feeds und robots.txt.",
1022
+ fr: "Métadonnées SEO, sitemaps, flux RSS/Atom/JSON et robots.txt.",
1023
+ ko: "SEO 메타데이터·사이트맵·RSS/Atom/JSON 피드·robots.txt."
1024
+ }
1025
+ },
1026
+ {
1027
+ slug: "l10n",
1028
+ desc: {
1029
+ en: "Localized URLs, a language switcher, and hreflang metadata — this site runs on it.",
1030
+ ja: "ローカライズ済み URL・言語スイッチャー・hreflang メタデータ——このサイトもこれで動いています。",
1031
+ "zh-CN": "本地化 URL、语言切换器与 hreflang 元数据——本站就运行在它之上。",
1032
+ es: "URLs localizadas, selector de idioma y metadatos hreflang — este sitio se ejecuta sobre él.",
1033
+ de: "Lokalisierte URLs, Sprachumschalter und hreflang-Metadaten — diese Seite läuft darauf.",
1034
+ fr: "URLs localisées, sélecteur de langue et métadonnées hreflang — ce site repose dessus.",
1035
+ ko: "로컬라이즈된 URL·언어 전환 UI·hreflang 메타데이터 — 이 사이트가 동작하는 기반입니다."
1036
+ }
1037
+ },
1038
+ {
1039
+ slug: "rich-embed",
1040
+ desc: {
1041
+ en: "Build-time rich media cards for external links.",
1042
+ ja: "外部リンクのビルド時リッチメディアカード。",
1043
+ "zh-CN": "外部链接的构建时富媒体卡片。",
1044
+ es: "Tarjetas multimedia en build para enlaces externos.",
1045
+ de: "Rich-Media-Karten für externe Links zur Build-Zeit.",
1046
+ fr: "Cartes média construites à la compilation pour les liens externes.",
1047
+ ko: "외부 링크의 빌드 시점 리치 미디어 카드."
1048
+ }
1049
+ },
1050
+ {
1051
+ slug: "deploy",
1052
+ desc: {
1053
+ en: "Static deployment output for hosting services.",
1054
+ ja: "ホスティングサービス向けの静的デプロイ出力。",
1055
+ "zh-CN": "面向托管服务的静态部署产物。",
1056
+ es: "Salida de despliegue estático para servicios de hosting.",
1057
+ de: "Statische Deployment-Ausgabe für Hosting-Dienste.",
1058
+ fr: "Sortie de déploiement statique pour les services d'hébergement.",
1059
+ ko: "호스팅 서비스용 정적 배포 산출물."
1060
+ }
1061
+ }
1062
+ ]
1063
+ },
1064
+ {
1065
+ title: {
1066
+ en: "Content and developer experience",
1067
+ ja: "コンテンツと開発体験",
1068
+ "zh-CN": "内容与开发者体验",
1069
+ es: "Contenido y experiencia de desarrollo",
1070
+ de: "Content und Developer Experience",
1071
+ fr: "Contenu et expérience développeur",
1072
+ ko: "콘텐츠와 개발자 경험"
1073
+ },
1074
+ summary: {
1075
+ en: "Query, organize, and keep your content healthy.",
1076
+ ja: "コンテンツを検索・整理し、健全に保つ。",
1077
+ "zh-CN": "查询、整理并保持内容健康。",
1078
+ es: "Consulta, organiza y mantén sano tu contenido.",
1079
+ de: "Content abfragen, organisieren und gesund halten.",
1080
+ fr: "Requêter, organiser et garder votre contenu sain.",
1081
+ ko: "콘텐츠를 조회·정리하고 건강하게 유지."
1082
+ },
1083
+ plugins: [
1084
+ {
1085
+ slug: "dataview",
1086
+ desc: {
1087
+ en: "Build-time queries over your notes.",
1088
+ ja: "ノートに対するビルド時クエリ。",
1089
+ "zh-CN": "对笔记的构建时查询。",
1090
+ es: "Consultas en build sobre tus notas.",
1091
+ de: "Build-Zeit-Abfragen über deine Notizen.",
1092
+ fr: "Requêtes temps de build sur vos notes.",
1093
+ ko: "노트에 대한 빌드 시점 조회."
1094
+ }
1095
+ },
1096
+ {
1097
+ slug: "kanban",
1098
+ desc: {
1099
+ en: "Obsidian-style Kanban boards from Markdown lists.",
1100
+ ja: "Markdown リストから Obsidian スタイルのカンバンボードを作成。",
1101
+ "zh-CN": "从 Markdown 列表创建 Obsidian 风格看板。",
1102
+ es: "Tableros Kanban estilo Obsidian desde listas Markdown.",
1103
+ de: "Obsidian-ähnliche Kanban-Boards aus Markdown-Listen.",
1104
+ fr: "Tableaux Kanban façon Obsidian depuis des listes Markdown.",
1105
+ ko: "Markdown 목록에서 Obsidian 스타일 칸반 보드 생성."
1106
+ }
1107
+ },
1108
+ {
1109
+ slug: "responsive-image",
1110
+ desc: {
1111
+ en: "Responsive images with lazy loading.",
1112
+ ja: "レスポンシブ画像と遅延読み込み。",
1113
+ "zh-CN": "响应式图片与懒加载。",
1114
+ es: "Imágenes responsive con carga diferida.",
1115
+ de: "Responsive Bilder mit Lazy Loading.",
1116
+ fr: "Images responsives avec chargement différé.",
1117
+ ko: "지연 로딩 지원 반응형 이미지."
1118
+ }
1119
+ },
1120
+ {
1121
+ slug: "quality",
1122
+ desc: {
1123
+ en: "Static quality and accessibility checks.",
1124
+ ja: "静的品質・アクセシビリティ検査。",
1125
+ "zh-CN": "静态质量与可访问性检查。",
1126
+ es: "Inspección estática de calidad y accesibilidad.",
1127
+ de: "Statische Qualitäts- und Barrierefreiheitsprüfung.",
1128
+ fr: "Contrôle statique de qualité et d'accessibilité.",
1129
+ ko: "정적 품질·접근성 검사."
1130
+ }
1131
+ }
1132
+ ]
1133
+ }
1134
+ ];
1135
+ function pluginsContent(language) {
1136
+ const copy = PLUGINS_COPY;
1137
+ const categories = PLUGIN_CATEGORIES;
1138
+ const lines = [frontmatter()];
1139
+ lines.push(heading(1, read(copy.title, language)), "");
1140
+ lines.push(read(copy.intro, language), "");
1141
+ lines.push(heading(2, read(copy.installHeading, language)), "");
1142
+ lines.push(read(copy.installStep1, language), "");
1143
+ lines.push(codeBlock("sh", "pnpm add @riebeckite/plugin-mermaid"));
1144
+ lines.push(read(copy.installStep2, language), "");
1145
+ lines.push(
1146
+ codeBlock(
1147
+ "ts",
1148
+ [
1149
+ 'import { defineConfig } from "@riebeckite/core";',
1150
+ 'import { l10n } from "@riebeckite/plugin-l10n";',
1151
+ 'import { mermaid } from "@riebeckite/plugin-mermaid";',
1152
+ 'import { obsidianMarkdown } from "@riebeckite/plugin-obsidian-markdown";',
1153
+ "",
1154
+ "export default defineConfig({",
1155
+ " // ...",
1156
+ " plugins: [obsidianMarkdown(), l10n({ ... }), mermaid()],",
1157
+ "});"
1158
+ ].join("\n")
1159
+ )
1160
+ );
1161
+ lines.push(read(copy.fullList, language), "");
1162
+ lines.push(`[${read(copy.fullListLabel, language)}](${PLUGIN_INDEX_URL})`, "");
1163
+ for (const category of categories) {
1164
+ lines.push(heading(2, read(category.title, language)), "");
1165
+ lines.push(read(category.summary, language), "");
1166
+ lines.push(`| ${read(copy.columnPlugin, language)} | ${read(copy.columnDescription, language)} |`);
1167
+ lines.push("| --- | --- |");
1168
+ for (const plugin of category.plugins) {
1169
+ const packageName = `@riebeckite/plugin-${plugin.slug}`;
1170
+ lines.push(
1171
+ `| [\`${packageName}\`](${pluginReadmeUrl(plugin.slug)}) | ${read(plugin.desc, language)} |`
1172
+ );
567
1173
  }
568
- };
1174
+ lines.push("");
1175
+ }
1176
+ lines.push("");
1177
+ lines.push(
1178
+ `Riebeckite: [documentation](${HELPERS.en}) · [日本語ドキュメント](${HELPERS.ja})`,
1179
+ ""
1180
+ );
1181
+ lines.push("");
1182
+ return lines.join("\n");
569
1183
  }
570
1184
 
571
- // src/scaffold/index.ts
572
- import fs5 from "node:fs/promises";
573
- import path6 from "node:path";
574
-
575
1185
  // src/scaffold/templates.ts
576
1186
  function siteTemplateFiles(variables) {
577
1187
  return [
@@ -581,7 +1191,7 @@ function siteTemplateFiles(variables) {
581
1191
  { path: "tsconfig.json", content: tsconfig() },
582
1192
  { path: ".gitignore", content: gitignore() },
583
1193
  { path: "README.md", content: readme(variables) },
584
- { path: "content/index.md", content: indexContent(variables) },
1194
+ ...localizedContentFiles(variables),
585
1195
  { path: "app/server.ts", content: server() },
586
1196
  { path: "app/client.ts", content: client() },
587
1197
  { path: "app/config.ts", content: appConfig() },
@@ -611,16 +1221,18 @@ function packageJson(variables) {
611
1221
  inspect: "riebeckite inspect"
612
1222
  },
613
1223
  dependencies: {
614
- "@riebeckite/core": "^0.0.1",
615
- "@riebeckite/honox": "^0.0.1",
616
- "@riebeckite/plugin-obsidian-markdown": "^0.0.1",
617
- "@riebeckite/theme-default": "^0.0.1",
1224
+ "@riebeckite/core": "^0.0.3",
1225
+ "@riebeckite/honox": "^0.0.3",
1226
+ "@riebeckite/plugin-color-mode": "^0.0.3",
1227
+ "@riebeckite/plugin-l10n": "^0.0.3",
1228
+ "@riebeckite/plugin-obsidian-markdown": "^0.0.3",
1229
+ "@riebeckite/theme-default": "^0.0.3",
618
1230
  hono: "^4.12.25",
619
1231
  honox: "0.1.56"
620
1232
  },
621
1233
  devDependencies: {
622
1234
  "@hono/vite-build": "^1.11.1",
623
- "@riebeckite/cli": "^0.0.1",
1235
+ "@riebeckite/cli": "^0.0.3",
624
1236
  "@types/node": "^24.5.2",
625
1237
  typescript: "^5.0.0",
626
1238
  vite: "^8.0.9"
@@ -632,22 +1244,33 @@ function packageJson(variables) {
632
1244
  `;
633
1245
  }
634
1246
  function riebeckiteConfig(variables) {
1247
+ const { title, description, baseUrl, locale } = variables;
1248
+ const languages = ["en", "ja", "zh-CN", "es", "de", "fr", "ko"];
635
1249
  return `import { defineConfig } from "@riebeckite/core";
1250
+ import { colorModePlugin } from "@riebeckite/plugin-color-mode";
1251
+ import { l10n } from "@riebeckite/plugin-l10n";
636
1252
  import { obsidianMarkdown } from "@riebeckite/plugin-obsidian-markdown";
637
1253
  import { defaultTheme } from "@riebeckite/theme-default";
638
1254
 
639
1255
  export default defineConfig({
640
1256
  site: {
641
- title: ${JSON.stringify(variables.title)},
642
- description: ${JSON.stringify(variables.description)},
643
- baseUrl: ${JSON.stringify(variables.baseUrl)},
644
- locale: ${JSON.stringify(variables.locale)},
1257
+ title: ${JSON.stringify(title)},
1258
+ description: ${JSON.stringify(description)},
1259
+ baseUrl: ${JSON.stringify(baseUrl)},
1260
+ locale: ${JSON.stringify(locale)},
645
1261
  },
646
1262
  content: {
647
1263
  directory: "content",
648
1264
  },
649
1265
  theme: defaultTheme(),
650
- plugins: [obsidianMarkdown()],
1266
+ plugins: [
1267
+ obsidianMarkdown(),
1268
+ colorModePlugin(),
1269
+ l10n({
1270
+ defaultLang: ${JSON.stringify(defaultLanguageForLocale(locale))},
1271
+ languages: ${JSON.stringify(languages)},
1272
+ }),
1273
+ ],
651
1274
  });
652
1275
  `;
653
1276
  }
@@ -729,12 +1352,83 @@ function gitignore() {
729
1352
  return ["node_modules/", "dist/", ".riebeckite/", ""].join("\n");
730
1353
  }
731
1354
  function readme(variables) {
1355
+ return defaultLanguageForLocale(variables.locale) === "ja" ? jaReadme(variables) : enReadme(variables);
1356
+ }
1357
+ var REPO = "https://github.com/Rerurate514/riebeckite";
1358
+ function enReadme(variables) {
732
1359
  return [
733
1360
  `# ${variables.title}`,
734
1361
  "",
735
- "A minimal Riebeckite site generated by `riebeckite init`.",
1362
+ "A multilingual Riebeckite site generated by `riebeckite init` — seven",
1363
+ "languages out of the box, plus a tour of the plugin and theme ecosystem.",
1364
+ "",
1365
+ "## What's inside",
1366
+ "",
1367
+ "- **Seven languages** (`en`, `ja`, `zh-CN`, `es`, `de`, `fr`, `ko`) via",
1368
+ ` [\`@riebeckite/plugin-l10n\`](${REPO}/blob/main/packages/plugins/l10n/README.md).`,
1369
+ " Every page is translated; non-default languages are served under `/lang/`",
1370
+ " paths, and each page carries a language switcher.",
1371
+ "- **Ecosystem tour pages**, written in all seven languages:",
1372
+ " - `/framework/plugins` — representative plugins grouped by capability.",
1373
+ " - `/framework/themes` — the bundled themes and how to switch.",
1374
+ "- The **HonoX application shell** under `app/` — routes, components, and the",
1375
+ " renderer are yours to extend.",
1376
+ "",
1377
+ "Run `pnpm dev` and open the site to explore.",
1378
+ "",
1379
+ "## Commands",
1380
+ "",
1381
+ "```sh",
1382
+ "pnpm install",
1383
+ "pnpm exec riebeckite check",
1384
+ "pnpm exec riebeckite dev",
1385
+ "pnpm exec riebeckite build",
1386
+ "```",
1387
+ "",
1388
+ "## Localization",
1389
+ "",
1390
+ "Content lives in `content/` as plain Markdown. Translations sit next to the",
1391
+ "default file using the `<base>.<lang>.md` convention (`about.ja.md`,",
1392
+ "`about.en.md`). The default language keeps the unsuffixed path; the rest are",
1393
+ "served under `/lang/`.",
1394
+ "",
1395
+ "To add or remove a language, edit the `l10n(...)` plugin in",
1396
+ "`riebeckite.config.ts` and add or remove the matching translation files.",
1397
+ "",
1398
+ "## Extending",
1399
+ "",
1400
+ `- **Plugins**: browse [packages/plugins](${REPO}/tree/main/packages/plugins)`,
1401
+ " in the Riebeckite repository — each package has its own README with options",
1402
+ " and examples.",
1403
+ "- **Themes**: this starter uses `@riebeckite/theme-default`; swap it for",
1404
+ " another theme in `riebeckite.config.ts`.",
1405
+ `- Docs: [English](${REPO}/blob/main/docs/en/README.md)`,
1406
+ ` · [日本語](${REPO}/blob/main/docs/ja/README.md)`,
1407
+ ""
1408
+ ].join("\n");
1409
+ }
1410
+ function jaReadme(variables) {
1411
+ return [
1412
+ `# ${variables.title}`,
1413
+ "",
1414
+ "`riebeckite init` で生成した多言語対応の Riebeckite サイトです。7 言語に対応し、",
1415
+ "プラグインとテーマのエコシステムを体感できる構成になっています。",
1416
+ "",
1417
+ "## 含まれているもの",
1418
+ "",
1419
+ "- **7 言語対応**(`en`, `ja`, `zh-CN`, `es`, `de`, `fr`, `ko`)—",
1420
+ ` [\`@riebeckite/plugin-l10n\`](${REPO}/blob/main/packages/plugins/l10n/README_ja.md)`,
1421
+ " によるもの。すべてのページが翻訳され、既定言語以外は `/lang/` パスで配信され",
1422
+ " ます。各ページには言語スイッチャーが付いています。",
1423
+ "- **エコシステム紹介ページ**(全 7 言語で作成):",
1424
+ " - `/framework/plugins` — 代表的なプラグインを機能別に紹介。",
1425
+ " - `/framework/themes` — 同梱のテーマと切り替え方。",
1426
+ "- **HonoX アプリケーションシェル** `app/` — ルート・コンポーネント・レンダラー",
1427
+ " は自由に拡張できます。",
1428
+ "",
1429
+ "`pnpm dev` を実行してサイトを開いてください。",
736
1430
  "",
737
- "## Commands",
1431
+ "## コマンド",
738
1432
  "",
739
1433
  "```sh",
740
1434
  "pnpm install",
@@ -743,24 +1437,24 @@ function readme(variables) {
743
1437
  "pnpm exec riebeckite build",
744
1438
  "```",
745
1439
  "",
746
- "Content lives in `content/`. The site owns `app/routes`, `app/components`,",
747
- "`app/islands`, and `app/style.css`; see the Riebeckite HonoX integration",
748
- "documentation for the site application contract.",
749
- ""
750
- ].join("\n");
751
- }
752
- function indexContent(variables) {
753
- return [
754
- "---",
755
- "publish: true",
756
- "---",
1440
+ "## ローカライズ",
757
1441
  "",
758
- `# ${variables.title}`,
1442
+ "コンテンツは `content/` にプレーンな Markdown として置きます。翻訳ページは既定",
1443
+ "ファイルの隣に `<base>.<lang>.md` の規則で配置します(`about.ja.md`、",
1444
+ "`about.en.md` など)。既定言語は接尾辞なしのパス、それ以外は `/lang/` のパスで",
1445
+ "配信されます。",
759
1446
  "",
760
- "Welcome to your Riebeckite site.",
1447
+ "言語の追加・削除は `riebeckite.config.ts` の `l10n(...)` プラグインを編集し、",
1448
+ "対応する翻訳ファイルを追加・削除してください。",
761
1449
  "",
762
- "Edit `content/index.md` to change this page, and add more Markdown files",
763
- "next to it. Files need `publish: true` frontmatter before they appear.",
1450
+ "## 拡張",
1451
+ "",
1452
+ `- **プラグイン**: [packages/plugins](${REPO}/tree/main/packages/plugins) を`,
1453
+ " 参照してください。各パッケージにはオプションと例付きの README があります。",
1454
+ "- **テーマ**: このスターターは `@riebeckite/theme-default` を使用しています。",
1455
+ " `riebeckite.config.ts` で別のテーマに差し替えられます。",
1456
+ `- ドキュメント: [English](${REPO}/blob/main/docs/en/README.md)`,
1457
+ ` · [日本語](${REPO}/blob/main/docs/ja/README.md)`,
764
1458
  ""
765
1459
  ].join("\n");
766
1460
  }
@@ -845,9 +1539,21 @@ export const ASSETS_ROOT = "public/";
845
1539
  `;
846
1540
  }
847
1541
  function globalDeclarations() {
848
- return `declare module "virtual:riebeckite/client" {
1542
+ return `import type { PluginHeadTag } from "@riebeckite/core";
1543
+
1544
+ declare module "virtual:riebeckite/client" {
849
1545
  export function initRiebeckiteClient(): void;
850
1546
  }
1547
+
1548
+ declare module "hono" {
1549
+ interface Env {
1550
+ Variables: {
1551
+ headTags?: readonly PluginHeadTag[];
1552
+ htmlLanguage?: string;
1553
+ };
1554
+ Bindings: Record<string, never>;
1555
+ }
1556
+ }
851
1557
  `;
852
1558
  }
853
1559
  function style() {
@@ -861,6 +1567,8 @@ body {
861
1567
 
862
1568
  .site-header {
863
1569
  display: flex;
1570
+ align-items: center;
1571
+ justify-content: space-between;
864
1572
  gap: 1rem;
865
1573
  padding: 1rem;
866
1574
  border-bottom: 1px solid #d1d5db;
@@ -881,7 +1589,8 @@ body {
881
1589
  `;
882
1590
  }
883
1591
  function siteHeader() {
884
- return `import { config } from "../config";
1592
+ return `import { ColorModeToggle } from "@riebeckite/plugin-color-mode";
1593
+ import { config } from "../config";
885
1594
 
886
1595
  export function SiteHeader() {
887
1596
  return (
@@ -889,6 +1598,7 @@ export function SiteHeader() {
889
1598
  <a href="/" class="site-header__home">
890
1599
  {config.site.title}
891
1600
  </a>
1601
+ <ColorModeToggle />
892
1602
  </header>
893
1603
  );
894
1604
  }
@@ -898,11 +1608,23 @@ function article() {
898
1608
  return `import type { PostContent } from "@riebeckite/core";
899
1609
  import { Article, ArticleContent, ArticleLayout } from "@riebeckite/honox/ui";
900
1610
 
901
- export function SiteArticle({ post }: { post: PostContent }) {
1611
+ export function SiteArticle({
1612
+ post,
1613
+ afterMeta,
1614
+ }: {
1615
+ post: PostContent;
1616
+ afterMeta?: string;
1617
+ }) {
902
1618
  return (
903
1619
  <Article class="site-article">
904
1620
  <ArticleLayout>
905
1621
  <ArticleContent>
1622
+ {afterMeta ? (
1623
+ <div
1624
+ class="site-article__after-meta"
1625
+ dangerouslySetInnerHTML={{ __html: afterMeta }}
1626
+ />
1627
+ ) : null}
906
1628
  <div dangerouslySetInnerHTML={{ __html: post.html ?? "" }} />
907
1629
  </ArticleContent>
908
1630
  </ArticleLayout>
@@ -912,8 +1634,10 @@ export function SiteArticle({ post }: { post: PostContent }) {
912
1634
  `;
913
1635
  }
914
1636
  function renderer() {
915
- return `import { jsxRenderer } from "hono/jsx-renderer";
1637
+ return `import type { PluginHeadTag } from "@riebeckite/core";
1638
+ import { jsxRenderer } from "hono/jsx-renderer";
916
1639
  import { Link, Script } from "honox/server";
1640
+ import { ColorModeScript } from "@riebeckite/plugin-color-mode";
917
1641
  import { SiteHeader } from "../components/site-header";
918
1642
  import { config } from "../config";
919
1643
 
@@ -929,14 +1653,21 @@ function themeAttributes() {
929
1653
  };
930
1654
  }
931
1655
 
932
- export default jsxRenderer(({ children }) => {
1656
+ export default jsxRenderer(({ children }, c) => {
1657
+ const headTags: readonly PluginHeadTag[] = c.get("headTags") ?? [];
1658
+
933
1659
  return (
934
- <html lang={config.site.locale} {...themeAttributes()}>
1660
+ <html
1661
+ lang={c.get("htmlLanguage") ?? config.site.locale}
1662
+ {...themeAttributes()}
1663
+ >
935
1664
  <head>
936
1665
  <meta charset="utf-8" />
937
1666
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
938
1667
  <title>{config.site.title}</title>
1668
+ <ColorModeScript />
939
1669
  <Link href="/app/style.css" rel="stylesheet" />
1670
+ {headTags.map(renderHeadTag)}
940
1671
  <Script src="/app/client.ts" async />
941
1672
  </head>
942
1673
  <body class="riebeckite-page">
@@ -946,6 +1677,21 @@ export default jsxRenderer(({ children }) => {
946
1677
  </html>
947
1678
  );
948
1679
  });
1680
+
1681
+ function renderHeadTag(tag: PluginHeadTag, index: number) {
1682
+ const key = \`\${tag.tag}-\${index}\`;
1683
+ if (tag.tag === "meta") return <meta {...tag.attrs} key={key} />;
1684
+ if (tag.tag === "link") return <link {...tag.attrs} key={key} />;
1685
+ return (
1686
+ <script
1687
+ {...tag.attrs}
1688
+ key={key}
1689
+ dangerouslySetInnerHTML={
1690
+ tag.children ? { __html: tag.children } : undefined
1691
+ }
1692
+ />
1693
+ );
1694
+ }
949
1695
  `;
950
1696
  }
951
1697
  function indexRoute() {
@@ -967,7 +1713,17 @@ export default createRoute(async (c) => {
967
1713
  return c.notFound();
968
1714
  }
969
1715
 
970
- return c.render(<SiteArticle post={post} />);
1716
+ if (indexEntry) {
1717
+ c.set("htmlLanguage", indexEntry.publicLocation.metadata?.["l10n.lang"]);
1718
+ c.set("headTags", indexEntry.headTags ?? []);
1719
+ }
1720
+
1721
+ return c.render(
1722
+ <SiteArticle
1723
+ post={post}
1724
+ afterMeta={indexEntry?.bodySlots?.["article.after-meta"]}
1725
+ />,
1726
+ );
971
1727
  });
972
1728
  `;
973
1729
  }
@@ -1001,76 +1757,505 @@ export default createRoute(
1001
1757
  if (route.kind === "redirect")
1002
1758
  return c.redirect(route.location, route.status);
1003
1759
 
1004
- const post = await content.getProcessedContent(route.entry.slug);
1005
- if (!isPublished(config, post.frontmatter)) {
1006
- return c.notFound();
1760
+ const post = await content.getProcessedContent(route.entry.slug);
1761
+ if (!isPublished(config, post.frontmatter)) {
1762
+ return c.notFound();
1763
+ }
1764
+
1765
+ c.set("htmlLanguage", route.entry.publicLocation.metadata?.["l10n.lang"]);
1766
+ c.set("headTags", route.entry.headTags ?? []);
1767
+
1768
+ return c.render(
1769
+ <SiteArticle
1770
+ post={post}
1771
+ afterMeta={route.entry.bodySlots?.["article.after-meta"]}
1772
+ />,
1773
+ );
1774
+ },
1775
+ );
1776
+ `;
1777
+ }
1778
+
1779
+ // src/scaffold/index.ts
1780
+ var ScaffoldSiteError = class extends Error {
1781
+ constructor(message) {
1782
+ super(message);
1783
+ this.name = "ScaffoldSiteError";
1784
+ }
1785
+ };
1786
+ async function scaffoldRiebeckiteSite(options) {
1787
+ const targetDirectory = path4.resolve(options.targetDirectory);
1788
+ const files = siteTemplateFiles(templateVariables(options, targetDirectory));
1789
+ await assertTargetWritable(
1790
+ targetDirectory,
1791
+ files,
1792
+ options.overwrite ?? false
1793
+ );
1794
+ for (const file of files) {
1795
+ const filePath = path4.join(targetDirectory, file.path);
1796
+ await fs4.mkdir(path4.dirname(filePath), { recursive: true });
1797
+ await fs4.writeFile(filePath, file.content, "utf8");
1798
+ }
1799
+ return { targetDirectory, files: files.map((file) => file.path) };
1800
+ }
1801
+ function templateVariables(options, targetDirectory) {
1802
+ const name = normalizePackageName(
1803
+ options.name ?? path4.basename(targetDirectory)
1804
+ );
1805
+ return {
1806
+ name,
1807
+ title: options.siteTitle ?? name,
1808
+ description: options.description ?? "",
1809
+ baseUrl: options.baseUrl ?? "",
1810
+ locale: options.locale ?? "en"
1811
+ };
1812
+ }
1813
+ async function assertTargetWritable(targetDirectory, files, overwrite) {
1814
+ let entries;
1815
+ try {
1816
+ entries = await fs4.readdir(targetDirectory);
1817
+ } catch (error) {
1818
+ if (isNotFoundError(error)) return;
1819
+ throw error;
1820
+ }
1821
+ const conflicts = entries.filter(
1822
+ (entry) => files.some((file) => file.path.split("/")[0] === entry)
1823
+ );
1824
+ if (conflicts.length === 0) return;
1825
+ if (overwrite) return;
1826
+ throw new ScaffoldSiteError(
1827
+ `The target directory is not empty: ${targetDirectory}
1828
+ Existing entries: ${conflicts.slice(0, 5).join(", ")}
1829
+ Pass --force to overwrite generated files.`
1830
+ );
1831
+ }
1832
+ function normalizePackageName(value) {
1833
+ const normalized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-_.]+/, "").replace(/[-_.]+$/, "");
1834
+ return normalized || "riebeckite-site";
1835
+ }
1836
+ function isNotFoundError(error) {
1837
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
1838
+ }
1839
+
1840
+ // src/ssg_plugin.ts
1841
+ import { defaultExtensionMap, toSSG } from "hono/ssg";
1842
+ import { relative } from "node:path";
1843
+ import { createServer } from "vite";
1844
+ var defaultEntry = "./src/index.tsx";
1845
+ function riebeckiteSsg(options = {}) {
1846
+ const virtualId = "virtual:riebeckite-ssg-void-entry";
1847
+ const resolvedVirtualId = `\0${virtualId}`;
1848
+ const entry = options.entry ?? defaultEntry;
1849
+ let resolvedConfig;
1850
+ return {
1851
+ name: "riebeckite-ssg",
1852
+ apply: "build",
1853
+ enforce: "post",
1854
+ config() {
1855
+ return {
1856
+ build: {
1857
+ rollupOptions: {
1858
+ input: [virtualId]
1859
+ }
1860
+ }
1861
+ };
1862
+ },
1863
+ configResolved(config) {
1864
+ resolvedConfig = config;
1865
+ },
1866
+ resolveId(id) {
1867
+ return id === virtualId ? resolvedVirtualId : void 0;
1868
+ },
1869
+ load(id) {
1870
+ return id === resolvedVirtualId ? 'console.log("suppress empty chunk message")' : void 0;
1871
+ },
1872
+ async generateBundle(_outputOptions, bundle) {
1873
+ const config = resolvedConfig;
1874
+ if (!config) {
1875
+ throw new Error("Riebeckite SSG could not resolve the Vite configuration.");
1876
+ }
1877
+ removeVirtualEntryChunk(bundle, resolvedVirtualId);
1878
+ const server2 = await createServer({
1879
+ root: config.root,
1880
+ define: config.define,
1881
+ resolve: {
1882
+ ...config.resolve,
1883
+ builtins: [...config.resolve.builtins, /^node:/]
1884
+ },
1885
+ plugins: [],
1886
+ build: { ssr: true },
1887
+ mode: config.mode
1888
+ });
1889
+ try {
1890
+ const module = await server2.ssrLoadModule(entry);
1891
+ const app = module.default;
1892
+ if (!app) {
1893
+ throw new Error(`Failed to find a default export from ${entry}.`);
1894
+ }
1895
+ const generatedHtml = [];
1896
+ const result = await toSSG(
1897
+ app,
1898
+ {
1899
+ writeFile: async (filePath, data) => {
1900
+ const fileName = relative(config.build.outDir, filePath).replaceAll(
1901
+ "\\",
1902
+ "/"
1903
+ );
1904
+ if (fileName.endsWith(".html") && typeof data === "string") {
1905
+ generatedHtml.push({ path: fileName, html: data });
1906
+ }
1907
+ this.emitFile({
1908
+ type: "asset",
1909
+ fileName,
1910
+ source: data
1911
+ });
1912
+ },
1913
+ async mkdir() {
1914
+ }
1915
+ },
1916
+ {
1917
+ dir: config.build.outDir,
1918
+ plugins: options.plugins ?? [],
1919
+ extensionMap: options.extensionMap ?? defaultExtensionMap
1920
+ }
1921
+ );
1922
+ if (!result.success) throw result.error;
1923
+ await emitGeneratedOutputs(
1924
+ module,
1925
+ (asset) => this.emitFile(asset)
1926
+ );
1927
+ inspectGeneratedHtmlPages(module, generatedHtml, {
1928
+ warn: (message) => this.warn(message),
1929
+ info: (message) => this.info(message)
1930
+ });
1931
+ } finally {
1932
+ await server2.close();
1933
+ }
1934
+ }
1935
+ };
1936
+ }
1937
+ async function emitGeneratedOutputs(module, emit) {
1938
+ const content = module.content;
1939
+ if (!content) return;
1940
+ const manifest = await content.getManifest();
1941
+ for (const output of manifest.generatedOutputs ?? []) {
1942
+ emit({
1943
+ type: "asset",
1944
+ fileName: output.path,
1945
+ source: output.content
1946
+ });
1947
+ }
1948
+ }
1949
+ function inspectGeneratedHtmlPages(module, pages, log) {
1950
+ const plugins = module.config?.plugins ?? [];
1951
+ for (const plugin of plugins) {
1952
+ const inspector = plugin.inspectGeneratedHtml;
1953
+ if (typeof inspector !== "function") continue;
1954
+ for (const page of pages) {
1955
+ for (const diagnostic of inspector(page) ?? []) {
1956
+ const label = `[${plugin.name ?? "plugin"}] ${page.path}: ${diagnostic.message}`;
1957
+ if (diagnostic.severity === "info") log.info(label);
1958
+ else log.warn(label);
1959
+ }
1960
+ }
1961
+ }
1962
+ }
1963
+ function removeVirtualEntryChunk(bundle, virtualEntryId) {
1964
+ for (const chunk of Object.values(bundle)) {
1965
+ if (chunk.type === "chunk" && chunk.moduleIds?.includes(virtualEntryId)) {
1966
+ delete bundle[chunk.fileName];
1967
+ }
1968
+ }
1969
+ }
1970
+
1971
+ // src/ssg.ts
1972
+ var defaultSsgEntry = "./app/server.ts";
1973
+ function riebeckiteSsgExtensionMap() {
1974
+ return {
1975
+ "application/atom+xml": "xml",
1976
+ "application/json": "json",
1977
+ "application/rss+xml": "xml",
1978
+ "application/xml": "xml",
1979
+ "text/html": "html",
1980
+ "text/plain": "txt",
1981
+ "text/xml": "xml"
1982
+ };
1983
+ }
1984
+ function createRiebeckiteSsg(options = {}) {
1985
+ return riebeckiteSsg({
1986
+ ...options,
1987
+ entry: options.entry ?? defaultSsgEntry,
1988
+ extensionMap: {
1989
+ ...riebeckiteSsgExtensionMap(),
1990
+ ...options.extensionMap
1991
+ }
1992
+ });
1993
+ }
1994
+
1995
+ // src/vite_plugin.ts
1996
+ import path7 from "node:path";
1997
+
1998
+ // src/asset_entries.ts
1999
+ import fs5 from "node:fs";
2000
+ import path5 from "node:path";
2001
+ import { uniqueStrings } from "@riebeckite/core";
2002
+ function writeRiebeckiteAssetEntries(config, paths2) {
2003
+ writeGeneratedFile(paths2.pluginStyles, createPluginStylesEntry(config));
2004
+ writeGeneratedFile(paths2.themeStyles, createThemeStylesEntry(config));
2005
+ }
2006
+ function createPluginStylesEntry(config) {
2007
+ return createStylesheetEntry(
2008
+ collectPluginStyleSpecifiers(config),
2009
+ "Plugin default styles, in resolved plugin order. Generated by @riebeckite/honox; do not edit. Import this before the theme stylesheet so a theme can override plugin defaults."
2010
+ );
2011
+ }
2012
+ function createThemeStylesEntry(config) {
2013
+ return createStylesheetEntry(
2014
+ config.theme.styles.map((style2) => style2.moduleSpecifier),
2015
+ "Theme styles. Generated by @riebeckite/honox; do not edit. Import this after the plugin stylesheet so the theme wins the plugin/theme cascade."
2016
+ );
2017
+ }
2018
+ function collectPluginStyleSpecifiers(config) {
2019
+ return config.plugins.flatMap(
2020
+ (plugin) => (plugin.assets ?? []).filter((asset) => asset.kind === "style").map((asset) => asset.moduleSpecifier)
2021
+ );
2022
+ }
2023
+ function createStylesheetEntry(moduleSpecifiers, banner) {
2024
+ const imports = uniqueStrings(moduleSpecifiers).map((specifier) => `@import ${JSON.stringify(specifier)};`).join("\n");
2025
+ if (!banner) return imports;
2026
+ const header = `/* ${banner.replace(/\*\//g, "* /")} */`;
2027
+ return imports ? `${header}
2028
+ ${imports}
2029
+ ` : `${header}
2030
+ `;
2031
+ }
2032
+ function writeGeneratedFile(filePath, contents) {
2033
+ fs5.mkdirSync(path5.dirname(filePath), { recursive: true });
2034
+ fs5.writeFileSync(filePath, contents);
2035
+ }
2036
+
2037
+ // src/client_module.ts
2038
+ import {
2039
+ serializePublicClientConfig
2040
+ } from "@riebeckite/core";
2041
+ var clientModuleId = "virtual:riebeckite/client";
2042
+ var resolvedClientModuleId = `\0${clientModuleId}`;
2043
+ function riebeckiteClientModule(getConfig) {
2044
+ return {
2045
+ name: "riebeckite-client-module",
2046
+ resolveId(id) {
2047
+ if (id === clientModuleId) return resolvedClientModuleId;
2048
+ return null;
2049
+ },
2050
+ load(id) {
2051
+ if (id !== resolvedClientModuleId) return null;
2052
+ return createClientModule(getConfig());
1007
2053
  }
2054
+ };
2055
+ }
2056
+ function createClientModule(config) {
2057
+ const imports = [];
2058
+ const initializers = [];
2059
+ for (const [index, entry] of config.plugins.flatMap((plugin) => plugin.clientEntries ?? []).entries()) {
2060
+ const localName = `pluginClientInitializer${index}`;
2061
+ const importTarget = entry.exportName ? `{ ${entry.exportName} as ${localName} }` : localName;
2062
+ imports.push(
2063
+ `import ${importTarget} from ${JSON.stringify(entry.moduleSpecifier)};`
2064
+ );
2065
+ const publicConfig = entry.publicConfig;
2066
+ initializers.push(
2067
+ publicConfig === void 0 ? `${localName}()` : `${localName}(${serializePublicClientConfig(publicConfig)})`
2068
+ );
2069
+ }
2070
+ return `${imports.join("\n")}
1008
2071
 
1009
- return c.render(<SiteArticle post={post} />);
1010
- },
1011
- );
2072
+ export function initRiebeckiteClient() {
2073
+ ${initializers.map((initializer) => ` ${initializer};`).join("\n")}
2074
+ }
1012
2075
  `;
1013
2076
  }
1014
2077
 
1015
- // src/scaffold/index.ts
1016
- var ScaffoldSiteError = class extends Error {
2078
+ // src/vite_runner.ts
2079
+ import fs6 from "node:fs/promises";
2080
+ import path6 from "node:path";
2081
+ import { build, createServer as createServer2 } from "vite";
2082
+ var viteConfigFileNames = [
2083
+ "vite.config.ts",
2084
+ "vite.config.js",
2085
+ "vite.config.mjs"
2086
+ ];
2087
+ async function resolveHonoxApplicationRoot(configRoot) {
2088
+ const roots = await findViteApplicationRoots(configRoot);
2089
+ if (roots.length === 1) return roots[0];
2090
+ if (roots.length === 0) {
2091
+ throw new HonoxApplicationRootError(
2092
+ `Could not find a Vite application under ${configRoot}.`
2093
+ );
2094
+ }
2095
+ throw new HonoxApplicationRootError(
2096
+ `Found multiple Vite applications under ${configRoot}: ${roots.join(", ")}.`
2097
+ );
2098
+ }
2099
+ var HonoxApplicationRootError = class extends Error {
1017
2100
  constructor(message) {
1018
2101
  super(message);
1019
- this.name = "ScaffoldSiteError";
2102
+ this.name = "HonoxApplicationRootError";
1020
2103
  }
1021
2104
  };
1022
- async function scaffoldRiebeckiteSite(options) {
1023
- const targetDirectory = path6.resolve(options.targetDirectory);
1024
- const files = siteTemplateFiles(templateVariables(options, targetDirectory));
1025
- await assertTargetWritable(
1026
- targetDirectory,
1027
- files,
1028
- options.overwrite ?? false
1029
- );
1030
- for (const file of files) {
1031
- const filePath = path6.join(targetDirectory, file.path);
1032
- await fs5.mkdir(path6.dirname(filePath), { recursive: true });
1033
- await fs5.writeFile(filePath, file.content, "utf8");
1034
- }
1035
- return { targetDirectory, files: files.map((file) => file.path) };
1036
- }
1037
- function templateVariables(options, targetDirectory) {
1038
- const name = normalizePackageName(
1039
- options.name ?? path6.basename(targetDirectory)
1040
- );
2105
+ async function resolveHonoxApplication(options) {
2106
+ const configRoot = path6.resolve(options.configRoot);
2107
+ const appRoot = options.appRoot ? path6.resolve(options.appRoot) : await resolveHonoxApplicationRoot(configRoot);
2108
+ const workspaceRoot = options.workspaceRoot ?? await findWorkspaceRoot(configRoot);
2109
+ const rawConfig = await loadRiebeckiteConfig({
2110
+ configRoot,
2111
+ configFile: options.configFile,
2112
+ workspaceRoot
2113
+ });
2114
+ const config = resolveHonoxConfig(rawConfig, appRoot);
1041
2115
  return {
1042
- name,
1043
- title: options.siteTitle ?? name,
1044
- description: options.description ?? "",
1045
- baseUrl: options.baseUrl ?? "",
1046
- locale: options.locale ?? "en"
2116
+ config,
2117
+ configRoot,
2118
+ appRoot,
2119
+ contentRoot: config.content.directory
1047
2120
  };
1048
2121
  }
1049
- async function assertTargetWritable(targetDirectory, files, overwrite) {
1050
- let entries;
2122
+ async function findWorkspaceRoot(configRoot) {
1051
2123
  try {
1052
- entries = await fs5.readdir(targetDirectory);
1053
- } catch (error) {
1054
- if (isNotFoundError(error)) return;
1055
- throw error;
2124
+ const workspaceConfig = path6.join(configRoot, "pnpm-workspace.yaml");
2125
+ return (await fs6.stat(workspaceConfig)).isFile() ? configRoot : void 0;
2126
+ } catch {
2127
+ return void 0;
1056
2128
  }
1057
- const conflicts = entries.filter(
1058
- (entry) => files.some((file) => file.path.split("/")[0] === entry)
2129
+ }
2130
+ async function startHonoxDevServer(options) {
2131
+ const server2 = await createServer2({ root: options.root });
2132
+ await server2.listen();
2133
+ server2.printUrls();
2134
+ await waitForShutdown(server2);
2135
+ }
2136
+ async function buildHonoxApplication(options) {
2137
+ await runTraced(
2138
+ options.tracer,
2139
+ "integration.honox.client_build",
2140
+ () => build({ root: options.root, mode: "client" })
1059
2141
  );
1060
- if (conflicts.length === 0) return;
1061
- if (overwrite) return;
1062
- throw new ScaffoldSiteError(
1063
- `The target directory is not empty: ${targetDirectory}
1064
- Existing entries: ${conflicts.slice(0, 5).join(", ")}
1065
- Pass --force to overwrite generated files.`
2142
+ await runTraced(
2143
+ options.tracer,
2144
+ "integration.honox.server_build",
2145
+ () => build({ root: options.root })
1066
2146
  );
1067
2147
  }
1068
- function normalizePackageName(value) {
1069
- const normalized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[-_.]+/, "").replace(/[-_.]+$/, "");
1070
- return normalized || "riebeckite-site";
2148
+ async function runTraced(tracer, name, buildTask) {
2149
+ return tracer ? await tracer.span(name, {}, buildTask) : await buildTask();
1071
2150
  }
1072
- function isNotFoundError(error) {
1073
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
2151
+ async function waitForShutdown(server2) {
2152
+ await new Promise((resolve) => {
2153
+ const close = () => {
2154
+ void server2.close().finally(resolve);
2155
+ };
2156
+ process.once("SIGINT", close);
2157
+ process.once("SIGTERM", close);
2158
+ });
2159
+ }
2160
+ async function findViteApplicationRoots(root) {
2161
+ const directories = [root];
2162
+ const applicationRoots = [];
2163
+ while (directories.length > 0) {
2164
+ const directory = directories.pop();
2165
+ if (!directory) continue;
2166
+ if (await hasViteConfig(directory)) applicationRoots.push(directory);
2167
+ const entries = await fs6.readdir(directory, { withFileTypes: true });
2168
+ for (const entry of entries) {
2169
+ if (!entry.isDirectory() || entry.name === "node_modules" || entry.name === ".git" || entry.name === "tests") {
2170
+ continue;
2171
+ }
2172
+ directories.push(path6.join(directory, entry.name));
2173
+ }
2174
+ }
2175
+ return applicationRoots;
2176
+ }
2177
+ async function hasViteConfig(directory) {
2178
+ for (const fileName of viteConfigFileNames) {
2179
+ try {
2180
+ if ((await fs6.stat(path6.join(directory, fileName))).isFile()) return true;
2181
+ } catch {
2182
+ }
2183
+ }
2184
+ return false;
2185
+ }
2186
+
2187
+ // src/vite_plugin.ts
2188
+ var defaultSsrExternals = [
2189
+ "extend",
2190
+ "debug",
2191
+ "node:fs/promises",
2192
+ "node:path",
2193
+ "parse-numeric-range",
2194
+ "slugify",
2195
+ "vfile-matter",
2196
+ "yaml"
2197
+ ];
2198
+ function riebeckite(options = {}) {
2199
+ let resolvedConfig;
2200
+ const getConfig = () => {
2201
+ if (!resolvedConfig) {
2202
+ throw new Error("Riebeckite config has not been loaded yet.");
2203
+ }
2204
+ return resolvedConfig;
2205
+ };
2206
+ return [
2207
+ {
2208
+ name: "riebeckite-host-integration",
2209
+ async config(userConfig) {
2210
+ const root = userConfig.root ? path7.resolve(userConfig.root) : process.cwd();
2211
+ const appRoot = options.appRoot ?? root;
2212
+ const application = await resolveHonoxApplication({
2213
+ configRoot: options.configRoot ?? appRoot,
2214
+ configFile: options.configFile,
2215
+ appRoot,
2216
+ workspaceRoot: options.workspaceRoot
2217
+ });
2218
+ resolvedConfig = application.config;
2219
+ writeRiebeckiteAssetEntries(resolvedConfig, {
2220
+ pluginStyles: path7.join(appRoot, "app/.riebeckite/plugin-styles.css"),
2221
+ themeStyles: path7.join(appRoot, "app/.riebeckite/theme-styles.css")
2222
+ });
2223
+ return {
2224
+ define: {
2225
+ "process.env.RIEBECKITE_APP_ROOT": JSON.stringify(appRoot)
2226
+ },
2227
+ resolve: {
2228
+ alias: options.workspaceRoot ? createWorkspacePackageAliases(options.workspaceRoot) : []
2229
+ }
2230
+ };
2231
+ }
2232
+ },
2233
+ riebeckiteClientModule(getConfig)
2234
+ ];
2235
+ }
2236
+ function riebeckiteVite(options = {}) {
2237
+ const { ssg, ssrExternals, ...integration } = options;
2238
+ return [
2239
+ ...riebeckite(integration),
2240
+ riebeckiteSsrExternals([...defaultSsrExternals, ...ssrExternals ?? []]),
2241
+ createRiebeckiteSsg(ssg)
2242
+ ];
2243
+ }
2244
+ function riebeckiteSsrExternals(externals) {
2245
+ return {
2246
+ name: "riebeckite-ssr-externals",
2247
+ config() {
2248
+ return {
2249
+ environments: {
2250
+ ssr: {
2251
+ resolve: {
2252
+ external: [...externals]
2253
+ }
2254
+ }
2255
+ }
2256
+ };
2257
+ }
2258
+ };
1074
2259
  }
1075
2260
  export {
1076
2261
  ScaffoldSiteError,
@@ -1079,6 +2264,7 @@ export {
1079
2264
  defaultSsgEntry,
1080
2265
  defaultSsrExternals,
1081
2266
  loadRiebeckiteConfig,
2267
+ replaceHonoxIslandDependencyPlugin,
1082
2268
  resolveHonoxApplication,
1083
2269
  resolveHonoxApplicationRoot,
1084
2270
  resolveHonoxConfig,