clarity-theme 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/README.md +53 -1
  2. package/README.zh-CN.md +50 -1
  3. package/nuxt.config.ts +18 -15
  4. package/package.json +13 -19
  5. package/{app → src}/app.config.ts +1 -1
  6. package/{app → src}/composables/useArticle.ts +2 -2
  7. package/{config → src/config}/app.ts +1 -1
  8. package/{config → src/config}/feeds.empty.ts +1 -1
  9. package/{config → src/config}/index.ts +1 -1
  10. package/{img → src/img}/index.ts +2 -2
  11. package/{modules → src/modules}/clarity-config/index.ts +12 -9
  12. package/src/modules/clarity-source-layout/index.ts +78 -0
  13. package/{server → src/server}/routes/subscriptions.opml.get.ts +1 -1
  14. package/{app → src}/types/article.ts +3 -3
  15. package/src/types/feed.ts +1 -0
  16. package/app/types/feed.ts +0 -1
  17. /package/{app → src}/app.vue +0 -0
  18. /package/{app → src}/assets/css/_variable.scss +0 -0
  19. /package/{app → src}/assets/css/animation.scss +0 -0
  20. /package/{app → src}/assets/css/article.scss +0 -0
  21. /package/{app → src}/assets/css/color.scss +0 -0
  22. /package/{app → src}/assets/css/font.scss +0 -0
  23. /package/{app → src}/assets/css/main.scss +0 -0
  24. /package/{app → src}/assets/css/reusable.scss +0 -0
  25. /package/{app → src}/assets/icons/solar-rewind-back-bold-duotone.svg +0 -0
  26. /package/{app → src}/assets/icons/solar-rewind-forward-bold-duotone.svg +0 -0
  27. /package/{app → src}/components/blog/BlogAside.vue +0 -0
  28. /package/{app → src}/components/blog/BlogFooter.vue +0 -0
  29. /package/{app → src}/components/blog/BlogHeader.global.vue +0 -0
  30. /package/{app → src}/components/blog/BlogPanel.vue +0 -0
  31. /package/{app → src}/components/blog/BlogSidebar.vue +0 -0
  32. /package/{app → src}/components/blog/BlogWidget.vue +0 -0
  33. /package/{app → src}/components/blog/Mask.vue +0 -0
  34. /package/{app → src}/components/blog/SkipToContent.vue +0 -0
  35. /package/{app → src}/components/blog/ThemeToggle.vue +0 -0
  36. /package/{app → src}/components/content/Alert.vue +0 -0
  37. /package/{app → src}/components/content/Badge.vue +0 -0
  38. /package/{app → src}/components/content/Blur.vue +0 -0
  39. /package/{app → src}/components/content/CardList.vue +0 -0
  40. /package/{app → src}/components/content/Chat.vue +0 -0
  41. /package/{app → src}/components/content/Copy.vue +0 -0
  42. /package/{app → src}/components/content/EmojiClock.vue +0 -0
  43. /package/{app → src}/components/content/FeedCard.vue +0 -0
  44. /package/{app → src}/components/content/FeedGroup.vue +0 -0
  45. /package/{app → src}/components/content/Folding.vue +0 -0
  46. /package/{app → src}/components/content/Key.vue +0 -0
  47. /package/{app → src}/components/content/LinkBanner.vue +0 -0
  48. /package/{app → src}/components/content/LinkCard.vue +0 -0
  49. /package/{app → src}/components/content/MdTitle.vue +0 -0
  50. /package/{app → src}/components/content/Mermaid.vue +0 -0
  51. /package/{app → src}/components/content/MusicScore.vue +0 -0
  52. /package/{app → src}/components/content/Pic.vue +0 -0
  53. /package/{app → src}/components/content/Poetry.vue +0 -0
  54. /package/{app → src}/components/content/ProseA.vue +0 -0
  55. /package/{app → src}/components/content/ProseCode.vue +0 -0
  56. /package/{app → src}/components/content/ProsePre.vue +0 -0
  57. /package/{app → src}/components/content/ProseTable.vue +0 -0
  58. /package/{app → src}/components/content/Quote.vue +0 -0
  59. /package/{app → src}/components/content/Tab.vue +0 -0
  60. /package/{app → src}/components/content/Timeline.vue +0 -0
  61. /package/{app → src}/components/content/Tip.vue +0 -0
  62. /package/{app → src}/components/content/VideoEmbed.vue +0 -0
  63. /package/{app → src}/components/partial/Button.vue +0 -0
  64. /package/{app → src}/components/partial/DlGroup.vue +0 -0
  65. /package/{app → src}/components/partial/Dropdown.vue +0 -0
  66. /package/{app → src}/components/partial/Error.vue +0 -0
  67. /package/{app → src}/components/partial/Expand.vue +0 -0
  68. /package/{app → src}/components/partial/IconNavList.vue +0 -0
  69. /package/{app → src}/components/partial/Pagination.vue +0 -0
  70. /package/{app → src}/components/partial/RadioGroup.vue +0 -0
  71. /package/{app → src}/components/partial/Secret.vue +0 -0
  72. /package/{app → src}/components/partial/Slider.vue +0 -0
  73. /package/{app → src}/components/partial/Toggle.vue +0 -0
  74. /package/{app → src}/components/popover/Lightbox.vue +0 -0
  75. /package/{app → src}/components/popover/Search.vue +0 -0
  76. /package/{app → src}/components/popover/SearchItem.vue +0 -0
  77. /package/{app → src}/components/post/Archive.vue +0 -0
  78. /package/{app → src}/components/post/Article.vue +0 -0
  79. /package/{app → src}/components/post/Comment.vue +0 -0
  80. /package/{app → src}/components/post/Excerpt.vue +0 -0
  81. /package/{app → src}/components/post/OrderToggle.vue +0 -0
  82. /package/{app → src}/components/post/PostFooter.vue +0 -0
  83. /package/{app → src}/components/post/PostHeader.vue +0 -0
  84. /package/{app → src}/components/post/PostSurround.vue +0 -0
  85. /package/{app → src}/components/post/Slide.vue +0 -0
  86. /package/{app → src}/components/util/Date.vue +0 -0
  87. /package/{app → src}/components/util/HydrateSafe.vue +0 -0
  88. /package/{app → src}/components/util/Img.vue +0 -0
  89. /package/{app → src}/components/util/Link.vue +0 -0
  90. /package/{app → src}/components/widget/BlogLog.vue +0 -0
  91. /package/{app → src}/components/widget/BlogStats.vue +0 -0
  92. /package/{app → src}/components/widget/BlogTech.vue +0 -0
  93. /package/{app → src}/components/widget/Empty.vue +0 -0
  94. /package/{app → src}/components/widget/Toc.vue +0 -0
  95. /package/{app → src}/composables/useAvoid.ts +0 -0
  96. /package/{app → src}/composables/useCopy.ts +0 -0
  97. /package/{app → src}/composables/usePagination.ts +0 -0
  98. /package/{app → src}/composables/useShiki.ts +0 -0
  99. /package/{app → src}/composables/useToc.ts +0 -0
  100. /package/{app → src}/composables/useWidgets.ts +0 -0
  101. /package/{config → src/config}/content.d.mts +0 -0
  102. /package/{config → src/config}/content.mjs +0 -0
  103. /package/{config → src/config}/content.ts +0 -0
  104. /package/{config → src/config}/define.mjs +0 -0
  105. /package/{config → src/config}/define.ts +0 -0
  106. /package/{config → src/config}/feed.ts +0 -0
  107. /package/{config → src/config}/index.d.mts +0 -0
  108. /package/{config → src/config}/index.mjs +0 -0
  109. /package/{config → src/config}/public.ts +0 -0
  110. /package/{config → src/config}/schema.d.mts +0 -0
  111. /package/{config → src/config}/schema.mjs +0 -0
  112. /package/{config → src/config}/schema.ts +0 -0
  113. /package/{config → src/config}/server.ts +0 -0
  114. /package/{app → src}/error.vue +0 -0
  115. /package/{img → src/img}/index.d.mts +0 -0
  116. /package/{img → src/img}/index.mjs +0 -0
  117. /package/{app → src}/layouts/default.vue +0 -0
  118. /package/{modules → src/modules}/clarity-config/anti-mirror-client.ts +0 -0
  119. /package/{app → src}/pages/[...slug].vue +0 -0
  120. /package/{app → src}/pages/archive.vue +0 -0
  121. /package/{app → src}/pages/index.vue +0 -0
  122. /package/{app → src}/pages/link.vue +0 -0
  123. /package/{app → src}/pages/preview.vue +0 -0
  124. /package/{app → src}/plugins/easter-egg.ts +0 -0
  125. /package/{app → src}/plugins/init.ts +0 -0
  126. /package/{app → src}/plugins/tippy.ts +0 -0
  127. /package/{public → src/public}/assets/atom.css +0 -0
  128. /package/{public → src/public}/assets/atom.xsl +0 -0
  129. /package/{public → src/public}/fonts/AlimamaFangYuanTi.woff2 +0 -0
  130. /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.d.mts +0 -0
  131. /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.mjs +0 -0
  132. /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.ts +0 -0
  133. /package/{remark-plugins → src/remark-plugins}/remark-code-component.d.mts +0 -0
  134. /package/{remark-plugins → src/remark-plugins}/remark-code-component.mjs +0 -0
  135. /package/{remark-plugins → src/remark-plugins}/remark-code-component.ts +0 -0
  136. /package/{server → src/server}/api/stats.get.ts +0 -0
  137. /package/{server → src/server}/routes/atom.xml.get.ts +0 -0
  138. /package/{server → src/server}/utils/clarity.ts +0 -0
  139. /package/{shared → src/shared}/types/content.ts +0 -0
  140. /package/{shared → src/shared}/utils/clarity.ts +0 -0
  141. /package/{shared → src/shared}/utils/icon.ts +0 -0
  142. /package/{shared → src/shared}/utils/link.ts +0 -0
  143. /package/{shared → src/shared}/utils/str.ts +0 -0
  144. /package/{shared → src/shared}/utils/time.ts +0 -0
  145. /package/{app → src}/shiki.config.ts +0 -0
  146. /package/{app → src}/stores/layout.ts +0 -0
  147. /package/{app → src}/stores/search.ts +0 -0
  148. /package/{app → src}/types/index.ts +0 -0
  149. /package/{app → src}/types/modal.ts +0 -0
  150. /package/{app → src}/types/nav.ts +0 -0
  151. /package/{app → src}/utils/anim.ts +0 -0
  152. /package/{app → src}/utils/img.ts +0 -0
