diff --git a/docs/content.config.ts b/docs/content.config.ts index 54b51cd..21c0654 100644 --- a/docs/content.config.ts +++ b/docs/content.config.ts @@ -18,6 +18,21 @@ export const docsContentConfig = { }, sections: { showcase: { layout: 'content' }, + examples: { + layout: 'docs', + sidebar: [ + { + type: 'group', + label: { en: 'Examples', zh: '使用示例' }, + items: [ + { type: 'item', href: '/examples/variants', label: { en: 'Template variants', zh: '同一模板,不同版本' } }, + { type: 'item', href: '/examples/skills', label: { en: 'Shared skills', zh: '复用常用 Skills' } }, + { type: 'item', href: '/examples/monorepo', label: { en: 'Extract from a monorepo', zh: '提取 monorepo 子项目' } }, + { type: 'item', href: '/examples/file-changes', label: { en: 'Edit files during creation', zh: '创建时调整文件' } }, + ], + }, + ], + }, guide: { layout: 'docs', sidebar: [ diff --git a/docs/docs.config.ts b/docs/docs.config.ts index 94c1ba8..180b116 100644 --- a/docs/docs.config.ts +++ b/docs/docs.config.ts @@ -8,6 +8,7 @@ import { PresetFooter } from './src/site/footer' import { DocsSiteLink } from './src/site/docs-link' import { headerNavigation } from './src/site/navigation' import { ShowcaseProject } from './src/showcase/showcase-project' +import { ExampleExplorer } from './src/examples/example-explorer' export const docsConfig = defineDocsConfig({ content: docsContentConfig, @@ -26,8 +27,8 @@ export const docsConfig = defineDocsConfig({ eyebrow: false, title: 'Create Preset', description: { - en: 'Provides the ability to quickly create preset projects.', - zh: '提供快速创建预设项目模板的能力。', + en: 'Similar templates. Still maintaining separate copies?', + zh: '模板相似,却要维护多份?', }, primaryAction: { href: '/guide/getting-started', @@ -40,6 +41,7 @@ export const docsConfig = defineDocsConfig({ FadePreview: DocsFadePreview, PackageManagerCommands, ShowcaseProject, + ExampleExplorer, }, }, theme: { diff --git a/docs/package.json b/docs/package.json index bb16426..6a1ee5e 100644 --- a/docs/package.json +++ b/docs/package.json @@ -16,6 +16,7 @@ "dependencies": { "@bassist/utils": "^0.17.0", "@blackwork/docs": "^0.6.0", + "@blackwork/machine": "0.2.2", "@blackwork/search": "^0.1.1", "blackwork": "^0.14.0", "clsx": "^2.1.1", @@ -23,6 +24,7 @@ "next": "^16.3.8", "react": "^19.2.8", "react-dom": "^19.2.8", + "shiki": "^4.4.3", "tailwind-merge": "^3.6.0" }, "devDependencies": { diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index 1a736d4..e824ae2 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -178,6 +178,9 @@ importers: '@blackwork/docs': specifier: ^0.6.0 version: 0.6.0(blackwork@0.14.0(@bassist/utils@0.17.0)(@types/react@19.2.18)(clsx@2.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwind-merge@3.6.0)(tailwindcss@4.3.3))(next@16.3.8(@types/node@24.13.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@blackwork/machine': + specifier: 0.2.2 + version: 0.2.2(blackwork@0.14.0(@bassist/utils@0.17.0)(@types/react@19.2.18)(clsx@2.1.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(tailwind-merge@3.6.0)(tailwindcss@4.3.3))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@blackwork/search': specifier: ^0.1.1 version: 0.1.1 @@ -199,6 +202,9 @@ importers: react-dom: specifier: ^19.2.8 version: 19.2.8(react@19.2.8) + shiki: + specifier: ^4.4.3 + version: 4.4.3 tailwind-merge: specifier: ^3.6.0 version: 3.6.0 diff --git a/docs/src/app/globals.css b/docs/src/app/globals.css index b56dd14..e8bdfaa 100644 --- a/docs/src/app/globals.css +++ b/docs/src/app/globals.css @@ -23,6 +23,21 @@ background-color: hsl(var(--foreground) / 0.35); } +/* File examples need room for both paths and code inside the shared docs shell. */ +@media (width >= 64rem) { + [data-docs-region='docs-shell']:has([data-preset-example]) { + column-gap: 3rem; + } + + [data-docs-region='docs-shell']:has([data-preset-example]) [data-docs-region='sidebar'] { + width: 14rem; + } + + [data-docs-region='docs-shell']:has([data-preset-example]) [data-docs-region='toc'] { + display: none; + } +} + /* Keep shared header controls reachable on phones, including docs pages. */ @media (width < 40rem) { [data-docs-region='header'] > div { diff --git a/docs/src/contents/en/examples/file-changes.mdx b/docs/src/contents/en/examples/file-changes.mdx new file mode 100644 index 0000000..d9ab1ee --- /dev/null +++ b/docs/src/contents/en/examples/file-changes.mdx @@ -0,0 +1,30 @@ +--- +title: Edit files during project creation +description: Remove demo files, update the README, and change defaults while creating a project. +order: 4 +--- + +# Edit files during project creation + +After cloning a service starter, there are often demo files to remove, a README to rewrite, defaults to change, and a demo command to delete from `package.json`. When the edits are the same each time, put them in a preset. + + + +## Operation order + +1. `exclude` skips `demo.js` during copying, leaving it out of the new project. +2. `write` rewrites `README.md`, with each entry in `lines` becoming one line in the file. +3. `replace` changes `APP_LABEL=Starter` to `APP_LABEL=Team` in `.env.example`. +4. `json` edits `package.json`. Setting `scripts.demo` to `null` removes that command and keeps `scripts.start`. + +## Preview the creation plan + +Save the config as `presets/service.json`, then add `--dry-run` to see the planned operations: + +```bash +pnpm create preset init my-service --from ./presets/service.json --dry-run +``` + +This step does not download the repository, so it cannot check whether the original text in `replace` exists. After checking the plan, remove `--dry-run` to create the project and inspect the files. + +See [Preset Configs](/guide/preset-configs) for other fields. diff --git a/docs/src/contents/en/examples/monorepo.mdx b/docs/src/contents/en/examples/monorepo.mdx new file mode 100644 index 0000000..27d3666 --- /dev/null +++ b/docs/src/contents/en/examples/monorepo.mdx @@ -0,0 +1,27 @@ +--- +title: Extract a project from a monorepo +description: Copy the needed directory and handle test files and workspace dependencies during creation. +order: 3 +--- + +# Extract a project from a monorepo + +The docs starter lives in `apps/docs` and the component library in `packages/ui`. A new docs site only needs the starter, but copying that directory still leaves test files to remove and `workspace:` dependencies to fix. + +The config below handles those steps together: take files from `apps/docs`, exclude `src/__tests__`, and set the component library dependency to a specific version. + + + +## Subdirectory and file exclusions + +With `source.directory` set to `apps/docs`, that directory's contents become the new project's root files. Neither `packages/ui` nor the repository's root `pnpm-workspace.yaml` is copied. + +Paths in `exclude` start from the selected directory too. Use `src/__tests__`, without the `apps/docs` prefix. + +## Workspace dependency conversion + +Enable `packageJson.resolveWorkspace` and set `@example/ui` to `1.2.3` in `workspaceVersions`. The generated `package.json` uses that version in place of `workspace:*`, with no registry lookup for this package. + +`@example/ui` is an example name. Use a published package and version that the environment can install. This changes the dependency version; it does not copy or bundle the component source from `packages/ui`. + +Without a specified version, the CLI queries `npm view` using the current npm registry settings. See [Resolve workspace dependencies](/guide/preset-configs#resolve-workspace-dependencies) for the full rules. diff --git a/docs/src/contents/en/examples/skills.mdx b/docs/src/contents/en/examples/skills.mdx new file mode 100644 index 0000000..ffdc6fe --- /dev/null +++ b/docs/src/contents/en/examples/skills.mdx @@ -0,0 +1,31 @@ +--- +title: Reuse skills across projects +description: Maintain common skills together and select the ones each project needs. +order: 2 +--- + +# Reuse skills across projects + +Code review, documentation, and testing often use the same skills across projects. Keeping them in one shared repository saves finding and copying each folder whenever a new project needs them. + +This example keeps three skills in the `skills` directory of `team-resources`. One config includes all three. The other uses `exclude` to leave out `testing` when it is not needed. + + + +## Target directory + +Run the example command from the project root with a new or empty `.agents/skills` directory as the target. For example, `skills/code-review/SKILL.md` in the repository becomes `.agents/skills/code-review/SKILL.md` in the project. + +If the agent reads skills from another location, change the target path in the command. Several projects can use this repository without an npm package for distribution. + +## Existing skills + +**For a nonempty target, the CLI asks to replace the entire directory. It does not merge individual skills.** Generate into a new temporary directory first: + +```bash +pnpm create preset init skills-preview --from ./presets/skills.json +``` + +Check the files, then copy the skill folders needed into the project. Compare same-name files before overwriting them. Later updates can be fetched and compared the same way. Create Preset does not sync updates automatically or link skills into other agents' directories. + +Each preset reads one source repository. Skills from several repositories need collecting in one repository before this config can distribute them together. diff --git a/docs/src/contents/en/examples/variants.mdx b/docs/src/contents/en/examples/variants.mdx new file mode 100644 index 0000000..57be6da --- /dev/null +++ b/docs/src/contents/en/examples/variants.mdx @@ -0,0 +1,32 @@ +--- +title: One template, different editions +description: Maintain shared code once and configure the differences between basic and team editions. +order: 1 +--- + +# One template, different editions + +The basic edition does not need analytics; the team edition does. Everything else is the same. With a separate template for each, every shared bug needs fixing twice. + +Keep the complete source, including analytics, and add a config that removes it for the basic edition. Below, `basic.json` removes the analytics file and its references in the entry point. `team.json` keeps them. + + + +## Template differences + +Both configs take files from `starter`. The basic edition adds two steps: + +- `exclude` leaves out `src/analytics.ts`. +- `replace` removes the import and call from `src/main.ts`, so the entry point no longer refers to the deleted file. + +The team edition keeps both files. In each edition, the generated `package.json` name matches the target directory: `my-basic-app` or `my-team-app`. + +Shared fixes go into the source repository once. New projects use the selected preset when created; projects already generated need updating separately. + +## Text replacement rules + +`replace` matches exact text. After editing the entry point, check the config's `from` value too. Creation fails if that text can no longer be found. + +This works for small, explicit file differences. Any references that need removing must be listed in the config. The CLI does not analyze module references or convert TypeScript automatically. + +See [Preset Configs](/guide/preset-configs) for more operations. diff --git a/docs/src/contents/en/guide/getting-started.mdx b/docs/src/contents/en/guide/getting-started.mdx index 728513f..2c633e5 100644 --- a/docs/src/contents/en/guide/getting-started.mdx +++ b/docs/src/contents/en/guide/getting-started.mdx @@ -6,6 +6,10 @@ order: 1 # Getting Started +The [examples](/examples/variants) include configs, source files, and output for template variants, shared skills, and monorepo projects. + +If a template author already provides a creation command, run it directly. There is no need to write a JSON config first. + Run Create Preset with a package manager: diff --git a/docs/src/contents/zh/examples/file-changes.mdx b/docs/src/contents/zh/examples/file-changes.mdx new file mode 100644 index 0000000..3b38f6d --- /dev/null +++ b/docs/src/contents/zh/examples/file-changes.mdx @@ -0,0 +1,30 @@ +--- +title: 创建时调整文件和配置 +description: 删演示文件、改 README、调整默认配置,创建项目时一起完成。 +order: 4 +--- + +# 创建时调整文件和配置 + +服务模板中的演示文件、README 和默认配置通常需要在创建项目后调整。示例将这些固定操作写入 Preset,在创建时删除 `demo.js`、重写 README、替换环境变量,并移除 `package.json` 中的演示命令。 + + + +## 文件处理顺序 + +1. `exclude`:复制时跳过 `demo.js`,新项目里就不会有这个文件。 +2. `write`:重写 `README.md`,`lines` 中的每一项对应文件中的一行。 +3. `replace`:把 `.env.example` 中的 `APP_LABEL=Starter` 换成 `APP_LABEL=Team`。 +4. `json`:修改 `package.json`,把 `scripts.demo` 设为 `null` 就能删掉这条命令,`scripts.start` 会保留。 + +## 预览创建计划 + +配置保存为 `presets/service.json` 后,可以加上 `--dry-run` 查看准备执行的操作: + +```bash +pnpm create preset init my-service --from ./presets/service.json --dry-run +``` + +这一步不会下载仓库,因此也不会检查 `replace` 中的原文能否找到。确认计划后,去掉 `--dry-run` 创建项目,再检查生成的文件。 + +其他字段的用法见[Preset 配置](/zh/guide/preset-configs)。 diff --git a/docs/src/contents/zh/examples/monorepo.mdx b/docs/src/contents/zh/examples/monorepo.mdx new file mode 100644 index 0000000..2a146cc --- /dev/null +++ b/docs/src/contents/zh/examples/monorepo.mdx @@ -0,0 +1,27 @@ +--- +title: 从 monorepo 提取子项目 +description: 提取子目录,排除测试文件,并转换 workspace 依赖。 +order: 3 +--- + +# 从 monorepo 提取子项目 + +文档模板位于 `apps/docs`,依赖同一仓库中 `packages/ui` 提供的组件库。将文档模板用于独立项目时,需要排除内部测试文件,并将 `workspace:` 依赖换成已发布的版本。 + +示例配置选取 `apps/docs`,排除 `src/__tests__`,并指定组件库的依赖版本。 + + + +## 子目录与文件排除 + +`source.directory` 设为 `apps/docs` 后,该目录的内容会复制到新项目根目录。`packages/ui` 和仓库根目录的 `pnpm-workspace.yaml` 不会复制。 + +`exclude` 中的路径相对于选中的目录,因此测试目录配置为 `src/__tests__`,不包含 `apps/docs` 前缀。 + +## workspace 依赖转换 + +开启 `packageJson.resolveWorkspace`,再在 `workspaceVersions` 中指定 `@example/ui` 的版本为 `1.2.3`。生成后的 `package.json` 就会用这个版本替换 `workspace:*`,也无需为这个包查询 registry。 + +`@example/ui` 只是示例名称,实际使用时要换成已发布、且当前环境能安装的包和版本。这里处理的是依赖版本,`packages/ui` 中的组件源码不会一起复制或打包。 + +没有指定版本时,CLI 会通过 `npm view` 查询,使用 npm 当前的 registry 配置。详细规则见[处理 workspace 依赖](/zh/guide/preset-configs#处理-workspace-依赖)。 diff --git a/docs/src/contents/zh/examples/skills.mdx b/docs/src/contents/zh/examples/skills.mdx new file mode 100644 index 0000000..d4aade3 --- /dev/null +++ b/docs/src/contents/zh/examples/skills.mdx @@ -0,0 +1,31 @@ +--- +title: 在多个项目中复用 Skills +description: 集中维护常用 Skills,通过配置选取并引入项目。 +order: 2 +--- + +# 在多个项目中复用 Skills + +代码审查、文档写作和测试约定等 Skills 可以在多个项目中复用。将它们集中保存在一个仓库,通过 Preset 选取目录,可以减少逐个复制文件的操作。 + +示例仓库 `team-resources` 的 `skills` 目录包含三个 Skill。两份配置分别用于引入全部 Skills,以及通过 `exclude` 排除 `testing` 目录。 + + + +## 目标目录 + +示例命令从项目根目录运行,目标是新建或空的 `.agents/skills` 目录。例如,仓库里的 `skills/code-review/SKILL.md` 会放到 `.agents/skills/code-review/SKILL.md`。 + +目标路径需要与所用 agent 的 Skills 目录一致,可以在命令中修改。同一个仓库可以供多个项目使用,不必为了分发再发布 npm 包。 + +## 已有 Skills 的处理 + +**目标目录已有文件时,CLI 会询问是否替换整个目录,不会逐个合并 Skill。** 为保留项目中的现有内容,可以先生成到一个新的临时目录: + +```bash +pnpm create preset init skills-preview --from ./presets/skills.json +``` + +检查生成内容后,将需要的 Skill 文件夹复制到项目中;同名文件需要比较后再决定是否覆盖。源仓库更新后,也可以通过临时目录比较新旧文件。Create Preset 不会自动同步更新,也不会把 Skills 链接到其他 agent 的目录。 + +一份 Preset 只读取一个源仓库。如果常用 Skills 分散在多个仓库,需要先整理到同一个仓库,再用这份配置分发。 diff --git a/docs/src/contents/zh/examples/variants.mdx b/docs/src/contents/zh/examples/variants.mdx new file mode 100644 index 0000000..e3a279d --- /dev/null +++ b/docs/src/contents/zh/examples/variants.mdx @@ -0,0 +1,32 @@ +--- +title: 同一模板,生成不同版本 +description: 公共代码只维护一份,基础版和团队版的区别交给配置处理。 +order: 1 +--- + +# 同一模板,生成不同版本 + +基础版和团队版共用同一套代码,区别在于是否包含统计模块。分别维护模板时,公共代码的修改需要同步到两份源码。 + +示例保留一份完整源码,通过两份 Preset 生成不同版本:`basic.json` 排除统计文件并删除入口中的相关代码,`team.json` 保留统计功能。 + + + +## 模板差异配置 + +两份配置都从 `starter` 目录取文件。基础版多做两步: + +- 用 `exclude` 排除 `src/analytics.ts`。 +- 用 `replace` 删除 `src/main.ts` 中的导入和调用,避免入口继续引用已删除的文件。 + +团队版保留这两个文件。两个版本的 `package.json` 都会按目标目录更新 `name`,分别变成 `my-basic-app` 和 `my-team-app`。 + +公共代码在源仓库中维护,创建项目时应用对应配置。已经生成的项目需要自行更新。 + +## 文本替换规则 + +`replace` 按原文精确匹配。入口代码修改后,需要同步检查配置中的 `from`;找不到对应文本时,创建会报错。 + +这类配置适合少量、明确的文件差异。删除文件后,相关引用也需要通过配置移除。CLI 不会自动分析引用关系或转换 TypeScript。 + +更多操作见[Preset 配置](/zh/guide/preset-configs)。 diff --git a/docs/src/contents/zh/guide/getting-started.mdx b/docs/src/contents/zh/guide/getting-started.mdx index 6ea87b3..e40e2df 100644 --- a/docs/src/contents/zh/guide/getting-started.mdx +++ b/docs/src/contents/zh/guide/getting-started.mdx @@ -6,6 +6,10 @@ order: 1 # 快速开始 +[使用示例](/zh/examples/variants)提供模板复用、Skills 分发和 monorepo 子项目提取的配置,以及对应的源文件和生成结果。 + +如果模板作者已经提供创建命令,直接运行即可,无需自己编写 JSON。 + 使用包管理器运行 Create Preset: diff --git a/docs/src/examples/changes.test.ts b/docs/src/examples/changes.test.ts new file mode 100644 index 0000000..aa79290 --- /dev/null +++ b/docs/src/examples/changes.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from 'vitest' +import { changedLines, sourceChange } from './changes' + +describe('example change annotations', () => { + test('does not mark retained lines after a removed import', () => { + expect(changedLines("import './analytics'\nstart()\n", 'start()\n')).toEqual({ before: [0], after: [] }) + }) + + test('matches repeated lines in order when content is inserted', () => { + expect(changedLines('a\nb\na\n', 'a\nx\nb\na\n')).toEqual({ before: [], after: [1] }) + }) + + test('highlights the two sides of a replacement', () => { + expect(changedLines('APP=Starter\n', 'APP=Team\n')).toEqual({ before: [0], after: [0] }) + }) + + test('distinguishes excluded files from files outside the selected directory', () => { + expect(sourceChange('apps/docs/test.ts', 'test()\n', 'apps/docs', {})).toEqual({ kind: 'removed', lines: [0] }) + expect(sourceChange('apps/docs-other/test.ts', 'test()\n', 'apps/docs', {})).toEqual({ kind: 'outside', lines: [] }) + }) +}) diff --git a/docs/src/examples/changes.ts b/docs/src/examples/changes.ts new file mode 100644 index 0000000..7799edf --- /dev/null +++ b/docs/src/examples/changes.ts @@ -0,0 +1,50 @@ +import type { FileChange } from './code-types' + +export function codeLines(content: string) { + return content.replace(/\n$/, '').split('\n') +} + +// Match lines in order, so a removed import does not mark every later line as changed. +export function changedLines(before: string, after: string) { + const left = codeLines(before) + const right = codeLines(after) + const lengths = Array.from({ length: left.length + 1 }, () => + new Array(right.length + 1).fill(0), + ) + for (let i = left.length - 1; i >= 0; i--) { + for (let j = right.length - 1; j >= 0; j--) { + lengths[i][j] = left[i] === right[j] + ? lengths[i + 1][j + 1] + 1 + : Math.max(lengths[i + 1][j], lengths[i][j + 1]) + } + } + const removed: number[] = [] + const added: number[] = [] + let i = 0 + let j = 0 + while (i < left.length || j < right.length) { + if (i < left.length && j < right.length && left[i] === right[j]) { + i++ + j++ + } else if (j < right.length && (i === left.length || lengths[i][j + 1] > lengths[i + 1][j])) { + added.push(j++) + } else { + removed.push(i++) + } + } + return { before: removed, after: added } +} + +export function sourceChange( + path: string, + content: string, + directory: string, + output: Record, +): { kind: FileChange; lines: number[] } { + const prefix = directory ? `${directory}/` : '' + if (!path.startsWith(prefix)) return { kind: 'outside', lines: [] } + const next = output[path.slice(prefix.length)] + if (next === undefined) return { kind: 'removed', lines: codeLines(content).map((_, index) => index) } + if (next === content) return { kind: 'unchanged', lines: [] } + return { kind: 'modified', lines: changedLines(content, next).before } +} diff --git a/docs/src/examples/code-types.ts b/docs/src/examples/code-types.ts new file mode 100644 index 0000000..5c3f6c3 --- /dev/null +++ b/docs/src/examples/code-types.ts @@ -0,0 +1,34 @@ +import type { HighlightedCommand } from '@/mdx/components/command-block' + +export type FileChange = 'unchanged' | 'modified' | 'added' | 'removed' | 'outside' + +export interface CodeToken { + text: string + light?: string + dark?: string +} + +export interface ExampleFile { + path: string + lines: CodeToken[][] + change: FileChange + changedLines: number[] +} + +export interface PresetView { + id: string + label: { en: string; zh: string } + target: string + command: HighlightedCommand + directory: string + source: ExampleFile[] + config: ExampleFile[] + excerpt: ExampleFile + output: ExampleFile[] +} + +export interface ExampleView { + id: string + repository: string + presets: PresetView[] +} diff --git a/docs/src/examples/code-view.module.css b/docs/src/examples/code-view.module.css new file mode 100644 index 0000000..71a3f94 --- /dev/null +++ b/docs/src/examples/code-view.module.css @@ -0,0 +1,79 @@ +.code { + margin: 0; + max-height: 32rem; + overflow: auto; + padding-block: 1.25rem; + font-family: var(--font-mono); + font-size: 0.8125rem; + line-height: 1.8; + tab-size: 2; + background: hsl(var(--background)); +} + +.code > code { + display: block; + min-width: max-content; +} + +.compact { + font-size: 0.75rem; + max-height: 20rem; +} + +.line { + display: flex; + padding-right: 1rem; + min-height: 1.8em; +} + +.number { + width: 2.75rem; + flex-shrink: 0; + padding-right: 0.875rem; + text-align: right; + color: hsl(var(--muted-foreground) / 0.6); + user-select: none; +} + +.token { + color: var(--code-light); +} + +:global(.dark) .token { + color: var(--code-dark); +} + +.line[data-edit='source'] { + background: rgb(217 119 6 / 0.09); + box-shadow: inset 2px 0 #b7791f; +} + +.line[data-edit='output'] { + background: rgb(16 185 129 / 0.09); + box-shadow: inset 2px 0 #059669; +} + +.mark { + flex-shrink: 0; + font-size: 0.625rem; + line-height: 1.6; + color: hsl(var(--muted-foreground)); +} + +.mark[data-change='modified'], +.mark[data-change='removed'] { + color: #9a6700; +} + +.mark[data-change='added'] { + color: #047857; +} + +:global(.dark) .mark[data-change='modified'], +:global(.dark) .mark[data-change='removed'] { + color: #e9b65e; +} + +:global(.dark) .mark[data-change='added'] { + color: #6ee7b7; +} diff --git a/docs/src/examples/code-view.tsx b/docs/src/examples/code-view.tsx new file mode 100644 index 0000000..e225ef7 --- /dev/null +++ b/docs/src/examples/code-view.tsx @@ -0,0 +1,33 @@ +import type { CSSProperties } from 'react' +import type { ExampleFile, FileChange } from './code-types' +import styles from './code-view.module.css' + +export const changeLabels: Record<'zh' | 'en', Record> = { + zh: { unchanged: '保留', modified: '修改', added: '新增', removed: '排除', outside: '未选取' }, + en: { unchanged: 'Kept', modified: 'Edited', added: 'Added', removed: 'Excluded', outside: 'Not selected' }, +} + +export function ChangeMark({ change, locale }: { change: FileChange; locale: 'zh' | 'en' }) { + return {changeLabels[locale][change]} +} + +export function CodeView({ file, side = 'output', compact = false }: { + file: ExampleFile + side?: 'source' | 'config' | 'output' + compact?: boolean +}) { + return ( +
+      
+        {file.lines.map((line, index) => (
+          
+            
+            {line.map((token, tokenIndex) => (
+              {token.text}
+            ))}{line.length === 0 ? ' ' : ''}{'\n'}
+          
+        ))}
+      
+    
+ ) +} diff --git a/docs/src/examples/copy.ts b/docs/src/examples/copy.ts new file mode 100644 index 0000000..5825743 --- /dev/null +++ b/docs/src/examples/copy.ts @@ -0,0 +1,70 @@ +export const exampleCopy = { + zh: { + title: '工作场景不再繁琐', + description: '从一个常见问题开始,看看源文件、Preset 配置和生成结果。', + before: '手动维护', + after: '使用 Create Preset', + view: '查看示例', + all: '使用示例', + scenarios: [ + { + id: 'variants', + title: '模板差别不大,却要维护好几份?', + before: '基础版、团队版各复制一份源码。修一个问题,每份模板都要检查和修改。', + after: '共用一份源码,把文件和配置差异写进 Preset,创建时生成需要的版本。', + }, + { + id: 'skills', + title: '每个项目都要复制同一批 Skills?', + before: '找到常用 Skills,逐个复制到项目目录,再删掉这次用不上的。', + after: '常用 Skills 集中放在一个仓库,按配置选取,一次引入到项目的 Skills 目录。', + }, + { + id: 'monorepo', + title: '只想用 monorepo 里的一个子项目?', + before: '克隆整个仓库,取出子目录,删除无关文件,再处理 workspace: 依赖。', + after: '指定子目录、排除文件和依赖处理规则,直接生成独立项目。', + }, + { + id: 'file-changes', + title: '模板拉下来,每次都要改同样的配置?', + before: '删除示例文件、修改 README、替换配置值,每次创建项目都再做一遍。', + after: '将固定步骤写进 Preset,创建项目时一起完成。', + }, + ], + }, + en: { + title: 'Less repetition in your workflow', + description: 'Start with a familiar problem. Compare the source files, preset config, and generated output.', + before: 'Manual upkeep', + after: 'With Create Preset', + view: 'View example', + all: 'Examples', + scenarios: [ + { + id: 'variants', + title: 'Almost identical templates, maintained separately?', + before: 'Keep separate copies for basic and team editions. Check and fix the same code in every copy.', + after: 'Keep one source. Describe the file and config differences in presets and generate the edition needed.', + }, + { + id: 'skills', + title: 'Copying the same skills into every project?', + before: 'Find the usual skills, copy them one by one, then remove the ones this project does not need.', + after: 'Keep common skills in one repository. Select them with a preset and bring them into a project together.', + }, + { + id: 'monorepo', + title: 'Only need one project from a monorepo?', + before: 'Clone the repository, extract a directory, remove unrelated files, then fix workspace: dependencies.', + after: 'Choose the directory, exclusions, and dependency rules to generate a standalone project.', + }, + { + id: 'file-changes', + title: 'Making the same edits after every clone?', + before: 'Remove sample files, rewrite the README, and change config values every time a project is created.', + after: 'Describe those steps in a preset and apply them during project creation.', + }, + ], + }, +} as const diff --git a/docs/src/examples/example-explorer-client.tsx b/docs/src/examples/example-explorer-client.tsx new file mode 100644 index 0000000..7cbb92d --- /dev/null +++ b/docs/src/examples/example-explorer-client.tsx @@ -0,0 +1,120 @@ +'use client' + +import { useId, useState } from 'react' +import { + Select, SelectContent, SelectItem, SelectTrigger, SelectValue, + Tabs, TabsContent, TabsList, TabsTrigger, +} from 'blackwork' +import type { ExampleFile, ExampleView } from './code-types' +import { Callout } from '@/mdx/components/callout' +import { CommandBlock } from '@/mdx/components/command-block' +import { ChangeMark, CodeView } from './code-view' + +type Locale = 'en' | 'zh' + +const labels = { + zh: { + source: '源文件', config: 'Preset 配置', output: '生成结果', + files: '文件列表', edition: '生成版本', command: '创建命令', + noteTitle: '示例说明', + note: '下方只演示文件变化,不会运行命令。试用前,先替换示例仓库地址,再把 JSON 保存到命令指定的路径。', + }, + en: { + source: 'Source', config: 'Preset config', output: 'Output', + files: 'Files', edition: 'Edition', command: 'Create command', + noteTitle: 'Example note', + note: 'The files below show the source, config, and expected output; no commands run here. To try it locally, replace the example repository URL and save the JSON at the path in the command.', + }, +} + +function FilePanel({ files, locale, root, side }: { + files: ExampleFile[] + locale: Locale + root: string + side: 'source' | 'config' | 'output' +}) { + const [selected, setSelected] = useState(files[0].path) + const file = files.find((item) => item.path === selected) ?? files[0] + const panelId = useId() + + return ( +
+ +
+

{selected}

+
+
+
+ ) +} + +export function ExampleExplorerClient({ data, locale }: { data: ExampleView; locale: Locale }) { + const copy = labels[locale] + const [presetIndex, setPresetIndex] = useState(0) + const preset = data.presets[presetIndex] + const selectId = useId() + const panels = [ + { id: 'source', title: copy.source, files: preset.source, root: data.repository }, + { id: 'config', title: copy.config, files: preset.config, root: 'presets' }, + { id: 'output', title: copy.output, files: preset.output, root: preset.target }, + ] as const + + return ( +
+ {copy.note} + {data.presets.length > 1 && ( +
+ + +
+ )} + +
+ + {panels.map((panel) => {panel.title})} + +
+ {panels.map((panel) => ( + + + + ))} +
+

{copy.command}

+ +
+ ) +} diff --git a/docs/src/examples/example-explorer.tsx b/docs/src/examples/example-explorer.tsx new file mode 100644 index 0000000..1131879 --- /dev/null +++ b/docs/src/examples/example-explorer.tsx @@ -0,0 +1,10 @@ +import type fixtures from './fixtures.json' +import { getExampleView } from './highlight.server' +import { ExampleExplorerClient } from './example-explorer-client' + +export async function ExampleExplorer({ example, locale = 'en' }: { + example: keyof typeof fixtures + locale?: 'en' | 'zh' +}) { + return +} diff --git a/docs/src/examples/fixtures.json b/docs/src/examples/fixtures.json new file mode 100644 index 0000000..5e251ed --- /dev/null +++ b/docs/src/examples/fixtures.json @@ -0,0 +1,222 @@ +{ + "variants": { + "repo": "https://github.com/example/app-starter.git", + "sourceFiles": { + "starter/src/main.ts": "import { track } from './analytics'\n\ntrack('app-started')\nconsole.log('Hello')\n", + "starter/src/analytics.ts": "export function track(event: string) {\n console.log('[analytics]', event)\n}\n", + "starter/package.json": "{\n \"name\": \"app-starter\",\n \"version\": \"1.0.0\",\n \"private\": true\n}\n" + }, + "presets": [ + { + "id": "basic", + "label": { + "zh": "基础版", + "en": "Basic" + }, + "target": "my-basic-app", + "config": { + "$schema": "https://preset.js.org/schema/preset.schema.json", + "version": 1, + "source": { + "repo": "https://github.com/example/app-starter.git", + "directory": "starter" + }, + "exclude": [ + "src/analytics.ts" + ], + "replace": [ + { + "path": "src/main.ts", + "from": "import { track } from './analytics'\n\ntrack('app-started')\n", + "to": "" + } + ] + }, + "outputFiles": { + "src/main.ts": "console.log('Hello')\n", + "package.json": "{\n \"name\": \"my-basic-app\",\n \"version\": \"1.0.0\",\n \"private\": true\n}\n" + } + }, + { + "id": "team", + "label": { + "zh": "团队版", + "en": "Team" + }, + "target": "my-team-app", + "config": { + "$schema": "https://preset.js.org/schema/preset.schema.json", + "version": 1, + "source": { + "repo": "https://github.com/example/app-starter.git", + "directory": "starter" + } + }, + "outputFiles": { + "src/main.ts": "import { track } from './analytics'\n\ntrack('app-started')\nconsole.log('Hello')\n", + "src/analytics.ts": "export function track(event: string) {\n console.log('[analytics]', event)\n}\n", + "package.json": "{\n \"name\": \"my-team-app\",\n \"version\": \"1.0.0\",\n \"private\": true\n}\n" + } + } + ] + }, + "skills": { + "repo": "https://github.com/example/team-resources.git", + "sourceFiles": { + "skills/code-review/SKILL.md": "---\nname: code-review\ndescription: Review code changes for correctness and edge cases.\n---\n\nRead the diff. Report concrete problems with file references.\n", + "skills/writing/SKILL.md": "---\nname: writing\ndescription: Edit project documentation for clarity.\n---\n\nUse short, direct sentences. Keep commands and API names accurate.\n", + "skills/testing/SKILL.md": "---\nname: testing\ndescription: Check changed behavior with focused tests.\n---\n\nRead the project test commands. Cover the changed behavior and failure paths.\n" + }, + "presets": [ + { + "id": "skills", + "label": { + "zh": "全部 Skills", + "en": "All skills" + }, + "target": ".agents/skills", + "config": { + "$schema": "https://preset.js.org/schema/preset.schema.json", + "version": 1, + "source": { + "repo": "https://github.com/example/team-resources.git", + "directory": "skills" + } + }, + "outputFiles": { + "code-review/SKILL.md": "---\nname: code-review\ndescription: Review code changes for correctness and edge cases.\n---\n\nRead the diff. Report concrete problems with file references.\n", + "writing/SKILL.md": "---\nname: writing\ndescription: Edit project documentation for clarity.\n---\n\nUse short, direct sentences. Keep commands and API names accurate.\n", + "testing/SKILL.md": "---\nname: testing\ndescription: Check changed behavior with focused tests.\n---\n\nRead the project test commands. Cover the changed behavior and failure paths.\n" + } + }, + { + "id": "skills-without-testing", + "label": { + "zh": "不含测试 Skill", + "en": "Without testing" + }, + "target": ".agents/skills", + "config": { + "$schema": "https://preset.js.org/schema/preset.schema.json", + "version": 1, + "source": { + "repo": "https://github.com/example/team-resources.git", + "directory": "skills" + }, + "exclude": [ + "testing" + ] + }, + "outputFiles": { + "code-review/SKILL.md": "---\nname: code-review\ndescription: Review code changes for correctness and edge cases.\n---\n\nRead the diff. Report concrete problems with file references.\n", + "writing/SKILL.md": "---\nname: writing\ndescription: Edit project documentation for clarity.\n---\n\nUse short, direct sentences. Keep commands and API names accurate.\n" + } + } + ] + }, + "monorepo": { + "repo": "https://github.com/example/product.git", + "sourceFiles": { + "apps/docs/src/page.ts": "import { Button } from '@example/ui'\n\nexport const action = Button({ label: 'Get started' })\n", + "apps/docs/package.json": "{\n \"name\": \"docs-starter\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"dependencies\": {\n \"@example/ui\": \"workspace:*\"\n }\n}\n", + "apps/docs/src/__tests__/page.test.ts": "// Tests used while maintaining this starter.\n", + "packages/ui/package.json": "{\n \"name\": \"@example/ui\",\n \"version\": \"1.2.3\"\n}\n", + "pnpm-workspace.yaml": "packages:\n - apps/*\n - packages/*\n" + }, + "presets": [ + { + "id": "docs", + "label": { + "zh": "独立文档项目", + "en": "Standalone docs" + }, + "target": "my-docs", + "config": { + "$schema": "https://preset.js.org/schema/preset.schema.json", + "version": 1, + "source": { + "repo": "https://github.com/example/product.git", + "directory": "apps/docs" + }, + "exclude": [ + "src/__tests__" + ], + "packageJson": { + "resolveWorkspace": true, + "workspaceVersions": { + "@example/ui": "1.2.3" + } + } + }, + "outputFiles": { + "src/page.ts": "import { Button } from '@example/ui'\n\nexport const action = Button({ label: 'Get started' })\n", + "package.json": "{\n \"name\": \"my-docs\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"dependencies\": {\n \"@example/ui\": \"1.2.3\"\n }\n}\n" + } + } + ] + }, + "file-changes": { + "repo": "https://github.com/example/service-starter.git", + "sourceFiles": { + "index.js": "console.log('Service ready')\n", + "demo.js": "console.log('Sample data')\n", + "README.md": "# Service starter\n\nA template with sample data.\n", + ".env.example": "APP_LABEL=Starter\n", + "package.json": "{\n \"name\": \"service-starter\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n \"start\": \"node index.js\",\n \"demo\": \"node demo.js\"\n }\n}\n" + }, + "presets": [ + { + "id": "service", + "label": { + "zh": "团队服务", + "en": "Team service" + }, + "target": "my-service", + "config": { + "$schema": "https://preset.js.org/schema/preset.schema.json", + "version": 1, + "source": { + "repo": "https://github.com/example/service-starter.git", + "directory": "" + }, + "exclude": [ + "demo.js" + ], + "write": [ + { + "path": "README.md", + "lines": [ + "# Team service", + "", + "Run `npm start` to start the service." + ] + } + ], + "replace": [ + { + "path": ".env.example", + "from": "APP_LABEL=Starter", + "to": "APP_LABEL=Team" + } + ], + "json": [ + { + "path": "package.json", + "value": { + "scripts": { + "demo": null + } + } + } + ] + }, + "outputFiles": { + "index.js": "console.log('Service ready')\n", + "README.md": "# Team service\n\nRun `npm start` to start the service.\n", + ".env.example": "APP_LABEL=Team\n", + "package.json": "{\n \"name\": \"my-service\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n \"start\": \"node index.js\"\n }\n}\n" + } + } + ] + } +} diff --git a/docs/src/examples/highlight.server.ts b/docs/src/examples/highlight.server.ts new file mode 100644 index 0000000..35361a4 --- /dev/null +++ b/docs/src/examples/highlight.server.ts @@ -0,0 +1,75 @@ +import { cache } from 'react' +import { createHighlighter } from 'shiki' +import fixtures from './fixtures.json' +import { changedLines, sourceChange } from './changes' +import type { ExampleFile, ExampleView } from './code-types' +import { highlightCommand } from '@/mdx/command-highlight.server' + +let highlighter: ReturnType | undefined + +async function highlight(path: string, content: string): Promise { + highlighter ??= createHighlighter({ + themes: ['github-light', 'github-dark'], + langs: ['json', 'typescript', 'javascript', 'markdown', 'yaml', 'dotenv'], + }) + const syntax = await highlighter + const lang = path.endsWith('.json') ? 'json' + : path.endsWith('.ts') ? 'typescript' + : path.endsWith('.js') ? 'javascript' + : path.endsWith('.md') ? 'markdown' + : path.endsWith('.yaml') ? 'yaml' + : path.startsWith('.env') ? 'dotenv' : 'text' + return syntax.codeToTokensWithThemes(content.replace(/\n$/, ''), { + lang, + themes: { light: 'github-light', dark: 'github-dark' }, + }).map((line) => line.map((token) => ({ + text: token.content, + light: token.variants.light.color, + dark: token.variants.dark.color, + }))) +} + +export const getExampleView = cache(async (id: keyof typeof fixtures): Promise => { + const fixture = fixtures[id] + const sourceFiles: Record = fixture.sourceFiles + return { + id, + repository: fixture.repo.split('/').pop()!.replace('.git', ''), + presets: await Promise.all(fixture.presets.map(async (preset) => { + const outputFiles = preset.outputFiles as Record + const directory = preset.config.source.directory + const prefix = directory ? `${directory}/` : '' + const configText = JSON.stringify(preset.config, null, 2) + // The homepage labels this as an excerpt; the full config remains in Examples. + const excerpt = { + source: { directory }, + ...Object.fromEntries(Object.entries(preset.config).filter(([key]) => + !['$schema', 'version', 'source'].includes(key), + )), + } + return { + id: preset.id, + label: preset.label, + target: preset.target, + command: await highlightCommand(`pnpm create preset init ${preset.target} --from ./presets/${preset.id}.json`), + directory, + source: await Promise.all(Object.entries(sourceFiles).map(async ([path, content]) => { + const change = sourceChange(path, content, directory, outputFiles) + return { path, lines: await highlight(path, content), change: change.kind, changedLines: change.lines } + })), + config: [{ path: `${preset.id}.json`, lines: await highlight('preset.json', configText), change: 'unchanged' as const, changedLines: [] }], + excerpt: { path: `${preset.id}.json`, lines: await highlight('preset.json', JSON.stringify(excerpt, null, 2)), change: 'unchanged' as const, changedLines: [] }, + output: await Promise.all(Object.entries(outputFiles).map(async ([path, content]) => { + const original = sourceFiles[`${prefix}${path}`] + const lines = await highlight(path, content) + return { + path, + lines, + change: original === undefined ? 'added' as const : original === content ? 'unchanged' as const : 'modified' as const, + changedLines: original === undefined ? lines.map((_, index) => index) : changedLines(original, content).after, + } + })), + } + })), + } +}) diff --git a/docs/src/home/preset-home.module.css b/docs/src/home/preset-home.module.css index b03d6bf..0d00f00 100644 --- a/docs/src/home/preset-home.module.css +++ b/docs/src/home/preset-home.module.css @@ -1,26 +1,143 @@ -/* Preserve the mint banner and toolbox identity from the VitePress homepage. */ .hero { - background-color: #d1fae5; + border-bottom: 1px solid hsl(var(--border)); + background: hsl(var(--muted) / 0.18); } -:global(.dark) .hero { - background-color: #a7f3d0; +.heroInner { + display: grid; + grid-template-columns: 1.2fr 1fr; + align-items: center; + gap: 4rem; + width: 91.6667%; + max-width: 72rem; + margin-inline: auto; + padding-block: 4rem; } +.brand { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 2rem; + font-size: 0.875rem; + font-weight: 600; +} + +.brandRule { height: 1rem; width: 1px; background: hsl(var(--border)); } +.brandNote { font-size: 0.6875rem; font-weight: 400; color: hsl(var(--muted-foreground)); } + .title { - color: #047857; - background-image: linear-gradient(120deg, #34d399 30%, #047857); - background-clip: text; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; + font-size: clamp(2.25rem, 4.6vw, 3.75rem); + font-weight: 600; + letter-spacing: -0.06em; + line-height: 1.3; +} + +.title > span { color: #047857; } +:global(.dark) .title > span { color: #6ee7b7; } + +.description { + margin-top: 1.5rem; + max-width: 31rem; + font-size: 0.9375rem; + line-height: 1.95; + color: hsl(var(--muted-foreground)); + text-wrap: pretty; } -.badgeLink:focus-visible { - outline: 2px solid #065f46; - outline-offset: 4px; +.heroMeta { display: flex; align-items: center; flex-wrap: wrap; gap: 1.25rem; margin-top: 1.75rem; font-size: 0.6875rem; color: hsl(var(--muted-foreground)); } +.heroMeta a:focus-visible { outline: 2px solid currentColor; outline-offset: 4px; } +.heroMeta a:hover { color: hsl(var(--foreground)); } +.badgeLink { display: inline-flex; border-radius: 2px; } + +.diagram { + min-width: 0; + padding: 1.5rem 0; +} + +.source { + position: relative; + width: 75%; + margin-inline: auto; + border: 1px solid rgb(5 150 105 / 0.35); + border-radius: 0.625rem; + background: hsl(var(--background)); + box-shadow: 0 8px 24px -16px rgb(5 150 105 / 0.3); +} + +.diagramHeader { + display: flex; + align-items: center; + gap: 0.5rem; + border-bottom: 1px solid hsl(var(--border)); + padding: 0.875rem 1rem; + font: 0.75rem var(--font-mono); +} + +.diagramHeader > svg { color: #059669; } +.sourceTag { margin-left: auto; font-family: var(--blackwork-font-sans); font-size: 0.625rem; color: #047857; } +:global(.dark) .sourceTag { color: #6ee7b7; } +.sourceFiles { padding: 0.75rem 1rem; font: 0.75rem / 2 var(--font-mono); } +.sourceFiles p { display: flex; align-items: center; gap: 0.5rem; } +.sourceFiles svg { opacity: 0.35; } + +.branch { + position: relative; + height: 3.75rem; +} + +.branch::before { + content: ''; + position: absolute; + left: 50%; + top: 0; + height: 1.875rem; + border-left: 1px solid hsl(var(--border)); +} + +.branch > span { + position: absolute; + inset: 1.875rem 25% 0; + border: 1px solid hsl(var(--border)); + border-bottom: 0; + border-radius: 0.5rem 0.5rem 0 0; +} + +.branch > svg { + position: absolute; + left: calc(50% - 0.75rem); + top: 1.125rem; + width: 1.5rem; + height: 1.5rem; + padding: 0.25rem; + border: 1px solid hsl(var(--border)); + border-radius: 50%; + background: hsl(var(--background)); + color: hsl(var(--muted-foreground)); +} + +.outputs { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } +.edition { min-width: 0; border: 1px solid hsl(var(--border)); border-radius: 0.5rem; background: hsl(var(--background)); } +.presetLabel { padding: 0.625rem 0.875rem; border-bottom: 1px solid hsl(var(--border)); font: 0.6875rem var(--font-mono); color: hsl(var(--muted-foreground)); } +.editionBody { padding: 0.875rem; font: 0.625rem / 2 var(--font-mono); } +.editionTitle { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.375rem; font-family: var(--blackwork-font-sans); font-size: 0.75rem; font-weight: 600; } +.statusDot { width: 0.3125rem; height: 0.3125rem; border-radius: 50%; background: #059669; } +.excluded { color: hsl(var(--muted-foreground)); text-decoration: line-through; text-decoration-color: #b7791f; } +.excluded > span { float: right; color: #9a6700; text-decoration: none; } +.diagram figcaption { margin-top: 1.5rem; text-align: center; font-size: 0.6875rem; color: hsl(var(--muted-foreground)); } + +@media (width < 64rem) { + .heroInner { gap: 2rem; } + .title { font-size: 2.625rem; } + .source { width: 90%; } } -.badgeLink { - display: inline-flex; - border-radius: 2px; +@media (width < 48rem) { + .heroInner { grid-template-columns: 1fr; gap: 2rem; padding-block: 2.5rem; } + .brand { margin-bottom: 1.5rem; } + .title { font-size: clamp(1.875rem, 7.8vw, 2.625rem); } + .description { font-size: 0.875rem; } + .diagram { width: 100%; max-width: 26rem; margin-inline: auto; padding-block: 0; } + .source { width: 80%; } + .brandNote { font-size: 0.625rem; } } diff --git a/docs/src/home/preset-home.tsx b/docs/src/home/preset-home.tsx index 4fa5a9e..9fc50b7 100644 --- a/docs/src/home/preset-home.tsx +++ b/docs/src/home/preset-home.tsx @@ -9,7 +9,8 @@ import { createHomeData, DefaultDocsHeader, } from '@blackwork/docs/theme' -import { Button } from 'blackwork/rsc' +import { Button, Card, CardContent, CardHeader } from 'blackwork/rsc' +import { Boxes, Zap, Terminal, Layers, SlidersHorizontal, KeyRound } from 'lucide-react' import Link from 'next/link' import { DocsHeaderSearchAction } from '@/search/docs-search' import { PackageManagerCommands } from '@/mdx/components/package-manager-commands' @@ -17,9 +18,13 @@ import { PresetFooter } from '@/site/footer' import { DocsSiteLink } from '@/site/docs-link' import { cn } from '@/utils/class-name' import { homeCopy } from './copy' +import { exampleCopy } from '@/examples/copy' +import { HomeScenarios } from './scenarios' +import { SourceDiagram } from './source-diagram' import styles from './preset-home.module.css' const githubUrl = 'https://github.com/preset-cli/create-preset' +const featureIcons = [Boxes, Zap, Terminal, Layers, SlidersHorizontal, KeyRound] export function PresetHome({ config, @@ -31,6 +36,8 @@ export function PresetHome({ source: DocsSource }) { const copy = homeCopy[entry.locale === 'zh' ? 'zh' : 'en'] + const locale = entry.locale === 'zh' ? 'zh' : 'en' + const examplesHref = `${locale === 'zh' ? '/zh' : ''}/examples/variants` const home = createHomeData({ config, locale: entry.locale, source }) const navigation = buildHeaderNavigation({ config, @@ -88,97 +95,69 @@ export function PresetHome({ data-pagefind-body="" >