package/README.md CHANGED
@@ -245,6 +245,57 @@ All documents are also available in 简体中文 as sibling `*.zh-CN.md` files.
245
245
  | [Changelog](./CHANGELOG.md) | Consumer-facing release history |
246
246
  | [Extraction history](./docs/history/2026-09-layer-extraction.md) | Historical phases and one-time differential validation |
247
247
 
248
+ ## Repository Layout
249
+
250
+ The repository root is the engineering layer; all Theme runtime source lives
251
+ under `src/`:
252
+
253
+ ```
254
+ clarity-theme/
255
+ ├─ src/ # Theme runtime source (Layer srcDir)
256
+ │ ├─ assets/ components/ composables/ layouts/ middleware/
257
+ │ ├─ pages/ plugins/ stores/ types/ utils/
258
+ │ ├─ app.config.ts app.vue error.vue shiki.config.ts
259
+ │ ├─ config/ # config API sources (npm ./config ./content ./schema)
260
+ │ ├─ img/ # img API sources (npm ./img)
261
+ │ ├─ modules/ # source-layout bootstrap + clarity-config modules
262
+ │ ├─ public/ server/ shared/ remark-plugins/
263
+ ├─ skills/ # Agent workflow skills (not part of the npm package)
264
+ ├─ docs/ # Human documentation
265
+ ├─ playground/ # Workspace-linked development consumer
266
+ ├─ scripts/ tests/ # Verification and tooling
267
+ └─ nuxt.config.ts, package.json, sync-manifest.json, …
268
+ ```
269
+
270
+ `nuxt.config.ts` stays at the package root. Its first module,
271
+ `src/modules/clarity-source-layout`, applies the `src/` directory metadata to
272
+ the Clarity layer only; static `srcDir`/`serverDir`/`dir.*` values are not
273
+ used because c12 would merge them into consumer root config. npm, Git-commit,
274
+ and local-directory installs therefore resolve the same layout without
275
+ overriding a consumer's own application directories.
276
+
277
+ The npm package, the Agent skill, and the docs are three separate concerns:
278
+
279
+ - **`clarity-theme` (npm package)** — the runtime Nuxt Layer installed with
280
+ `extends: ['clarity-theme']`. Its `files` field ships only `src/` plus the
281
+ root entry files; dev assets never enter the tarball.
282
+ - **`skills/migrate-blog-v3-to-clarity`** — the canonical Agent workflow for
283
+ migrating an existing blog-v3 project. It is versioned with the repository
284
+ but intentionally **not** bundled into the npm package.
285
+ - **`docs/`** — human-readable documentation mirrored in English and Chinese.
286
+
287
+ ### Installing the migration skill
288
+
289
+ ```bash
290
+ npx skills add iicemeta/clarity-theme --list # discover available skills
291
+ npx skills add iicemeta/clarity-theme --skill migrate-blog-v3-to-clarity
292
+ ```
293
+
294
+ For development, `npx skills add ./skills --list` discovers the local copy.
295
+ To migrate a blog-v3 site with an Agent, ask it to use the
296
+ `migrate-blog-v3-to-clarity` skill; the human walkthrough remains
297
+ [Migration](./docs/MIGRATION.md).
298
+
248
299
  ## Development
249
300
 
250
301
  ```bash
@@ -260,6 +311,7 @@ pnpm test:migration
260
311
  pnpm test:contract
261
312
  pnpm test:consumer
262
313
  pnpm test:compatibility
314
+ pnpm pack --dry-run # inspect the npm tarball without writing it
263
315
  ```
264
316
 
265
317
  CI derives Node and pnpm versions from package metadata. It runs lint/typecheck/verify/sync/migration/contract/peers on the fixed Node matrix, then playground generate, real consumer acceptance, and rendering compatibility on the primary Node version. A separate weekly workflow only detects and reports upstream drift.
@@ -281,7 +333,7 @@ The weekly workflow only detects and reports drift. It never applies, commits, o
281
333
 
282
334
  ## Release Status
283
335
 
284
- Clarity Theme is a **v0.1.1 gated npm release**. The Layer/package boundary, validated configuration, Content factory, rendering pipeline, server outputs, playground, real-consumer acceptance, compatibility matrix, three-layer CI, upstream-sync baseline, release gate, and OIDC publish workflow are implemented. See the [release checklist](./docs/RELEASE-CHECKLIST.md) for the exact remaining manual publication steps.
336
+ Clarity Theme is a **v0.1.2 gated npm release**. The Layer/package boundary, validated configuration, Content factory, rendering pipeline, server outputs, playground, real-consumer acceptance, compatibility matrix, three-layer CI, upstream-sync baseline, release gate, and OIDC publish workflow are implemented. See the [release checklist](./docs/RELEASE-CHECKLIST.md) for the exact remaining manual publication steps.
285
337
 
286
338
  The v0.1.0 correctness gates from [Roadmap](./docs/ROADMAP.md) are resolved: server/client configuration is split, feature-off routes return 404 at runtime, anti-mirror navigation is verified in a real browser, the no-op random-permalink field is removed, and multi-pattern stats are covered by a regression test. Remaining deferred items are recorded in the roadmap and release notes.
287
339
 
package/README.zh-CN.md CHANGED
@@ -266,6 +266,54 @@ UI 分组、同路径组件覆盖、CSS 覆盖、服务端/路由自定义与 Sh
266
266
  | [更新日志](./CHANGELOG.md) | 面向使用者的发布历史 |
267
267
  | [抽取历史](./docs/history/2026-09-layer-extraction.zh-CN.md) | 历史阶段与一次性差分验证 |
268
268
 
269
+ ## 仓库结构
270
+
271
+ 仓库根目录是工程管理层;Theme 的全部运行时源码统一放在 `src/`:
272
+
273
+ ```
274
+ clarity-theme/
275
+ ├─ src/ # Theme 运行时源码(Layer srcDir)
276
+ │ ├─ assets/ components/ composables/ layouts/ middleware/
277
+ │ ├─ pages/ plugins/ stores/ types/ utils/
278
+ │ ├─ app.config.ts app.vue error.vue shiki.config.ts
279
+ │ ├─ config/ # config API 源码(npm ./config ./content ./schema)
280
+ │ ├─ img/ # img API 源码(npm ./img)
281
+ │ ├─ modules/ # source-layout 引导模块 + clarity-config 模块
282
+ │ ├─ public/ server/ shared/ remark-plugins/
283
+ ├─ skills/ # Agent 工作流 Skill(不进入 npm 包)
284
+ ├─ docs/ # 人工文档
285
+ ├─ playground/ # workspace 链接的开发用消费项目
286
+ ├─ scripts/ tests/ # 验证与工具脚本
287
+ └─ nuxt.config.ts、package.json、sync-manifest.json、…
288
+ ```
289
+
290
+ `nuxt.config.ts` 保留在包根目录。它最先加载的
291
+ `src/modules/clarity-source-layout` 只为 Clarity 自身 layer 应用 `src/`
292
+ 目录元数据;不静态声明 `srcDir`/`serverDir`/`dir.*`,是因为 c12 会把它们
293
+ 合并进未显式覆盖这些键的 consumer root 配置。因此 npm 安装、Git commit
294
+ 安装与本地目录安装解析到同一份布局,同时不会覆盖 consumer 自己的
295
+ 应用目录。
296
+
297
+ npm 包、Agent Skill 与文档三者职责分离:
298
+
299
+ - **`clarity-theme`(npm 包)** — 运行时 Nuxt Layer,通过
300
+ `extends: ['clarity-theme']` 使用。`files` 只发布 `src/` 与根入口文件,
301
+ 开发资产不会进入 tarball。
302
+ - **`skills/migrate-blog-v3-to-clarity`** — 迁移既有 blog-v3 项目的
303
+ Agent 工作流 Skill。它与仓库一起版本化,但**刻意不**打进 npm 包。
304
+ - **`docs/`** — 中英双语的人工文档。
305
+
306
+ ### 安装迁移 Skill
307
+
308
+ ```bash
309
+ npx skills add iicemeta/clarity-theme --list # 发现可用 Skill
310
+ npx skills add iicemeta/clarity-theme --skill migrate-blog-v3-to-clarity
311
+ ```
312
+
313
+ 开发环境可用 `npx skills add ./skills --list` 发现本地副本。让 Agent 迁移
314
+ blog-v3 站点时,请让它使用 `migrate-blog-v3-to-clarity` Skill;人工流程
315
+ 见[迁移指南](./docs/MIGRATION.zh-CN.md)。
316
+
269
317
  ## 开发
270
318
 
271
319
  ```bash
@@ -281,6 +329,7 @@ pnpm test:migration
281
329
  pnpm test:contract
282
330
  pnpm test:consumer
283
331
  pnpm test:compatibility
332
+ pnpm pack --dry-run # 不落盘检查 npm tarball
284
333
  ```
285
334
 
286
335
  CI 从包元数据推导 Node 与 pnpm 版本。它先在固定的 Node 矩阵上运行 lint/typecheck/verify/sync/migration/contract/peers,再在主 Node 版本上运行 playground 生成、真实消费者验收与渲染兼容性。另有一个每周工作流只检测并报告上游漂移。
@@ -302,7 +351,7 @@ pnpm sync:verify # 重新运行主题纯度与基线检查
302
351
 
303
352
  ## 发布状态
304
353
 
305
- Clarity Theme 是一个 **v0.1.1 门禁 npm 发布版本**。Layer/包边界、已校验配置、Content 工厂、渲染管线、服务端输出、playground、真实消费者验收、兼容性矩阵、三层 CI、上游同步基线、发布门禁与 OIDC 发布工作流均已实现。剩余的人工发布步骤见[发布检查清单](./docs/RELEASE-CHECKLIST.zh-CN.md)。
354
+ Clarity Theme 是一个 **v0.1.2 门禁 npm 发布版本**。Layer/包边界、已校验配置、Content 工厂、渲染管线、服务端输出、playground、真实消费者验收、兼容性矩阵、三层 CI、上游同步基线、发布门禁与 OIDC 发布工作流均已实现。剩余的人工发布步骤见[发布检查清单](./docs/RELEASE-CHECKLIST.zh-CN.md)。
306
355
 
307
356
  [路线图](./docs/ROADMAP.zh-CN.md)中的 v0.1.0 正确性门禁已解决:服务端/客户端配置已拆分、功能关闭路由在运行时返回 404、反镜像导航已在真实浏览器中验证、no-op 随机固定链接字段已移除,多模式统计已增加回归测试。其余推迟事项记录在路线图与发布说明中。
308
357
 
package/nuxt.config.ts CHANGED
@@ -12,13 +12,13 @@ const toThemePath = (path: string) => join(themeDir, path).replaceAll('\\', '/')
12
12
  function pluginPath(path: string) {
13
13
  // 插件必须是 .mjs:@nuxt/content 以 Node 原生方式加载,
14
14
  // 不允许 node_modules 内的 TS 文件(TS 剥离限制)
15
- return pathToFileURL(join(themeDir, 'remark-plugins', `${path}.mjs`)).href
15
+ return pathToFileURL(join(themeDir, 'src', 'remark-plugins', `${path}.mjs`)).href
16
16
  }
17
17
 
18
18
  /**
19
19
  * Clarity Theme Layer 配置
20
20
  *
21
- * 站点数据(SEO、favicon、scripts 等)由 modules/clarity-config
21
+ * 站点数据(SEO、favicon、scripts 等)由 src/modules/clarity-config
22
22
  * 读取消费项目的 clarity.config.ts 后统一注入,此处仅包含 Theme 自身能力。
23
23
  */
24
24
  export default defineNuxtConfig({
@@ -50,18 +50,18 @@ export default defineNuxtConfig({
50
50
  compatibilityDate: '2024-08-03',
51
51
 
52
52
  components: [
53
- { path: toThemePath('app/components/partial'), prefix: 'Z' },
54
- toThemePath('app/components'),
53
+ { path: toThemePath('src/components/partial'), prefix: 'Z' },
54
+ toThemePath('src/components'),
55
55
  ],
56
56
 
57
57
  // @keep-sorted
58
58
  css: [
59
- toThemePath('app/assets/css/animation.scss'),
60
- toThemePath('app/assets/css/article.scss'),
61
- toThemePath('app/assets/css/color.scss'),
62
- toThemePath('app/assets/css/font.scss'),
63
- toThemePath('app/assets/css/main.scss'),
64
- toThemePath('app/assets/css/reusable.scss'),
59
+ toThemePath('src/assets/css/animation.scss'),
60
+ toThemePath('src/assets/css/article.scss'),
61
+ toThemePath('src/assets/css/color.scss'),
62
+ toThemePath('src/assets/css/font.scss'),
63
+ toThemePath('src/assets/css/main.scss'),
64
+ toThemePath('src/assets/css/reusable.scss'),
65
65
  ],
66
66
 
67
67
  // @keep-sorted
@@ -72,6 +72,9 @@ export default defineNuxtConfig({
72
72
 
73
73
  // @keep-sorted
74
74
  modules: [
75
+ // 必须最先运行:把 src/ 仅应用到 Clarity 自身 layer,避免静态
76
+ // srcDir / serverDir / dir.* 经 c12 合并泄漏到 consumer root 配置。
77
+ toThemePath('src/modules/clarity-source-layout'),
75
78
  '@bikariya/image-viewer',
76
79
  '@bikariya/modals',
77
80
  '@bikariya/shiki',
@@ -84,7 +87,7 @@ export default defineNuxtConfig({
84
87
  '@vueuse/nuxt',
85
88
  // Layer 配置中的相对 module 路径会以消费项目为基准解析,必须使用绝对路径;
86
89
  // 且 clarity-config 必须先于 nuxt-llms 运行:后者在 setup 时读取注入的 llms 站点配置
87
- toThemePath('modules/clarity-config'),
90
+ toThemePath('src/modules/clarity-config'),
88
91
  'nuxt-llms',
89
92
  ],
90
93
 
@@ -112,8 +115,8 @@ export default defineNuxtConfig({
112
115
  nodeTsConfig: {
113
116
  // @keep-sorted
114
117
  include: [
115
- '../config/**/*.ts',
116
- '../remark-plugins/**/*.ts',
118
+ '../src/config/**/*.ts',
119
+ '../src/remark-plugins/**/*.ts',
117
120
  ],
118
121
  },
119
122
  },
@@ -122,7 +125,7 @@ export default defineNuxtConfig({
122
125
  css: {
123
126
  preprocessorOptions: {
124
127
  scss: {
125
- additionalData: `@use "${toThemePath('app/assets/css/_variable.scss')}" as *;`,
128
+ additionalData: `@use "${toThemePath('src/assets/css/_variable.scss')}" as *;`,
126
129
  },
127
130
  },
128
131
  },
@@ -189,7 +192,7 @@ ${homepage}
189
192
 
190
193
  icon: {
191
194
  customCollections: [
192
- { prefix: 'clarity', dir: toThemePath('app/assets/icons') },
195
+ { prefix: 'clarity', dir: toThemePath('src/assets/icons') },
193
196
  ],
194
197
  clientBundle: {
195
198
  scan: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clarity-theme",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "description": "Clarity Theme — 从 blog-v3 提取的可复用 Nuxt 4 Layer 博客主题",
6
6
  "license": "MIT",
7
7
  "publishConfig": {
@@ -18,36 +18,29 @@
18
18
  "exports": {
19
19
  ".": "./nuxt.config.ts",
20
20
  "./config": {
21
- "types": "./config/index.d.mts",
22
- "default": "./config/index.mjs"
21
+ "types": "./src/config/index.d.mts",
22
+ "default": "./src/config/index.mjs"
23
23
  },
24
24
  "./content": {
25
- "types": "./config/content.d.mts",
26
- "default": "./config/content.mjs"
25
+ "types": "./src/config/content.d.mts",
26
+ "default": "./src/config/content.mjs"
27
27
  },
28
28
  "./img": {
29
- "types": "./img/index.d.mts",
30
- "default": "./img/index.mjs"
29
+ "types": "./src/img/index.d.mts",
30
+ "default": "./src/img/index.mjs"
31
31
  },
32
32
  "./schema": {
33
- "types": "./config/schema.d.mts",
34
- "default": "./config/schema.mjs"
33
+ "types": "./src/config/schema.d.mts",
34
+ "default": "./src/config/schema.mjs"
35
35
  }
36
36
  },
37
37
  "main": "./nuxt.config.ts",
38
38
  "files": [
39
39
  "LICENSE",
40
40
  "README.md",
41
- "app",
42
- "config",
43
- "img",
44
- "modules",
45
41
  "nuxt.config.ts",
46
42
  "package.json",
47
- "public",
48
- "remark-plugins",
49
- "server",
50
- "shared"
43
+ "src"
51
44
  ],
52
45
  "engines": {
53
46
  "node": "^22.19 || ^24.11 || >=26"
@@ -112,6 +105,7 @@
112
105
  "@nuxt/hints": "^1.1.4",
113
106
  "@stylistic/stylelint-plugin": "^5.2.1",
114
107
  "@types/hast": "^3.0.5",
108
+ "@types/mdast": "^4.0.4",
115
109
  "@zinkawaii/stylelint-config": "^0.5.2",
116
110
  "cspell": "^10.2.2",
117
111
  "eslint": "^10.10.0",
@@ -132,8 +126,8 @@
132
126
  "dev": "pnpm --dir playground dev",
133
127
  "build": "pnpm --dir playground build",
134
128
  "generate": "pnpm --dir playground generate",
135
- "lint": "eslint && stylelint \"app/**/*.{vue,scss}\" \"playground/app/**/*.vue\"",
136
- "lint:fix": "eslint --fix && stylelint \"app/**/*.{vue,scss}\" \"playground/app/**/*.vue\" --fix",
129
+ "lint": "eslint && stylelint \"src/**/*.{vue,scss}\" \"playground/app/**/*.vue\"",
130
+ "lint:fix": "eslint --fix && stylelint \"src/**/*.{vue,scss}\" \"playground/app/**/*.vue\" --fix",
137
131
  "typecheck": "pnpm --dir playground typecheck",
138
132
  "verify": "node scripts/verify-theme.mjs",
139
133
  "test:sync": "node --test tests/sync-upstream.test.mjs",
@@ -2,7 +2,7 @@
2
2
  * Clarity Theme UI 默认配置。
3
3
  *
4
4
  * 站点数据(标题、作者、文章分类等)来自根目录 clarity.config.ts,
5
- * 由 modules/clarity-config 注入到 appConfig.clarity 中。
5
+ * 由 src/modules/clarity-config 注入到 appConfig.clarity 中。
6
6
  *
7
7
  * 消费项目可在自己的 app/app.config.ts 中按需覆盖任意键。
8
8
  */
@@ -1,5 +1,5 @@
1
- import type { MetaSlotsTree } from '../../remark-plugins/rehype-meta-slots'
2
- import type { ClarityContentRow } from '../../shared/types/content'
1
+ import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
2
+ import type { ClarityContentRow } from '../shared/types/content'
3
3
  import type { ArticleOrderType, ArticleProps } from '../types/article'
4
4
  import { orderBy } from 'es-toolkit/array'
5
5
 
@@ -1,4 +1,4 @@
1
- import type { NavItem } from '../app/types/nav'
1
+ import type { NavItem } from '../types/nav'
2
2
  import type { ClarityPublicConfig } from './public'
3
3
 
4
4
  export interface NavGroup {
@@ -1,4 +1,4 @@
1
- import type { FeedGroup } from '../app/types/feed'
1
+ import type { FeedGroup } from '../types/feed'
2
2
 
3
3
  /** 消费项目未提供 feeds.ts 时的空数据兜底 */
4
4
  const emptyFeeds: FeedGroup[] = []
@@ -1,4 +1,4 @@
1
- export type { Nav, NavItem } from '../app/types/nav'
1
+ export type { Nav, NavItem } from '../types/nav'
2
2
  export type { ClarityAppConfig, ClarityUiConfig, ClarityUiConfigInput, NavGroup } from './app'
3
3
  export { defineClarityConfig } from './define'
4
4
  export type { Arch, FeedEntry, FeedGroup } from './feed'
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * `clarity-theme/img` 类型真源。
3
3
  *
4
- * 类型实现复用 `app/utils/img.ts`(Layer 内部与包导出共享同一份签名),
4
+ * 类型实现复用 `src/utils/img.ts`(Layer 内部与包导出共享同一份签名),
5
5
  * 运行时实现在同目录 index.mjs:Node 原生 TS 剥离不允许
6
6
  * node_modules 内的 TS 文件,因此包入口运行时必须是 .mjs。
7
7
  */
8
- export * from '../app/utils/img'
8
+ export * from '../utils/img'
@@ -21,7 +21,10 @@ import { toServerClarityConfig } from '../../config/server'
21
21
  import handleMirror from './anti-mirror-client'
22
22
 
23
23
  const moduleDir = dirname(fileURLToPath(import.meta.url))
24
- const themeDir = resolve(moduleDir, '../..')
24
+ /** Theme 运行时源码根(src/):模块位于 src/modules/clarity-config */
25
+ const themeSrcDir = resolve(moduleDir, '../..')
26
+ /** Theme npm 包根(nuxt.config.ts / package.json 所在层) */
27
+ const themePkgDir = resolve(moduleDir, '../../..')
25
28
 
26
29
  /** app/app.config.ts 的 clarity 键只允许 UI 覆盖(站点级字段属于 clarity.config.ts) */
27
30
  const uiConfigKeys = new Set(['component', 'footer', 'header', 'link', 'nav', 'pagination', 'themes'])
@@ -42,7 +45,7 @@ export default defineNuxtModule<ModuleOptions>({
42
45
  const rootDir = nuxt.options.rootDir
43
46
 
44
47
  const configPath = resolve(rootDir, options.configFile ?? findConfigFile(rootDir))
45
- const feedsPath = findFeedsFile(rootDir, themeDir)
48
+ const feedsPath = findFeedsFile(rootDir, themeSrcDir)
46
49
 
47
50
  // ---- 依赖注入层:Theme 内部不感知消费项目的文件布局 ----
48
51
  Object.assign(nuxt.options.alias, {
@@ -51,14 +54,14 @@ export default defineNuxtModule<ModuleOptions>({
51
54
  })
52
55
 
53
56
  // @pinia/nuxt 不会自动扫描 Layer 的 stores 目录,需显式注册
54
- addImportsDir(resolve(themeDir, 'app/stores'))
57
+ addImportsDir(resolve(themeSrcDir, 'stores'))
55
58
 
56
59
  // ---- AppConfig 类型化:消费者 defineAppConfig({ clarity: ... }) 获得完整类型提示 ----
57
60
  // 注意:类型文件生成于 buildDir/types/ 下,相对路径必须以其为基准计算,
58
61
  // 否则导入解析失败会因 skipLibCheck 静默退化为 any(审计发现 #9)。
59
62
  const appConfigTypeDir = resolve(nuxt.options.buildDir, 'types')
60
- const appConfigTypePath = relative(appConfigTypeDir, resolve(themeDir, 'config/app.ts')).replaceAll('\\', '/')
61
- const globalsTypePath = relative(appConfigTypeDir, resolve(themeDir, 'app/types/index.ts')).replaceAll('\\', '/')
63
+ const appConfigTypePath = relative(appConfigTypeDir, resolve(themeSrcDir, 'config/app.ts')).replaceAll('\\', '/')
64
+ const globalsTypePath = relative(appConfigTypeDir, resolve(themeSrcDir, 'types/index.ts')).replaceAll('\\', '/')
62
65
  addTypeTemplate({
63
66
  filename: 'types/clarity-app-config.d.ts',
64
67
  getContents: () => [
@@ -86,7 +89,7 @@ export default defineNuxtModule<ModuleOptions>({
86
89
  // 需置于 '~' 之前以保证 Vite 别名前缀优先匹配
87
90
  if (!existsSync(resolve(nuxt.options.srcDir, 'shiki.config.ts'))) {
88
91
  nuxt.options.alias = {
89
- '~/shiki.config': resolve(themeDir, 'app/shiki.config.ts'),
92
+ '~/shiki.config': resolve(themeSrcDir, 'shiki.config.ts'),
90
93
  ...nuxt.options.alias,
91
94
  }
92
95
  }
@@ -176,7 +179,7 @@ export default defineNuxtModule<ModuleOptions>({
176
179
 
177
180
  // ---- 构建信息(供 BlogTech 等 Widget 使用) ----
178
181
  const consumerPkg = await loadJson(resolve(rootDir, 'package.json'), jiti)
179
- const themePkg = await loadJson(resolve(themeDir, 'package.json'), jiti)
182
+ const themePkg = await loadJson(resolve(themePkgDir, 'package.json'), jiti)
180
183
  nuxt.options.runtimeConfig.public.clarity = {
181
184
  theme: 'Clarity',
182
185
  themeVersion: String(themePkg?.version ?? ''),
@@ -308,13 +311,13 @@ function findConfigFile(rootDir: string) {
308
311
  return 'clarity.config.ts'
309
312
  }
310
313
 
311
- function findFeedsFile(rootDir: string, themeDir: string) {
314
+ function findFeedsFile(rootDir: string, themeSrcDir: string) {
312
315
  for (const name of ['feeds.ts', 'feeds.mjs', 'feeds.js']) {
313
316
  if (existsSync(resolve(rootDir, name))) {
314
317
  return { found: true, resolved: resolve(rootDir, name) }
315
318
  }
316
319
  }
317
- return { found: false, resolved: resolve(themeDir, 'config/feeds.empty.ts') }
320
+ return { found: false, resolved: resolve(themeSrcDir, 'config/feeds.empty.ts') }
318
321
  }
319
322
 
320
323
  async function loadJson(path: string, jiti: ReturnType<typeof createJiti>) {
@@ -0,0 +1,78 @@
1
+ import type { Nuxt } from '@nuxt/schema'
2
+ import { dirname, relative, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { defineNuxtModule, getLayerDirectories } from 'nuxt/kit'
5
+
6
+ const moduleDir = dirname(fileURLToPath(import.meta.url))
7
+ const themeSrcDir = resolve(moduleDir, '../..')
8
+ const themePkgDir = resolve(moduleDir, '../../..')
9
+
10
+ /**
11
+ * Clarity keeps all runtime source under `src/`. These values must only apply
12
+ * to the theme layer; declaring them statically in `nuxt.config.ts` would let
13
+ * c12 merge them into consumers which do not explicitly override them.
14
+ */
15
+ export default defineNuxtModule({
16
+ meta: {
17
+ name: 'clarity-source-layout',
18
+ },
19
+
20
+ setup(_options, nuxt) {
21
+ const layer = findThemeLayer(nuxt)
22
+ if (!layer) {
23
+ return
24
+ }
25
+
26
+ const sourceDir = withTrailingSlash(themeSrcDir)
27
+ const directories = {
28
+ layouts: withTrailingSlash(resolve(themeSrcDir, 'layouts')),
29
+ middleware: withTrailingSlash(resolve(themeSrcDir, 'middleware')),
30
+ modules: withTrailingSlash(resolve(themeSrcDir, 'modules')),
31
+ pages: withTrailingSlash(resolve(themeSrcDir, 'pages')),
32
+ plugins: withTrailingSlash(resolve(themeSrcDir, 'plugins')),
33
+ public: withTrailingSlash(resolve(themeSrcDir, 'public')),
34
+ server: withTrailingSlash(resolve(themeSrcDir, 'server')),
35
+ shared: withTrailingSlash(resolve(themeSrcDir, 'shared')),
36
+ }
37
+
38
+ layer.config.srcDir = sourceDir
39
+ layer.config.serverDir = directories.server
40
+ layer.config.dir = {
41
+ ...layer.config.dir,
42
+ app: sourceDir,
43
+ modules: directories.modules,
44
+ public: directories.public,
45
+ shared: directories.shared,
46
+ }
47
+
48
+ // `getLayerDirectories()` caches a plain metadata object per layer before
49
+ // modules are installed. Nuxt exposes no invalidation API, so update the
50
+ // cached paths in place as well as `layer.config`.
51
+ const cached = getLayerDirectories(nuxt).find(directory => isSamePath(directory.root, themePkgDir))
52
+ if (cached) {
53
+ Object.assign(cached, {
54
+ app: sourceDir,
55
+ appLayouts: directories.layouts,
56
+ appMiddleware: directories.middleware,
57
+ appPages: directories.pages,
58
+ appPlugins: directories.plugins,
59
+ modules: directories.modules,
60
+ public: directories.public,
61
+ server: directories.server,
62
+ shared: directories.shared,
63
+ })
64
+ }
65
+ },
66
+ })
67
+
68
+ function findThemeLayer(nuxt: Nuxt) {
69
+ return nuxt.options._layers.find(layer => isSamePath(layer.config.rootDir ?? layer.cwd, themePkgDir))
70
+ }
71
+
72
+ function isSamePath(left: string, right: string) {
73
+ return left === right || relative(resolve(left), resolve(right)) === ''
74
+ }
75
+
76
+ function withTrailingSlash(path: string) {
77
+ return `${path.replace(/[\\/]$/, '').replaceAll('\\', '/')}/`
78
+ }
@@ -1,4 +1,4 @@
1
- import type { FeedEntry, FeedGroup } from '../../app/types/feed'
1
+ import type { FeedEntry, FeedGroup } from '../../types/feed'
2
2
  import XmlBuilder from 'fast-xml-builder'
3
3
  import { toZonedTemporal } from '../../shared/utils/time'
4
4
 
@@ -1,6 +1,6 @@
1
- import type { ClarityAppConfig } from '../../config/app'
2
- import type { ArticleSchema } from '../../config/content'
3
- import type { MetaSlotsTree } from '../../remark-plugins/rehype-meta-slots'
1
+ import type { ClarityAppConfig } from '../config/app'
2
+ import type { ArticleSchema } from '../config/content'
3
+ import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
4
4
 
5
5
  export type ArticleOrderType = keyof ClarityAppConfig['article']['order']
6
6
 
@@ -0,0 +1 @@
1
+ export type { FeedEntry, FeedGroup } from '../config/feed'
package/app/types/feed.ts DELETED
@@ -1 +0,0 @@
1
- export type { FeedEntry, FeedGroup } from '../../config/feed'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes