@mnaoumov/create-obsidian-plugin 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +341 -2
  3. package/dist/main.js +4184 -0
  4. package/package.json +83 -4
  5. package/src/main.ts +650 -0
  6. package/templates/default/.depcheckrc.json.ejs +19 -0
  7. package/templates/default/.editorconfig.ejs +11 -0
  8. package/templates/default/.env.ejs +1 -0
  9. package/templates/default/.env@vault_has-vault-false.ejs +2 -0
  10. package/templates/default/.env@vault_has-vault-true.ejs +1 -0
  11. package/templates/default/.gitattributes.ejs +7 -0
  12. package/templates/default/.github/FUNDING.yml.ejs +3 -0
  13. package/templates/default/.github/ISSUE_TEMPLATE/bug_report.yml.ejs +71 -0
  14. package/templates/default/.github/ISSUE_TEMPLATE/config.yml.ejs +1 -0
  15. package/templates/default/.github/ISSUE_TEMPLATE/feature_request.yml.ejs +61 -0
  16. package/templates/default/.github/workflows/attest-release-assets.yml.ejs +24 -0
  17. package/templates/default/.github/workflows/ci.yml.ejs +20 -0
  18. package/templates/default/.github/workflows/ci.yml@steps_biome-linter.ejs +1 -0
  19. package/templates/default/.github/workflows/ci.yml@steps_cspell.ejs +1 -0
  20. package/templates/default/.github/workflows/ci.yml@steps_dprint.ejs +1 -0
  21. package/templates/default/.github/workflows/ci.yml@steps_eslint.ejs +1 -0
  22. package/templates/default/.github/workflows/ci.yml@steps_jest.ejs +1 -0
  23. package/templates/default/.github/workflows/ci.yml@steps_markdownlint.ejs +1 -0
  24. package/templates/default/.github/workflows/ci.yml@steps_prettier.ejs +1 -0
  25. package/templates/default/.github/workflows/ci.yml@steps_vitest.ejs +1 -0
  26. package/templates/default/.github/workflows/release.yml.ejs +27 -0
  27. package/templates/default/.github/workflows/update-license-year.yml.ejs +38 -0
  28. package/templates/default/.gitignore_common.ejs +27 -0
  29. package/templates/default/.gitignore_scss.ejs +1 -0
  30. package/templates/default/.gitignore_tailwind.ejs +3 -0
  31. package/templates/default/.husky/commit-msg.ejs +1 -0
  32. package/templates/default/.husky/pre-commit.ejs +1 -0
  33. package/templates/default/.markdownlint-cli2.mjs.ejs +4 -0
  34. package/templates/default/.nano-staged.mjs.ejs +5 -0
  35. package/templates/default/.npmrc.ejs +1 -0
  36. package/templates/default/.parcelrc.ejs +9 -0
  37. package/templates/default/.parcelrc@transformers_svelte.ejs +1 -0
  38. package/templates/default/.prettierignore.ejs +10 -0
  39. package/templates/default/.typesafe-i18n.json.ejs +5 -0
  40. package/templates/default/CONTRIBUTING.md.ejs +49 -0
  41. package/templates/default/CONTRIBUTING.md@demo-vault_dev-utils.ejs +15 -0
  42. package/templates/default/CONTRIBUTING.md@scripts_biome-formatter.ejs +1 -0
  43. package/templates/default/CONTRIBUTING.md@scripts_biome-linter.ejs +1 -0
  44. package/templates/default/CONTRIBUTING.md@scripts_conventional-commits.ejs +1 -0
  45. package/templates/default/CONTRIBUTING.md@scripts_cspell.ejs +1 -0
  46. package/templates/default/CONTRIBUTING.md@scripts_dev-utils.ejs +1 -0
  47. package/templates/default/CONTRIBUTING.md@scripts_dprint.ejs +1 -0
  48. package/templates/default/CONTRIBUTING.md@scripts_eslint.ejs +1 -0
  49. package/templates/default/CONTRIBUTING.md@scripts_jest.ejs +1 -0
  50. package/templates/default/CONTRIBUTING.md@scripts_markdownlint.ejs +1 -0
  51. package/templates/default/CONTRIBUTING.md@scripts_prettier.ejs +1 -0
  52. package/templates/default/CONTRIBUTING.md@scripts_vitest.ejs +1 -0
  53. package/templates/default/LICENSE.ejs +21 -0
  54. package/templates/default/README.md.ejs +28 -0
  55. package/templates/default/README.md@demo-vault_dev-utils.ejs +12 -0
  56. package/templates/default/README.md@preset_dev-utils.ejs +12 -0
  57. package/templates/default/README.md@support_has-funding.ejs +8 -0
  58. package/templates/default/babel.config.json.ejs +8 -0
  59. package/templates/default/babel.config.mjs.ejs +5 -0
  60. package/templates/default/biome.jsonc.ejs +47 -0
  61. package/templates/default/commitlint.config.ts.ejs +1 -0
  62. package/templates/default/cspell.json.ejs +67 -0
  63. package/templates/default/demo-vault/.gitignore.ejs +15 -0
  64. package/templates/default/demo-vault/.obsidian/app.json.ejs +4 -0
  65. package/templates/default/demo-vault/.obsidian/community-plugins.json.ejs +4 -0
  66. package/templates/default/demo-vault/00 Start.md.ejs +32 -0
  67. package/templates/default/demo-vault/00 Start.md@features_wasm.ejs +1 -0
  68. package/templates/default/demo-vault/01 Sample commands.md.ejs +38 -0
  69. package/templates/default/demo-vault/01 Sample commands.md@commands_wasm.ejs +4 -0
  70. package/templates/default/demo-vault/02 Settings.md.ejs +40 -0
  71. package/templates/default/demo-vault/03 WebAssembly.md.ejs +34 -0
  72. package/templates/default/demo-vault/Materials/01 Sample commands/Scratch note.md.ejs +5 -0
  73. package/templates/default/demo-vault/README.md.ejs +10 -0
  74. package/templates/default/demo-vault/_assets/CodeScriptToolkit/demoSetup.ts.ejs +88 -0
  75. package/templates/default/demo-vault/_assets/CodeScriptToolkit/demoSetup.ts@commands_wasm.ejs +10 -0
  76. package/templates/default/demo-vault/_assets/CodeScriptToolkit/startup.ts.ejs +17 -0
  77. package/templates/default/dprint.json_dev-utils.ejs +10 -0
  78. package/templates/default/dprint.json_standalone.ejs +26 -0
  79. package/templates/default/e2e/sample.spec.ts_obsidian-test.ejs +16 -0
  80. package/templates/default/e2e/sample.spec.ts_obsidian-test@lint-no-void-disable_dev-utils.ejs +3 -0
  81. package/templates/default/e2e/sample.spec.ts_obsidian-test@lint-no-void-enable_dev-utils.ejs +1 -0
  82. package/templates/default/e2e/sample.spec.ts_obsidian-test@unofficial_with-unofficial.ejs +13 -0
  83. package/templates/default/e2e/sample.spec.ts_wdio-obsidian.ejs +7 -0
  84. package/templates/default/e2e/sample.spec.ts_wdio-obsidian@unofficial_with-unofficial.ejs +15 -0
  85. package/templates/default/eslint.config.mts_dev-utils.ejs +1 -0
  86. package/templates/default/eslint.config.mts_standalone.ejs +119 -0
  87. package/templates/default/eslint.config.mts_standalone@extra-files_has-e2e.ejs +2 -0
  88. package/templates/default/jest.config.ts.ejs +57 -0
  89. package/templates/default/jest.config.ts@lint-root-dir_dev-utils.ejs +1 -0
  90. package/templates/default/jest.config.ts@ts-jest-tsconfig_solid.ejs +8 -0
  91. package/templates/default/linkinator.config.json.ejs +11 -0
  92. package/templates/default/manifest.json.ejs +11 -0
  93. package/templates/default/manifest.json@funding_has-funding.ejs +1 -0
  94. package/templates/default/manifest.json@platform_desktop-and-mobile.ejs +1 -0
  95. package/templates/default/manifest.json@platform_desktop-only.ejs +1 -0
  96. package/templates/default/package.json.ejs +32 -0
  97. package/templates/default/package.json@targets_parcel.ejs +36 -0
  98. package/templates/default/parcel-namer-obsidian.cjs.ejs +26 -0
  99. package/templates/default/parcel-resolver-obsidian.cjs.ejs +37 -0
  100. package/templates/default/parcel-transformer-inline-imports.cjs.ejs +39 -0
  101. package/templates/default/parcel-transformer-svelte.cjs.ejs +67 -0
  102. package/templates/default/pinned-versions.json.ejs +1 -0
  103. package/templates/default/pnpm-workspace.yaml.ejs +19 -0
  104. package/templates/default/postcss.config.cjs.ejs +12 -0
  105. package/templates/default/prettier.config.mjs.ejs +5 -0
  106. package/templates/default/rollup.config.mjs.ejs +7 -0
  107. package/templates/default/scripts/babel.config.ts_rollup.ejs +16 -0
  108. package/templates/default/scripts/babel.config.ts_rollup@babel-plugin_preact.ejs +1 -0
  109. package/templates/default/scripts/babel.config.ts_rollup@babel-preset_react.ejs +1 -0
  110. package/templates/default/scripts/babel.config.ts_rollup@babel-preset_solid.ejs +1 -0
  111. package/templates/default/scripts/build-clean.ts.ejs +4 -0
  112. package/templates/default/scripts/build-compile.ts.ejs +4 -0
  113. package/templates/default/scripts/build-styles.ts.ejs +12 -0
  114. package/templates/default/scripts/build.ts.ejs +6 -0
  115. package/templates/default/scripts/build.ts@bundler_cli-bundler.ejs +63 -0
  116. package/templates/default/scripts/build.ts@bundler_cli-bundler@compile_svelte.ejs +4 -0
  117. package/templates/default/scripts/build.ts@bundler_cli-bundler@fs-import_hot-reload-plugin.ejs +2 -0
  118. package/templates/default/scripts/build.ts@bundler_cli-bundler@hot-reload_hot-reload-plugin.ejs +3 -0
  119. package/templates/default/scripts/build.ts@bundler_cli-bundler@import_tailwind.ejs +2 -0
  120. package/templates/default/scripts/build.ts@bundler_cli-bundler@lint-build-styles_dev-utils.ejs +1 -0
  121. package/templates/default/scripts/build.ts@bundler_cli-bundler@reload_obsidian-cli.ejs +5 -0
  122. package/templates/default/scripts/build.ts@bundler_cli-bundler@run_parcel.ejs +1 -0
  123. package/templates/default/scripts/build.ts@bundler_cli-bundler@run_rollup.ejs +1 -0
  124. package/templates/default/scripts/build.ts@bundler_cli-bundler@run_vite.ejs +1 -0
  125. package/templates/default/scripts/build.ts@bundler_cli-bundler@run_webpack.ejs +1 -0
  126. package/templates/default/scripts/build.ts@bundler_esbuild.ejs +3 -0
  127. package/templates/default/scripts/build.ts@bundler_esbuild@compile_svelte.ejs +4 -0
  128. package/templates/default/scripts/build.ts@bundler_esbuild@dev-utils-pre-build_tailwind.ejs +2 -0
  129. package/templates/default/scripts/build.ts@bundler_esbuild@fs-import_hot-reload-plugin.ejs +2 -0
  130. package/templates/default/scripts/build.ts@bundler_esbuild@hot-reload_hot-reload-plugin.ejs +4 -0
  131. package/templates/default/scripts/build.ts@bundler_esbuild@import_postcss.ejs +1 -0
  132. package/templates/default/scripts/build.ts@bundler_esbuild@import_scss.ejs +1 -0
  133. package/templates/default/scripts/build.ts@bundler_esbuild@import_solid.ejs +1 -0
  134. package/templates/default/scripts/build.ts@bundler_esbuild@import_svelte.ejs +10 -0
  135. package/templates/default/scripts/build.ts@bundler_esbuild@import_tailwind.ejs +1 -0
  136. package/templates/default/scripts/build.ts@bundler_esbuild@import_vue.ejs +1 -0
  137. package/templates/default/scripts/build.ts@bundler_esbuild@lint-build-styles_dev-utils.ejs +1 -0
  138. package/templates/default/scripts/build.ts@bundler_esbuild@options_preact.ejs +2 -0
  139. package/templates/default/scripts/build.ts@bundler_esbuild@options_react.ejs +1 -0
  140. package/templates/default/scripts/build.ts@bundler_esbuild@options_wasm.ejs +5 -0
  141. package/templates/default/scripts/build.ts@bundler_esbuild@plugin_postcss.ejs +1 -0
  142. package/templates/default/scripts/build.ts@bundler_esbuild@plugin_scss.ejs +1 -0
  143. package/templates/default/scripts/build.ts@bundler_esbuild@plugin_solid.ejs +1 -0
  144. package/templates/default/scripts/build.ts@bundler_esbuild@plugin_svelte.ejs +5 -0
  145. package/templates/default/scripts/build.ts@bundler_esbuild@plugin_vue.ejs +1 -0
  146. package/templates/default/scripts/build.ts@bundler_esbuild@preset_dev-utils.ejs +7 -0
  147. package/templates/default/scripts/build.ts@bundler_esbuild@preset_standalone.ejs +138 -0
  148. package/templates/default/scripts/build.ts@bundler_esbuild@reload_obsidian-cli.ejs +6 -0
  149. package/templates/default/scripts/commit.ts_dev-utils.ejs +3 -0
  150. package/templates/default/scripts/commit.ts_standalone.ejs +3 -0
  151. package/templates/default/scripts/commitlint-config.ts.ejs +5 -0
  152. package/templates/default/scripts/demo-vault-global-setup.ts.ejs +43 -0
  153. package/templates/default/scripts/dev.ts.ejs +6 -0
  154. package/templates/default/scripts/dev.ts@bundler_cli-bundler.ejs +8 -0
  155. package/templates/default/scripts/dev.ts@bundler_esbuild.ejs +3 -0
  156. package/templates/default/scripts/dev.ts@bundler_esbuild@dev-utils-pre-build_tailwind.ejs +2 -0
  157. package/templates/default/scripts/dev.ts@bundler_esbuild@lint-build-styles_dev-utils.ejs +1 -0
  158. package/templates/default/scripts/dev.ts@bundler_esbuild@preset_dev-utils.ejs +7 -0
  159. package/templates/default/scripts/dev.ts@bundler_esbuild@preset_standalone.ejs +3 -0
  160. package/templates/default/scripts/esbuild-plugins.ts.ejs +20 -0
  161. package/templates/default/scripts/esbuild-plugins.ts@dev-utils-import_vue.ejs +2 -0
  162. package/templates/default/scripts/esbuild-plugins.ts@dev-utils-plugin_vue.ejs +1 -0
  163. package/templates/default/scripts/esbuild-plugins.ts@dev-utils-plugin_wasm.ejs +13 -0
  164. package/templates/default/scripts/eslint-config.ts.ejs +113 -0
  165. package/templates/default/scripts/eslint-config.ts@extra-files_has-e2e.ejs +7 -0
  166. package/templates/default/scripts/find-overexposed-fix.ts.ejs +26 -0
  167. package/templates/default/scripts/find-overexposed.ts.ejs +15 -0
  168. package/templates/default/scripts/format-check.ts.ejs +4 -0
  169. package/templates/default/scripts/format-check.ts@tool_biome-formatter.ejs +8 -0
  170. package/templates/default/scripts/format-check.ts@tool_dprint.ejs +3 -0
  171. package/templates/default/scripts/format-check.ts@tool_dprint@preset_dev-utils.ejs +7 -0
  172. package/templates/default/scripts/format-check.ts@tool_dprint@preset_standalone.ejs +8 -0
  173. package/templates/default/scripts/format-check.ts@tool_prettier.ejs +8 -0
  174. package/templates/default/scripts/format.ts.ejs +4 -0
  175. package/templates/default/scripts/format.ts@tool_biome-formatter.ejs +8 -0
  176. package/templates/default/scripts/format.ts@tool_dprint.ejs +3 -0
  177. package/templates/default/scripts/format.ts@tool_dprint@preset_dev-utils.ejs +7 -0
  178. package/templates/default/scripts/format.ts@tool_dprint@preset_standalone.ejs +8 -0
  179. package/templates/default/scripts/format.ts@tool_prettier.ejs +8 -0
  180. package/templates/default/scripts/framework-component-stub.ts.ejs +46 -0
  181. package/templates/default/scripts/gate.ts.ejs +10 -0
  182. package/templates/default/scripts/lint-fix.ts.ejs +6 -0
  183. package/templates/default/scripts/lint-fix.ts@tool_biome-linter.ejs +8 -0
  184. package/templates/default/scripts/lint-fix.ts@tool_eslint.ejs +3 -0
  185. package/templates/default/scripts/lint-fix.ts@tool_eslint@preset_dev-utils.ejs +7 -0
  186. package/templates/default/scripts/lint-fix.ts@tool_eslint@preset_standalone.ejs +8 -0
  187. package/templates/default/scripts/lint-md-fix.ts_dev-utils.ejs +7 -0
  188. package/templates/default/scripts/lint-md-fix.ts_standalone.ejs +37 -0
  189. package/templates/default/scripts/lint-md.ts_dev-utils.ejs +7 -0
  190. package/templates/default/scripts/lint-md.ts_standalone.ejs +37 -0
  191. package/templates/default/scripts/lint.ts.ejs +6 -0
  192. package/templates/default/scripts/lint.ts@tool_biome-linter.ejs +8 -0
  193. package/templates/default/scripts/lint.ts@tool_eslint.ejs +3 -0
  194. package/templates/default/scripts/lint.ts@tool_eslint@preset_dev-utils.ejs +7 -0
  195. package/templates/default/scripts/lint.ts@tool_eslint@preset_standalone.ejs +8 -0
  196. package/templates/default/scripts/markdownlint-cli2-config.ts_dev-utils.ejs +14 -0
  197. package/templates/default/scripts/markdownlint-cli2-config.ts_standalone.ejs +36 -0
  198. package/templates/default/scripts/nano-staged-config.ts.ejs +9 -0
  199. package/templates/default/scripts/postcss.config.ts.ejs +1 -0
  200. package/templates/default/scripts/postcss.config.ts@plugins_postcss.ejs +7 -0
  201. package/templates/default/scripts/prepare.ts_dev-utils.ejs +3 -0
  202. package/templates/default/scripts/prepare.ts_standalone.ejs +3 -0
  203. package/templates/default/scripts/prettier.config.ts.ejs +12 -0
  204. package/templates/default/scripts/rollup.config.ts.ejs +129 -0
  205. package/templates/default/scripts/rollup.config.ts@import_rollup-babel.ejs +5 -0
  206. package/templates/default/scripts/rollup.config.ts@import_rollup-json.ejs +1 -0
  207. package/templates/default/scripts/rollup.config.ts@import_rollup-postcss.ejs +4 -0
  208. package/templates/default/scripts/rollup.config.ts@import_scss.ejs +1 -0
  209. package/templates/default/scripts/rollup.config.ts@import_svelte.ejs +2 -0
  210. package/templates/default/scripts/rollup.config.ts@import_vue.ejs +1 -0
  211. package/templates/default/scripts/rollup.config.ts@import_wasm.ejs +1 -0
  212. package/templates/default/scripts/rollup.config.ts@lint-out-dir_dev-utils.ejs +1 -0
  213. package/templates/default/scripts/rollup.config.ts@post-plugin_preact.ejs +4 -0
  214. package/templates/default/scripts/rollup.config.ts@post-plugin_react.ejs +5 -0
  215. package/templates/default/scripts/rollup.config.ts@post-plugin_rollup-json.ejs +1 -0
  216. package/templates/default/scripts/rollup.config.ts@post-plugin_rollup-postcss.ejs +4 -0
  217. package/templates/default/scripts/rollup.config.ts@post-plugin_scss.ejs +4 -0
  218. package/templates/default/scripts/rollup.config.ts@post-plugin_solid.ejs +4 -0
  219. package/templates/default/scripts/rollup.config.ts@post-plugin_wasm.ejs +4 -0
  220. package/templates/default/scripts/rollup.config.ts@pre-plugin_svelte.ejs +4 -0
  221. package/templates/default/scripts/rollup.config.ts@pre-plugin_vue.ejs +13 -0
  222. package/templates/default/scripts/rollup.config.ts@unwrap_rollup-babel.ejs +1 -0
  223. package/templates/default/scripts/rollup.config.ts@unwrap_rollup-json.ejs +1 -0
  224. package/templates/default/scripts/rollup.config.ts@unwrap_rollup-postcss.ejs +1 -0
  225. package/templates/default/scripts/rollup.config.ts@unwrap_scss.ejs +1 -0
  226. package/templates/default/scripts/rollup.config.ts@unwrap_svelte.ejs +1 -0
  227. package/templates/default/scripts/rollup.config.ts@unwrap_vue.ejs +1 -0
  228. package/templates/default/scripts/rollup.config.ts@unwrap_wasm.ejs +1 -0
  229. package/templates/default/scripts/spellcheck.ts_dev-utils.ejs +7 -0
  230. package/templates/default/scripts/spellcheck.ts_standalone.ejs +8 -0
  231. package/templates/default/scripts/stylesheet-stub.ts.ejs +17 -0
  232. package/templates/default/scripts/test-coverage.ts.ejs +8 -0
  233. package/templates/default/scripts/test-e2e.ts.ejs +1 -0
  234. package/templates/default/scripts/test-e2e.ts@tool_obsidian-test.ejs +8 -0
  235. package/templates/default/scripts/test-e2e.ts@tool_wdio-obsidian.ejs +8 -0
  236. package/templates/default/scripts/test-integration-android.ts.ejs +8 -0
  237. package/templates/default/scripts/test-integration-demo-vault.ts.ejs +8 -0
  238. package/templates/default/scripts/test-integration-desktop-performance.ts.ejs +8 -0
  239. package/templates/default/scripts/test-integration-desktop.ts.ejs +8 -0
  240. package/templates/default/scripts/test-integration-no-app.ts.ejs +8 -0
  241. package/templates/default/scripts/test-integration.ts.ejs +22 -0
  242. package/templates/default/scripts/test-watch.ts_dev-utils.ejs +1 -0
  243. package/templates/default/scripts/test-watch.ts_dev-utils@tool_jest.ejs +13 -0
  244. package/templates/default/scripts/test-watch.ts_dev-utils@tool_vitest.ejs +8 -0
  245. package/templates/default/scripts/test-watch.ts_standalone.ejs +1 -0
  246. package/templates/default/scripts/test-watch.ts_standalone@tool_jest.ejs +13 -0
  247. package/templates/default/scripts/test-watch.ts_standalone@tool_vitest.ejs +8 -0
  248. package/templates/default/scripts/test.ts_dev-utils.ejs +1 -0
  249. package/templates/default/scripts/test.ts_dev-utils@tool_jest.ejs +13 -0
  250. package/templates/default/scripts/test.ts_dev-utils@tool_vitest.ejs +8 -0
  251. package/templates/default/scripts/test.ts_standalone.ejs +1 -0
  252. package/templates/default/scripts/test.ts_standalone@tool_jest.ejs +13 -0
  253. package/templates/default/scripts/test.ts_standalone@tool_vitest.ejs +8 -0
  254. package/templates/default/scripts/version.ts_dev-utils.ejs +10 -0
  255. package/templates/default/scripts/version.ts_standalone.ejs +317 -0
  256. package/templates/default/scripts/vite.config.ts.ejs +76 -0
  257. package/templates/default/scripts/vite.config.ts@import_preact.ejs +1 -0
  258. package/templates/default/scripts/vite.config.ts@import_react.ejs +1 -0
  259. package/templates/default/scripts/vite.config.ts@import_solid.ejs +1 -0
  260. package/templates/default/scripts/vite.config.ts@import_svelte.ejs +1 -0
  261. package/templates/default/scripts/vite.config.ts@import_vue.ejs +1 -0
  262. package/templates/default/scripts/vite.config.ts@lint-empty-out-dir_dev-utils.ejs +1 -0
  263. package/templates/default/scripts/vite.config.ts@lint-out-dir_dev-utils.ejs +1 -0
  264. package/templates/default/scripts/vite.config.ts@plugin_preact.ejs +1 -0
  265. package/templates/default/scripts/vite.config.ts@plugin_react.ejs +1 -0
  266. package/templates/default/scripts/vite.config.ts@plugin_solid.ejs +1 -0
  267. package/templates/default/scripts/vite.config.ts@plugin_svelte.ejs +1 -0
  268. package/templates/default/scripts/vite.config.ts@plugin_vue.ejs +1 -0
  269. package/templates/default/scripts/vitest-config.ts.ejs +75 -0
  270. package/templates/default/scripts/vitest-config.ts@post-config_solid.ejs +67 -0
  271. package/templates/default/scripts/wasm-module-stub.ts.ejs +21 -0
  272. package/templates/default/scripts/webpack.config.ts.ejs +124 -0
  273. package/templates/default/scripts/webpack.config.ts@import_svelte.ejs +1 -0
  274. package/templates/default/scripts/webpack.config.ts@import_vue.ejs +1 -0
  275. package/templates/default/scripts/webpack.config.ts@import_webpack-css-extract.ejs +4 -0
  276. package/templates/default/scripts/webpack.config.ts@lint-webpack-default_dev-utils.ejs +1 -0
  277. package/templates/default/scripts/webpack.config.ts@plugin_vue.ejs +1 -0
  278. package/templates/default/scripts/webpack.config.ts@plugin_webpack-css-extract.ejs +4 -0
  279. package/templates/default/scripts/webpack.config.ts@rule_css-modules.ejs +7 -0
  280. package/templates/default/scripts/webpack.config.ts@rule_css.ejs +4 -0
  281. package/templates/default/scripts/webpack.config.ts@rule_postcss.ejs +4 -0
  282. package/templates/default/scripts/webpack.config.ts@rule_scss.ejs +4 -0
  283. package/templates/default/scripts/webpack.config.ts@rule_solid.ejs +17 -0
  284. package/templates/default/scripts/webpack.config.ts@rule_svelte.ejs +7 -0
  285. package/templates/default/scripts/webpack.config.ts@rule_tailwind.ejs +4 -0
  286. package/templates/default/scripts/webpack.config.ts@rule_vue.ejs +4 -0
  287. package/templates/default/scripts/webpack.config.ts@rule_wasm.ejs +8 -0
  288. package/templates/default/scripts/webpack.config.ts@ts-loader-compiler-options_vue.ejs +5 -0
  289. package/templates/default/scripts/webpack.config.ts@ts-loader-options_vue.ejs +5 -0
  290. package/templates/default/src/demo-vault-buttons.demo-vault.integration.test.ts.ejs +12 -0
  291. package/templates/default/src/demo-vault.no-app.integration.test.ts.ejs +18 -0
  292. package/templates/default/src/editor-extensions/sample-state-field.ts_dev-utils.ejs +56 -0
  293. package/templates/default/src/editor-extensions/sample-state-field.ts_standalone.ejs +60 -0
  294. package/templates/default/src/editor-extensions/sample-view-plugin.ts.ejs +66 -0
  295. package/templates/default/src/editor-extensions/sample-widget.ts.ejs +22 -0
  296. package/templates/default/src/editor-suggests/sample-editor-suggest.ts.ejs +42 -0
  297. package/templates/default/src/i18n/en/index.ts.ejs +10 -0
  298. package/templates/default/src/i18n/index.ts_i18next.ejs +21 -0
  299. package/templates/default/src/i18n/index.ts_typesafe-i18n.ejs +14 -0
  300. package/templates/default/src/i18n/locales/en.json.ejs +3 -0
  301. package/templates/default/src/lit-elements/sample-lit-element.ts.ejs +54 -0
  302. package/templates/default/src/main.test.ts.ejs +11 -0
  303. package/templates/default/src/main.test.ts@test-imports_vitest.ejs +6 -0
  304. package/templates/default/src/main.ts.ejs +4 -0
  305. package/templates/default/src/main.ts@import_css-modules.ejs +1 -0
  306. package/templates/default/src/main.ts@import_css.ejs +1 -0
  307. package/templates/default/src/main.ts@import_postcss.ejs +1 -0
  308. package/templates/default/src/main.ts@import_scss.ejs +1 -0
  309. package/templates/default/src/main.ts@import_tailwind.ejs +1 -0
  310. package/templates/default/src/modals/sample-form-modal.ts.ejs +35 -0
  311. package/templates/default/src/modals/sample-suggest-modal.ts.ejs +37 -0
  312. package/templates/default/src/plugin-settings-component.test.ts_demo.ejs +49 -0
  313. package/templates/default/src/plugin-settings-component.test.ts_demo@test-imports_vitest.ejs +5 -0
  314. package/templates/default/src/plugin-settings-component.test.ts_enhanced.ejs +41 -0
  315. package/templates/default/src/plugin-settings-component.test.ts_enhanced@test-imports_vitest.ejs +5 -0
  316. package/templates/default/src/plugin-settings-component.ts_demo.ejs +87 -0
  317. package/templates/default/src/plugin-settings-component.ts_enhanced.ejs +27 -0
  318. package/templates/default/src/plugin-settings-tab.ts_demo.ejs +286 -0
  319. package/templates/default/src/plugin-settings-tab.ts_enhanced.ejs +26 -0
  320. package/templates/default/src/plugin-settings.test.ts_demo.ejs +14 -0
  321. package/templates/default/src/plugin-settings.test.ts_demo@test-imports_vitest.ejs +6 -0
  322. package/templates/default/src/plugin-settings.ts_demo.ejs +64 -0
  323. package/templates/default/src/plugin-settings.ts_enhanced.ejs +3 -0
  324. package/templates/default/src/plugin.test.ts_jest.ejs +9 -0
  325. package/templates/default/src/plugin.test.ts_vitest.ejs +14 -0
  326. package/templates/default/src/plugin.ts_demo.ejs +316 -0
  327. package/templates/default/src/plugin.ts_demo@import_i18next.ejs +1 -0
  328. package/templates/default/src/plugin.ts_demo@import_lit.ejs +4 -0
  329. package/templates/default/src/plugin.ts_demo@import_preact.ejs +4 -0
  330. package/templates/default/src/plugin.ts_demo@import_react.ejs +4 -0
  331. package/templates/default/src/plugin.ts_demo@import_solid.ejs +4 -0
  332. package/templates/default/src/plugin.ts_demo@import_svelte.ejs +4 -0
  333. package/templates/default/src/plugin.ts_demo@import_typesafe-i18n.ejs +1 -0
  334. package/templates/default/src/plugin.ts_demo@import_vue.ejs +4 -0
  335. package/templates/default/src/plugin.ts_demo@import_wasm.ejs +1 -0
  336. package/templates/default/src/plugin.ts_demo@method_ui-view.ejs +7 -0
  337. package/templates/default/src/plugin.ts_demo@onload_i18next.ejs +2 -0
  338. package/templates/default/src/plugin.ts_demo@onload_lit.ejs +4 -0
  339. package/templates/default/src/plugin.ts_demo@onload_preact.ejs +4 -0
  340. package/templates/default/src/plugin.ts_demo@onload_react.ejs +4 -0
  341. package/templates/default/src/plugin.ts_demo@onload_solid.ejs +4 -0
  342. package/templates/default/src/plugin.ts_demo@onload_svelte.ejs +4 -0
  343. package/templates/default/src/plugin.ts_demo@onload_typesafe-i18n.ejs +8 -0
  344. package/templates/default/src/plugin.ts_demo@onload_vue.ejs +4 -0
  345. package/templates/default/src/plugin.ts_demo@onload_wasm.ejs +2 -0
  346. package/templates/default/src/plugin.ts_enhanced.ejs +72 -0
  347. package/templates/default/src/plugin.ts_enhanced@import2_codemirror.ejs +3 -0
  348. package/templates/default/src/plugin.ts_enhanced@import2_i18next.ejs +2 -0
  349. package/templates/default/src/plugin.ts_enhanced@import2_lit.ejs +4 -0
  350. package/templates/default/src/plugin.ts_enhanced@import2_preact.ejs +4 -0
  351. package/templates/default/src/plugin.ts_enhanced@import2_react.ejs +4 -0
  352. package/templates/default/src/plugin.ts_enhanced@import2_solid.ejs +4 -0
  353. package/templates/default/src/plugin.ts_enhanced@import2_svelte.ejs +4 -0
  354. package/templates/default/src/plugin.ts_enhanced@import2_typesafe-i18n.ejs +2 -0
  355. package/templates/default/src/plugin.ts_enhanced@import2_vue.ejs +4 -0
  356. package/templates/default/src/plugin.ts_enhanced@import2_wasm.ejs +1 -0
  357. package/templates/default/src/plugin.ts_enhanced@method_ui-view.ejs +7 -0
  358. package/templates/default/src/plugin.ts_enhanced@onload_codemirror.ejs +4 -0
  359. package/templates/default/src/plugin.ts_enhanced@onload_i18next.ejs +2 -0
  360. package/templates/default/src/plugin.ts_enhanced@onload_lit.ejs +4 -0
  361. package/templates/default/src/plugin.ts_enhanced@onload_preact.ejs +4 -0
  362. package/templates/default/src/plugin.ts_enhanced@onload_react.ejs +4 -0
  363. package/templates/default/src/plugin.ts_enhanced@onload_solid.ejs +4 -0
  364. package/templates/default/src/plugin.ts_enhanced@onload_svelte.ejs +4 -0
  365. package/templates/default/src/plugin.ts_enhanced@onload_typesafe-i18n.ejs +8 -0
  366. package/templates/default/src/plugin.ts_enhanced@onload_vue.ejs +4 -0
  367. package/templates/default/src/plugin.ts_enhanced@onload_wasm.ejs +2 -0
  368. package/templates/default/src/plugin.ts_standalone.ejs +56 -0
  369. package/templates/default/src/plugin.ts_standalone@import_codemirror.ejs +3 -0
  370. package/templates/default/src/plugin.ts_standalone@import_i18next.ejs +1 -0
  371. package/templates/default/src/plugin.ts_standalone@import_lit.ejs +4 -0
  372. package/templates/default/src/plugin.ts_standalone@import_preact.ejs +4 -0
  373. package/templates/default/src/plugin.ts_standalone@import_react.ejs +4 -0
  374. package/templates/default/src/plugin.ts_standalone@import_solid.ejs +4 -0
  375. package/templates/default/src/plugin.ts_standalone@import_svelte.ejs +4 -0
  376. package/templates/default/src/plugin.ts_standalone@import_typesafe-i18n.ejs +1 -0
  377. package/templates/default/src/plugin.ts_standalone@import_vue.ejs +4 -0
  378. package/templates/default/src/plugin.ts_standalone@import_wasm.ejs +1 -0
  379. package/templates/default/src/plugin.ts_standalone@method_ui-view.ejs +7 -0
  380. package/templates/default/src/plugin.ts_standalone@onload_codemirror.ejs +4 -0
  381. package/templates/default/src/plugin.ts_standalone@onload_i18next.ejs +2 -0
  382. package/templates/default/src/plugin.ts_standalone@onload_lit.ejs +4 -0
  383. package/templates/default/src/plugin.ts_standalone@onload_preact.ejs +4 -0
  384. package/templates/default/src/plugin.ts_standalone@onload_react.ejs +4 -0
  385. package/templates/default/src/plugin.ts_standalone@onload_solid.ejs +4 -0
  386. package/templates/default/src/plugin.ts_standalone@onload_svelte.ejs +4 -0
  387. package/templates/default/src/plugin.ts_standalone@onload_typesafe-i18n.ejs +8 -0
  388. package/templates/default/src/plugin.ts_standalone@onload_vue.ejs +4 -0
  389. package/templates/default/src/plugin.ts_standalone@onload_wasm.ejs +2 -0
  390. package/templates/default/src/preact-components/sample-preact-component.tsx_dev-utils.ejs +47 -0
  391. package/templates/default/src/preact-components/sample-preact-component.tsx_standalone.ejs +47 -0
  392. package/templates/default/src/react-components/sample-react-component.tsx_dev-utils.ejs +35 -0
  393. package/templates/default/src/react-components/sample-react-component.tsx_standalone.ejs +47 -0
  394. package/templates/default/src/settings.ts.ejs +32 -0
  395. package/templates/default/src/solid-components/sample-solid-component.tsx_dev-utils.ejs +47 -0
  396. package/templates/default/src/solid-components/sample-solid-component.tsx_standalone.ejs +47 -0
  397. package/templates/default/src/styles/css-modules.d.ts.ejs +4 -0
  398. package/templates/default/src/styles/main.css_dev-utils.ejs +17 -0
  399. package/templates/default/src/styles/main.css_dev-utils@content_tailwind.ejs +1 -0
  400. package/templates/default/src/styles/main.css_standalone.ejs +11 -0
  401. package/templates/default/src/styles/main.css_standalone@content_tailwind.ejs +1 -0
  402. package/templates/default/src/styles/main.module.css.ejs +3 -0
  403. package/templates/default/src/styles/main.scss_dev-utils.ejs +16 -0
  404. package/templates/default/src/styles/main.scss_standalone.ejs +10 -0
  405. package/templates/default/src/styles/styles.d.ts.ejs +4 -0
  406. package/templates/default/src/svelte-components/sample-svelte-component.d.ts.ejs +7 -0
  407. package/templates/default/src/svelte-components/sample-svelte-component.svelte.ejs +24 -0
  408. package/templates/default/src/views/sample-lit-view.ts.ejs +30 -0
  409. package/templates/default/src/views/sample-lit-view.ts@lint-register-element_dev-utils.ejs +1 -0
  410. package/templates/default/src/views/sample-preact-view.tsx_dev-utils.ejs +45 -0
  411. package/templates/default/src/views/sample-preact-view.tsx_standalone.ejs +42 -0
  412. package/templates/default/src/views/sample-react-view.tsx_dev-utils.ejs +47 -0
  413. package/templates/default/src/views/sample-react-view.tsx_standalone.ejs +49 -0
  414. package/templates/default/src/views/sample-solid-view.tsx_dev-utils.ejs +46 -0
  415. package/templates/default/src/views/sample-solid-view.tsx_standalone.ejs +46 -0
  416. package/templates/default/src/views/sample-svelte-view.ts.ejs +47 -0
  417. package/templates/default/src/views/sample-view.ts.ejs +37 -0
  418. package/templates/default/src/views/sample-vue-view.ts.ejs +34 -0
  419. package/templates/default/src/vue-components/sample-vue-component.d.ts.ejs +6 -0
  420. package/templates/default/src/vue-components/sample-vue-component.vue.ejs +25 -0
  421. package/templates/default/src/wasm/README.md.ejs +29 -0
  422. package/templates/default/src/wasm/answer.ts_esbuild.ejs +23 -0
  423. package/templates/default/src/wasm/answer.ts_parcel.ejs +29 -0
  424. package/templates/default/src/wasm/answer.ts_rollup.ejs +19 -0
  425. package/templates/default/src/wasm/answer.ts_vite.ejs +29 -0
  426. package/templates/default/src/wasm/answer.ts_webpack.ejs +29 -0
  427. package/templates/default/src/wasm/module.wasm +0 -0
  428. package/templates/default/src/wasm/module.wat.ejs +12 -0
  429. package/templates/default/src/wasm/sample-command.ts.ejs +33 -0
  430. package/templates/default/src/wasm.d.ts_esbuild.ejs +12 -0
  431. package/templates/default/src/wasm.d.ts_parcel.ejs +7 -0
  432. package/templates/default/src/wasm.d.ts_rollup.ejs +8 -0
  433. package/templates/default/src/wasm.d.ts_vite.ejs +8 -0
  434. package/templates/default/src/wasm.d.ts_webpack.ejs +7 -0
  435. package/templates/default/tsconfig.json_dev-utils.ejs +34 -0
  436. package/templates/default/tsconfig.json_dev-utils@compiler-options_i18next.ejs +1 -0
  437. package/templates/default/tsconfig.json_dev-utils@compiler-options_lit.ejs +2 -0
  438. package/templates/default/tsconfig.json_dev-utils@compiler-options_preact.ejs +2 -0
  439. package/templates/default/tsconfig.json_dev-utils@compiler-options_react.ejs +1 -0
  440. package/templates/default/tsconfig.json_dev-utils@compiler-options_solid.ejs +2 -0
  441. package/templates/default/tsconfig.json_dev-utils@include_has-e2e.ejs +1 -0
  442. package/templates/default/tsconfig.json_dev-utils@include_svelte.ejs +1 -0
  443. package/templates/default/tsconfig.json_dev-utils@include_vue.ejs +1 -0
  444. package/templates/default/tsconfig.json_dev-utils@types_jest.ejs +2 -0
  445. package/templates/default/tsconfig.json_dev-utils@types_svelte.ejs +2 -0
  446. package/templates/default/tsconfig.json_dev-utils@types_wdio-obsidian.ejs +5 -0
  447. package/templates/default/tsconfig.json_dev-utils@types_with-unofficial.ejs +2 -0
  448. package/templates/default/tsconfig.json_standalone.ejs +47 -0
  449. package/templates/default/tsconfig.json_standalone@compiler-options_i18next.ejs +1 -0
  450. package/templates/default/tsconfig.json_standalone@compiler-options_lit.ejs +2 -0
  451. package/templates/default/tsconfig.json_standalone@compiler-options_preact.ejs +2 -0
  452. package/templates/default/tsconfig.json_standalone@compiler-options_react.ejs +1 -0
  453. package/templates/default/tsconfig.json_standalone@compiler-options_solid.ejs +2 -0
  454. package/templates/default/tsconfig.json_standalone@include-extra_preact.ejs +2 -0
  455. package/templates/default/tsconfig.json_standalone@include-extra_react.ejs +2 -0
  456. package/templates/default/tsconfig.json_standalone@include-extra_solid.ejs +2 -0
  457. package/templates/default/tsconfig.json_standalone@include_has-e2e.ejs +1 -0
  458. package/templates/default/tsconfig.json_standalone@include_svelte.ejs +1 -0
  459. package/templates/default/tsconfig.json_standalone@include_vue.ejs +1 -0
  460. package/templates/default/tsconfig.json_standalone@types_jest.ejs +2 -0
  461. package/templates/default/tsconfig.json_standalone@types_svelte.ejs +2 -0
  462. package/templates/default/tsconfig.json_standalone@types_wdio-obsidian.ejs +5 -0
  463. package/templates/default/tsconfig.json_standalone@types_with-unofficial.ejs +2 -0
  464. package/templates/default/versions.json.ejs +3 -0
  465. package/templates/default/vite.config.ts.ejs +3 -0
  466. package/templates/default/vitest.config.ts_dev-utils.ejs +1 -0
  467. package/templates/default/vitest.config.ts_standalone.ejs +55 -0
  468. package/templates/default/vitest.config.ts_standalone@jsx_solid.ejs +11 -0
  469. package/templates/default/wdio.conf.ts.ejs +21 -0
  470. package/templates/default/webpack.config.ts.ejs +3 -0
package/dist/main.js ADDED
@@ -0,0 +1,4184 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.ts
4
+ import {
5
+ cancel as cancel3,
6
+ confirm,
7
+ intro,
8
+ log as log4,
9
+ note,
10
+ outro,
11
+ select as select2,
12
+ spinner,
13
+ text as text2
14
+ } from "@clack/prompts";
15
+ import { spawn } from "node:child_process";
16
+ import {
17
+ chmodSync,
18
+ existsSync as existsSync6,
19
+ readFileSync as readFileSync6,
20
+ writeFileSync as writeFileSync2
21
+ } from "node:fs";
22
+ import {
23
+ join as join5,
24
+ resolve as resolvePath
25
+ } from "node:path";
26
+ import { compare } from "semver";
27
+
28
+ // src/cli-args.ts
29
+ import {
30
+ existsSync,
31
+ readFileSync
32
+ } from "node:fs";
33
+
34
+ // src/clack-select.ts
35
+ import {
36
+ SelectPrompt,
37
+ settings
38
+ } from "@clack/core";
39
+ import {
40
+ S_BAR,
41
+ S_BAR_END,
42
+ S_RADIO_ACTIVE,
43
+ S_RADIO_INACTIVE,
44
+ symbol
45
+ } from "@clack/prompts";
46
+ import { styleText } from "node:util";
47
+
48
+ // src/clack-utils.ts
49
+ import {
50
+ cancel,
51
+ isCancel
52
+ } from "@clack/prompts";
53
+ var lastCancelWasEscape = false;
54
+ var GoBackError = class extends Error {
55
+ constructor() {
56
+ super("Go back");
57
+ }
58
+ };
59
+ function assertNotCancelled(value) {
60
+ if (isCancel(value)) {
61
+ cancel("Operation cancelled.");
62
+ process.exit(0);
63
+ }
64
+ }
65
+ function resetCancelFlag() {
66
+ lastCancelWasEscape = false;
67
+ }
68
+ function setEscapeCancel() {
69
+ lastCancelWasEscape = true;
70
+ }
71
+ function throwGoBackOnCancel(value) {
72
+ if (isCancel(value)) {
73
+ if (lastCancelWasEscape) {
74
+ lastCancelWasEscape = false;
75
+ throw new GoBackError();
76
+ }
77
+ cancel("Operation cancelled.");
78
+ process.exit(0);
79
+ }
80
+ return value;
81
+ }
82
+
83
+ // src/clack-select.ts
84
+ async function select(opts) {
85
+ const selectOpts = {
86
+ options: opts.options,
87
+ render() {
88
+ const withGuide = settings.withGuide;
89
+ const header = `${withGuide ? `${styleText("gray", S_BAR)}
90
+ ` : ""}${symbol(this.state)} ${opts.message}
91
+ `;
92
+ const selected = this.options[this.cursor];
93
+ switch (this.state) {
94
+ case "cancel": {
95
+ const bar = withGuide ? `${styleText("gray", S_BAR)} ` : "";
96
+ const label = selected?.label ?? "";
97
+ return `${header}${bar}${styleText(["strikethrough", "dim"], label)}${withGuide ? `
98
+ ${styleText("gray", S_BAR)}` : ""}`;
99
+ }
100
+ case "submit": {
101
+ const bar = withGuide ? `${styleText("gray", S_BAR)} ` : "";
102
+ const label = selected?.label ?? "";
103
+ return `${header}${bar}${styleText("dim", label)}`;
104
+ }
105
+ default: {
106
+ const bar = withGuide ? `${styleText("cyan", S_BAR)} ` : "";
107
+ const barEnd = withGuide ? styleText("cyan", S_BAR_END) : "";
108
+ const lines = this.options.map((opt, i) => {
109
+ const o = opt;
110
+ const isActive = i === this.cursor;
111
+ const radio = isActive ? styleText("green", S_RADIO_ACTIVE) : styleText("dim", S_RADIO_INACTIVE);
112
+ const label = isActive ? o.label : styleText("dim", o.label);
113
+ const hint = o.hint && isActive ? ` ${styleText("dim", `(${o.hint})`)}` : "";
114
+ return `${radio} ${label}${hint}`;
115
+ });
116
+ return `${header}${lines.map((line) => `${bar}${line}`).join("\n")}
117
+ ${barEnd}
118
+ `;
119
+ }
120
+ }
121
+ }
122
+ };
123
+ if (opts.initialValue !== void 0) {
124
+ selectOpts.initialValue = opts.initialValue;
125
+ }
126
+ const prompt = new SelectPrompt(selectOpts);
127
+ prompt.on("key", (_char, key) => {
128
+ if (key.name === "escape") {
129
+ setEscapeCancel();
130
+ }
131
+ });
132
+ resetCancelFlag();
133
+ const result = await prompt.prompt();
134
+ return throwGoBackOnCancel(result) ?? "";
135
+ }
136
+
137
+ // src/feature-option.ts
138
+ var FeatureOption = class {
139
+ jsxImportSource;
140
+ partialName;
141
+ promptHint;
142
+ promptLabel;
143
+ settingValue;
144
+ constructor(config) {
145
+ this.settingValue = config.settingValue;
146
+ this.jsxImportSource = config.jsxImportSource;
147
+ this.partialName = config.partialName ?? config.settingValue;
148
+ this.promptLabel = config.promptLabel;
149
+ this.promptHint = config.promptHint;
150
+ }
151
+ // eslint-disable-next-line @typescript-eslint/no-empty-function -- Base no-op; subclasses override.
152
+ configure(_builder, _answers) {
153
+ }
154
+ };
155
+ async function promptFeature(params) {
156
+ const result = await select({
157
+ initialValue: params.savedValue ?? params.defaultOption.settingValue,
158
+ message: params.message,
159
+ options: params.options.map((o) => ({ hint: o.promptHint, label: o.promptLabel, value: o.settingValue }))
160
+ });
161
+ return result;
162
+ }
163
+ function resolveFeature(options, value) {
164
+ const option = options.find((o) => o.settingValue === value);
165
+ if (!option) {
166
+ throw new Error(`Unknown feature value: ${value}`);
167
+ }
168
+ return option;
169
+ }
170
+
171
+ // src/features/api-subset/official.ts
172
+ var Official = class extends FeatureOption {
173
+ constructor() {
174
+ super({ promptHint: "Stable, documented API only", promptLabel: "Official API", settingValue: "official" });
175
+ }
176
+ };
177
+
178
+ // src/features/api-subset/with-unofficial.ts
179
+ var WithUnofficial = class extends FeatureOption {
180
+ constructor() {
181
+ super({ promptHint: "Includes internal undocumented APIs (for experienced devs)", promptLabel: "Official + Unofficial", settingValue: "with-unofficial" });
182
+ }
183
+ configure(builder) {
184
+ builder.addPackage("@obsidian-typings/obsidian-public-latest");
185
+ }
186
+ };
187
+
188
+ // src/features/api-subset/index.ts
189
+ var API_SUBSET_OPTIONS = [new Official(), new WithUnofficial()];
190
+ async function promptApiSubset(savedValue) {
191
+ return promptFeature({ defaultOption: new Official(), message: "Obsidian API subset", options: API_SUBSET_OPTIONS, savedValue });
192
+ }
193
+
194
+ // src/features/preset/is-dev-utils-preset.ts
195
+ var DEV_UTILS_PRESETS = /* @__PURE__ */ new Set(["demo", "enhanced"]);
196
+ function isDevUtilsPreset(preset) {
197
+ return DEV_UTILS_PRESETS.has(preset);
198
+ }
199
+
200
+ // src/features/bundler/esbuild.ts
201
+ var Esbuild = class extends FeatureOption {
202
+ constructor() {
203
+ super({ promptHint: "Fast and simple (recommended)", promptLabel: "esbuild", settingValue: "esbuild" });
204
+ }
205
+ configure(builder, answers) {
206
+ builder.addPackage("esbuild");
207
+ if (isDevUtilsPreset(answers.preset)) {
208
+ builder.addFiles(["scripts/esbuild-plugins.ts"]);
209
+ }
210
+ }
211
+ };
212
+
213
+ // src/features/bundler/parcel.ts
214
+ var Parcel = class extends FeatureOption {
215
+ constructor() {
216
+ super({ promptHint: "Zero-config bundler", promptLabel: "Parcel", settingValue: "parcel" });
217
+ }
218
+ configure(builder) {
219
+ builder.addPackage("parcel").addDepcheckIgnore("parcel", "run as a CLI by the build script.").addPackage("@parcel/config-default").addDepcheckIgnore("@parcel/config-default", "extended by name in `.parcelrc`.").addPackage("@parcel/plugin").addFiles([
220
+ ".parcelrc",
221
+ "parcel-namer-obsidian.cjs",
222
+ "parcel-resolver-obsidian.cjs",
223
+ "parcel-transformer-inline-imports.cjs"
224
+ ]);
225
+ builder.addPartial("cli-bundler");
226
+ }
227
+ };
228
+
229
+ // src/features/bundler/rollup.ts
230
+ var Rollup = class extends FeatureOption {
231
+ constructor() {
232
+ super({ promptHint: "Flexible with plugin ecosystem", promptLabel: "Rollup", settingValue: "rollup" });
233
+ }
234
+ configure(builder) {
235
+ builder.addPackage("@rollup/plugin-commonjs").addPackage("@rollup/plugin-node-resolve").addPackage("@rollup/plugin-typescript").addPackage("@rollup/plugin-terser").addPackage("rollup").addFiles([
236
+ "rollup.config.mjs",
237
+ "scripts/rollup.config.ts"
238
+ ]);
239
+ builder.addPartial("cli-bundler");
240
+ }
241
+ };
242
+
243
+ // src/features/bundler/vite.ts
244
+ var Vite = class extends FeatureOption {
245
+ constructor() {
246
+ super({ promptHint: "Modern dev server with HMR", promptLabel: "Vite", settingValue: "vite" });
247
+ }
248
+ configure(builder) {
249
+ builder.addPackage("vite").addFiles([
250
+ "vite.config.ts",
251
+ "scripts/vite.config.ts"
252
+ ]);
253
+ builder.addPartial("cli-bundler");
254
+ }
255
+ };
256
+
257
+ // src/features/bundler/webpack.ts
258
+ var Webpack = class extends FeatureOption {
259
+ constructor() {
260
+ super({ promptHint: "Mature bundler with broad plugin support", promptLabel: "Webpack", settingValue: "webpack" });
261
+ }
262
+ configure(builder) {
263
+ builder.addPackage("webpack").addPackage("webpack-cli").addDepcheckIgnore("webpack-cli", "what `npx webpack` in the build script runs.").addPackage("terser-webpack-plugin").addPackage("ts-loader").addDepcheckIgnore("ts-loader", "named as a loader string in `scripts/webpack.config.ts`.").addFiles([
264
+ "webpack.config.ts",
265
+ "scripts/webpack.config.ts"
266
+ ]);
267
+ builder.addPartial("cli-bundler");
268
+ }
269
+ };
270
+
271
+ // src/features/bundler/index.ts
272
+ var BUNDLER_OPTIONS = [new Esbuild(), new Parcel(), new Rollup(), new Vite(), new Webpack()];
273
+ async function promptBundler(savedValue) {
274
+ return promptFeature({ defaultOption: new Esbuild(), message: "Bundler", options: BUNDLER_OPTIONS, savedValue });
275
+ }
276
+
277
+ // src/features/git-hooks.ts
278
+ function addHusky(builder) {
279
+ builder.addPackage("husky").addDepcheckIgnore("husky", "run as a CLI by `scripts/prepare.ts`; the hooks in `.husky/` are its output.").addScript("prepare").addFiles(["scripts/prepare.ts"]);
280
+ }
281
+ function addStagedFilesHook(builder) {
282
+ addHusky(builder);
283
+ builder.addPackage("nano-staged").addDepcheckIgnore("nano-staged", "`.husky/pre-commit` runs it; configured by `scripts/nano-staged-config.ts`.").addFiles([
284
+ ".husky/pre-commit",
285
+ ".nano-staged.mjs",
286
+ "scripts/nano-staged-config.ts"
287
+ ]);
288
+ }
289
+
290
+ // src/features/commit-linting/conventional-commits.ts
291
+ var ConventionalCommits = class extends FeatureOption {
292
+ constructor() {
293
+ super({ promptHint: "commitlint + a husky commit-msg hook", promptLabel: "Conventional Commits", settingValue: "conventional-commits" });
294
+ }
295
+ configure(builder) {
296
+ addHusky(builder);
297
+ builder.addPackage("@commitlint/cli").addDepcheckIgnore("@commitlint/cli", "`.husky/commit-msg` runs it as the `commitlint` binary.").addPackage("@commitlint/config-conventional").addDepcheckIgnore("@commitlint/config-conventional", "extended by name (a string in the `extends` of `scripts/commitlint-config.ts`), never imported.").addPackage("@commitlint/types").addPackage("czg").addDepcheckIgnore("czg", "run as a CLI by `scripts/commit.ts`.").addScript("commit").addFiles([
298
+ "commitlint.config.ts",
299
+ "scripts/commit.ts",
300
+ "scripts/commitlint-config.ts",
301
+ ".husky/commit-msg"
302
+ ]);
303
+ }
304
+ };
305
+
306
+ // src/features/commit-linting/none.ts
307
+ var None = class extends FeatureOption {
308
+ constructor() {
309
+ super({ promptHint: "No commit linting", promptLabel: "(none)", settingValue: "none" });
310
+ }
311
+ };
312
+
313
+ // src/features/commit-linting/index.ts
314
+ var COMMIT_LINTING_OPTIONS = [new None(), new ConventionalCommits()];
315
+ async function promptCommitLinting(savedValue) {
316
+ return promptFeature({ defaultOption: new ConventionalCommits(), message: "Commit linting", options: COMMIT_LINTING_OPTIONS, savedValue });
317
+ }
318
+
319
+ // src/features/coverage-badge/coverage-badge.ts
320
+ var CoverageBadge = class extends FeatureOption {
321
+ constructor() {
322
+ super({ promptHint: 'A "coverage: 100%" README badge', promptLabel: "Coverage badge", settingValue: "coverage-badge" });
323
+ }
324
+ configure(builder, answers) {
325
+ builder.addBadge(
326
+ `[![Coverage: 100%](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/${answers.authorGitHubName}/obsidian-${answers.pluginId})`
327
+ );
328
+ }
329
+ };
330
+
331
+ // src/features/coverage-badge/none.ts
332
+ var None2 = class extends FeatureOption {
333
+ constructor() {
334
+ super({ promptHint: "No coverage badge", promptLabel: "(none)", settingValue: "none" });
335
+ }
336
+ };
337
+
338
+ // src/features/coverage-badge/index.ts
339
+ var COVERAGE_BADGE_OPTIONS = [new None2(), new CoverageBadge()];
340
+ async function promptCoverageBadge(savedValue) {
341
+ return promptFeature({ defaultOption: new None2(), message: "Coverage badge", options: COVERAGE_BADGE_OPTIONS, savedValue });
342
+ }
343
+
344
+ // src/features/e2e-test-runner/none.ts
345
+ var None3 = class extends FeatureOption {
346
+ constructor() {
347
+ super({ promptHint: "No E2E testing", promptLabel: "(none)", settingValue: "none" });
348
+ }
349
+ };
350
+
351
+ // src/features/e2e-test-runner/obsidian-test.ts
352
+ var ObsidianTest = class extends FeatureOption {
353
+ constructor() {
354
+ super({ promptHint: "Tests run inside Obsidian with real app APIs", promptLabel: "obsidian-test", settingValue: "obsidian-test" });
355
+ }
356
+ configure(builder) {
357
+ builder.addPackage("obsidian-test").addScript("test:e2e").addFiles([
358
+ "e2e/sample.spec.ts",
359
+ "scripts/test-e2e.ts"
360
+ ]).addPartial("has-e2e");
361
+ }
362
+ };
363
+
364
+ // src/features/e2e-test-runner/wdio-obsidian.ts
365
+ var WdioObsidian = class extends FeatureOption {
366
+ constructor() {
367
+ super({ promptHint: "WebdriverIO service for Obsidian, multi-version & CI/CD", promptLabel: "wdio-obsidian", settingValue: "wdio-obsidian" });
368
+ }
369
+ configure(builder) {
370
+ builder.addPackage("@wdio/cli").addDepcheckIgnore("@wdio/cli", "run as the `wdio` CLI by `scripts/test-e2e.ts`.").addPackage("@wdio/globals").addPackage("@wdio/mocha-framework").addPackage("expect-webdriverio").addPackage("wdio-obsidian-service").addScript("test:e2e").addFiles([
371
+ "wdio.conf.ts",
372
+ "e2e/sample.spec.ts",
373
+ "scripts/test-e2e.ts"
374
+ ]).addPartial("has-e2e");
375
+ }
376
+ };
377
+
378
+ // src/features/e2e-test-runner/index.ts
379
+ var E2E_TEST_RUNNER_OPTIONS = [new None3(), new ObsidianTest(), new WdioObsidian()];
380
+ async function promptE2eTestRunner(savedValue) {
381
+ return promptFeature({ defaultOption: new None3(), message: "E2E testing", options: E2E_TEST_RUNNER_OPTIONS, savedValue });
382
+ }
383
+
384
+ // src/features/editor-extensions/code-mirror.ts
385
+ var CodeMirror = class extends FeatureOption {
386
+ constructor() {
387
+ super({ promptHint: "CodeMirror 6 state fields, view plugins, decorations", promptLabel: "CodeMirror", settingValue: "codemirror" });
388
+ }
389
+ configure(builder) {
390
+ builder.addPackage("@codemirror/state").addPackage("@codemirror/view").addFiles([
391
+ "src/editor-extensions/sample-state-field.ts",
392
+ "src/editor-extensions/sample-view-plugin.ts",
393
+ "src/editor-extensions/sample-widget.ts",
394
+ "src/editor-suggests/sample-editor-suggest.ts"
395
+ ]);
396
+ }
397
+ };
398
+
399
+ // src/features/editor-extensions/none.ts
400
+ var None4 = class extends FeatureOption {
401
+ constructor() {
402
+ super({ promptHint: "No editor extensions", promptLabel: "(none)", settingValue: "none" });
403
+ }
404
+ };
405
+
406
+ // src/features/editor-extensions/index.ts
407
+ var EDITOR_EXTENSIONS_OPTIONS = [new None4(), new CodeMirror()];
408
+ async function promptEditorExtensions(savedValue) {
409
+ return promptFeature({ defaultOption: new None4(), message: "Editor extensions", options: EDITOR_EXTENSIONS_OPTIONS, savedValue });
410
+ }
411
+
412
+ // src/features/formatter/biome.ts
413
+ var Biome = class extends FeatureOption {
414
+ constructor() {
415
+ super({ partialName: "biome-formatter", promptHint: "Fast formatter (pairs with Biome linter)", promptLabel: "Biome", settingValue: "biome" });
416
+ }
417
+ configure(builder, answers) {
418
+ if (answers.linter !== "biome") {
419
+ builder.addLintStagedCommand("*.{ts,tsx,mts}", "biome format --write --no-errors-on-unmatched");
420
+ }
421
+ builder.addPackage("@biomejs/biome").addDepcheckIgnore("@biomejs/biome", "run as the `biome` CLI by the format and lint scripts, configured by `biome.jsonc`.").addScript("format").addScript("format:check").addFiles([
422
+ "biome.jsonc",
423
+ "scripts/format.ts",
424
+ "scripts/format-check.ts"
425
+ ]);
426
+ }
427
+ };
428
+
429
+ // src/features/formatter/dprint.ts
430
+ var Dprint = class extends FeatureOption {
431
+ constructor() {
432
+ super({ promptHint: "Fast, pluggable, written in Rust", promptLabel: "dprint", settingValue: "dprint" });
433
+ }
434
+ configure(builder) {
435
+ builder.addLintStagedCommand("*.{ts,tsx,mts}", "dprint fmt").addPackage("dprint").addDepcheckIgnore("dprint", "run as a CLI by the format scripts, configured by `dprint.json`.").addScript("format").addScript("format:check").addFiles([
436
+ "dprint.json",
437
+ "scripts/format.ts",
438
+ "scripts/format-check.ts"
439
+ ]);
440
+ }
441
+ };
442
+
443
+ // src/features/formatter/none.ts
444
+ var None5 = class extends FeatureOption {
445
+ constructor() {
446
+ super({ promptHint: "No formatting", promptLabel: "(none)", settingValue: "none" });
447
+ }
448
+ };
449
+
450
+ // src/features/formatter/prettier.ts
451
+ var Prettier = class extends FeatureOption {
452
+ constructor() {
453
+ super({ promptHint: "Opinionated, widely adopted", promptLabel: "Prettier", settingValue: "prettier" });
454
+ }
455
+ configure(builder) {
456
+ builder.addLintStagedCommand("*.{ts,tsx,mts}", "prettier --write").addPackage("prettier").addScript("format").addScript("format:check").addFiles([
457
+ ".prettierignore",
458
+ "prettier.config.mjs",
459
+ "scripts/prettier.config.ts",
460
+ "scripts/format.ts",
461
+ "scripts/format-check.ts"
462
+ ]);
463
+ }
464
+ };
465
+
466
+ // src/features/formatter/index.ts
467
+ var FORMATTER_OPTIONS = [new None5(), new Biome(), new Dprint(), new Prettier()];
468
+ var FORMATTERS_NEEDING_INITIAL_FORMAT = /* @__PURE__ */ new Set(["biome", "prettier"]);
469
+ function needsInitialFormat(formatter) {
470
+ return FORMATTERS_NEEDING_INITIAL_FORMAT.has(formatter);
471
+ }
472
+ async function promptFormatter(savedValue) {
473
+ return promptFeature({ defaultOption: new Dprint(), message: "Formatter", options: FORMATTER_OPTIONS, savedValue });
474
+ }
475
+
476
+ // src/features/funding-platform/funding-platform-option.ts
477
+ var FundingPlatformOption = class extends FeatureOption {
478
+ badgeColor;
479
+ badgeLabel;
480
+ badgeLogo;
481
+ badgeLogoColor;
482
+ fundingYmlKey;
483
+ constructor(config) {
484
+ super({ promptHint: config.promptHint, promptLabel: config.promptLabel, settingValue: config.settingValue });
485
+ this.badgeColor = config.badgeColor;
486
+ this.badgeLabel = config.badgeLabel;
487
+ this.badgeLogo = config.badgeLogo;
488
+ this.badgeLogoColor = config.badgeLogoColor;
489
+ this.fundingYmlKey = config.fundingYmlKey;
490
+ }
491
+ /** The shields.io badge, as one markdown image link to {@link getUrl}. */
492
+ getBadge(answers) {
493
+ const label = encodeURIComponent(this.badgeLabel.replaceAll("-", "--").replaceAll("_", "__"));
494
+ let query = "";
495
+ if (this.badgeLogo !== void 0) {
496
+ query = `?logo=${this.badgeLogo}`;
497
+ if (this.badgeLogoColor !== void 0) {
498
+ query += `&logoColor=${this.badgeLogoColor}`;
499
+ }
500
+ }
501
+ return `[![${this.badgeLabel}](https://img.shields.io/badge/${label}-${this.badgeColor}${query})](${this.getUrl(answers)})`;
502
+ }
503
+ /** The value written after `<fundingYmlKey>: ` in `FUNDING.yml`. */
504
+ getFundingYmlValue(answers) {
505
+ return answers.fundingUsername;
506
+ }
507
+ };
508
+
509
+ // src/features/funding-platform/buy-me-a-coffee.ts
510
+ var BuyMeACoffee = class extends FundingPlatformOption {
511
+ constructor() {
512
+ super({
513
+ badgeColor: "ffdd00",
514
+ badgeLabel: "Buy Me a Coffee",
515
+ badgeLogo: "buy-me-a-coffee",
516
+ badgeLogoColor: "black",
517
+ fundingYmlKey: "buy_me_a_coffee",
518
+ promptHint: "buymeacoffee.com/<username>",
519
+ promptLabel: "Buy Me a Coffee",
520
+ settingValue: "buy-me-a-coffee"
521
+ });
522
+ }
523
+ getUrl(answers) {
524
+ return `https://www.buymeacoffee.com/${answers.fundingUsername}`;
525
+ }
526
+ };
527
+
528
+ // src/features/funding-platform/custom.ts
529
+ var Custom = class extends FundingPlatformOption {
530
+ constructor() {
531
+ super({
532
+ badgeColor: "ff69b4",
533
+ badgeLabel: "Support",
534
+ fundingYmlKey: "custom",
535
+ promptHint: "Any other URL",
536
+ promptLabel: "Custom URL",
537
+ settingValue: "custom"
538
+ });
539
+ }
540
+ /** A one-element flow sequence, single-quoted so a URL's `#` or `: ` cannot end the scalar early. */
541
+ getFundingYmlValue(answers) {
542
+ return `['${answers.fundingUrl.replaceAll("'", "''")}']`;
543
+ }
544
+ getUrl(answers) {
545
+ return answers.fundingUrl;
546
+ }
547
+ };
548
+
549
+ // src/features/funding-platform/github-sponsors.ts
550
+ var GitHubSponsors = class extends FundingPlatformOption {
551
+ constructor() {
552
+ super({
553
+ badgeColor: "ea4aaa",
554
+ badgeLabel: "GitHub Sponsors",
555
+ badgeLogo: "githubsponsors",
556
+ badgeLogoColor: "white",
557
+ fundingYmlKey: "github",
558
+ promptHint: "github.com/sponsors/<username>",
559
+ promptLabel: "GitHub Sponsors",
560
+ settingValue: "github-sponsors"
561
+ });
562
+ }
563
+ getUrl(answers) {
564
+ return `https://github.com/sponsors/${answers.fundingUsername}`;
565
+ }
566
+ };
567
+
568
+ // src/features/funding-platform/ko-fi.ts
569
+ var KoFi = class extends FundingPlatformOption {
570
+ constructor() {
571
+ super({
572
+ badgeColor: "ff5e5b",
573
+ badgeLabel: "Ko-fi",
574
+ badgeLogo: "kofi",
575
+ badgeLogoColor: "white",
576
+ fundingYmlKey: "ko_fi",
577
+ promptHint: "ko-fi.com/<username>",
578
+ promptLabel: "Ko-fi",
579
+ settingValue: "ko-fi"
580
+ });
581
+ }
582
+ getUrl(answers) {
583
+ return `https://ko-fi.com/${answers.fundingUsername}`;
584
+ }
585
+ };
586
+
587
+ // src/features/funding-platform/liberapay.ts
588
+ var Liberapay = class extends FundingPlatformOption {
589
+ constructor() {
590
+ super({
591
+ badgeColor: "f6c915",
592
+ badgeLabel: "Liberapay",
593
+ badgeLogo: "liberapay",
594
+ badgeLogoColor: "black",
595
+ fundingYmlKey: "liberapay",
596
+ promptHint: "liberapay.com/<username>",
597
+ promptLabel: "Liberapay",
598
+ settingValue: "liberapay"
599
+ });
600
+ }
601
+ getUrl(answers) {
602
+ return `https://liberapay.com/${answers.fundingUsername}`;
603
+ }
604
+ };
605
+
606
+ // src/features/funding-platform/none.ts
607
+ var None6 = class extends FundingPlatformOption {
608
+ constructor() {
609
+ super({
610
+ badgeColor: "",
611
+ badgeLabel: "",
612
+ fundingYmlKey: "",
613
+ promptHint: "No funding link, FUNDING.yml or badge",
614
+ promptLabel: "(none)",
615
+ settingValue: "none"
616
+ });
617
+ }
618
+ getUrl() {
619
+ return "";
620
+ }
621
+ };
622
+
623
+ // src/features/funding-platform/open-collective.ts
624
+ var OpenCollective = class extends FundingPlatformOption {
625
+ constructor() {
626
+ super({
627
+ badgeColor: "7fadf2",
628
+ badgeLabel: "Open Collective",
629
+ badgeLogo: "opencollective",
630
+ badgeLogoColor: "white",
631
+ fundingYmlKey: "open_collective",
632
+ promptHint: "opencollective.com/<username>",
633
+ promptLabel: "Open Collective",
634
+ settingValue: "open-collective"
635
+ });
636
+ }
637
+ getUrl(answers) {
638
+ return `https://opencollective.com/${answers.fundingUsername}`;
639
+ }
640
+ };
641
+
642
+ // src/features/funding-platform/patreon.ts
643
+ var Patreon = class extends FundingPlatformOption {
644
+ constructor() {
645
+ super({
646
+ badgeColor: "f96854",
647
+ badgeLabel: "Patreon",
648
+ badgeLogo: "patreon",
649
+ badgeLogoColor: "white",
650
+ fundingYmlKey: "patreon",
651
+ promptHint: "patreon.com/<username>",
652
+ promptLabel: "Patreon",
653
+ settingValue: "patreon"
654
+ });
655
+ }
656
+ getUrl(answers) {
657
+ return `https://www.patreon.com/${answers.fundingUsername}`;
658
+ }
659
+ };
660
+
661
+ // src/features/funding-platform/polar.ts
662
+ var Polar = class extends FundingPlatformOption {
663
+ constructor() {
664
+ super({
665
+ badgeColor: "0062ff",
666
+ badgeLabel: "Polar",
667
+ fundingYmlKey: "polar",
668
+ promptHint: "polar.sh/<username>",
669
+ promptLabel: "Polar",
670
+ settingValue: "polar"
671
+ });
672
+ }
673
+ getUrl(answers) {
674
+ return `https://polar.sh/${answers.fundingUsername}`;
675
+ }
676
+ };
677
+
678
+ // src/features/funding-platform/thanks-dev.ts
679
+ var ThanksDev = class extends FundingPlatformOption {
680
+ constructor() {
681
+ super({
682
+ badgeColor: "000000",
683
+ badgeLabel: "thanks.dev",
684
+ fundingYmlKey: "thanks_dev",
685
+ promptHint: "thanks.dev/<username>, e.g. u/gh/<GitHub username>",
686
+ promptLabel: "thanks.dev",
687
+ settingValue: "thanks-dev"
688
+ });
689
+ }
690
+ getUrl(answers) {
691
+ return `https://thanks.dev/${answers.fundingUsername}`;
692
+ }
693
+ };
694
+
695
+ // src/features/funding-platform/index.ts
696
+ var FUNDING_PLATFORM_OPTIONS = [
697
+ new None6(),
698
+ new BuyMeACoffee(),
699
+ new GitHubSponsors(),
700
+ new KoFi(),
701
+ new Liberapay(),
702
+ new OpenCollective(),
703
+ new Patreon(),
704
+ new Polar(),
705
+ new ThanksDev(),
706
+ new Custom()
707
+ ];
708
+ function asksFundingUsername(platform) {
709
+ return platform !== void 0 && platform !== "none" && platform !== "custom";
710
+ }
711
+ async function promptFundingPlatform(savedValue) {
712
+ return promptFeature({ defaultOption: new BuyMeACoffee(), message: "Funding platform", options: FUNDING_PLATFORM_OPTIONS, savedValue });
713
+ }
714
+ function resolveFunding(answers) {
715
+ const option = FUNDING_PLATFORM_OPTIONS.find((candidate) => candidate.settingValue === answers.fundingPlatform);
716
+ if (!option?.getUrl(answers)) {
717
+ return null;
718
+ }
719
+ return option;
720
+ }
721
+ function validateFundingUsername(value) {
722
+ if (!value) {
723
+ return "Should not be empty";
724
+ }
725
+ if (!/^[\w./-]+$/.test(value)) {
726
+ return "Use only letters, digits, `.`, `_`, `-` and `/`";
727
+ }
728
+ return void 0;
729
+ }
730
+
731
+ // src/features/git-hub-actions/ci-and-release.ts
732
+ var CiAndRelease = class extends FeatureOption {
733
+ constructor() {
734
+ super({ promptHint: "CI + automated release on tag push", promptLabel: "CI + Release", settingValue: "ci-and-release" });
735
+ }
736
+ configure(builder) {
737
+ builder.addFiles([
738
+ ".github/workflows/ci.yml",
739
+ ".github/workflows/release.yml"
740
+ ]);
741
+ }
742
+ };
743
+
744
+ // src/features/git-hub-actions/ci-workflow.ts
745
+ var CiWorkflow = class extends FeatureOption {
746
+ constructor() {
747
+ super({ promptHint: "CI workflow only (lint, test, build)", promptLabel: "CI", settingValue: "ci" });
748
+ }
749
+ configure(builder) {
750
+ builder.addFiles([".github/workflows/ci.yml"]);
751
+ }
752
+ };
753
+
754
+ // src/features/git-hub-actions/none.ts
755
+ var None7 = class extends FeatureOption {
756
+ constructor() {
757
+ super({ promptHint: "No GitHub Actions workflows", promptLabel: "(none)", settingValue: "none" });
758
+ }
759
+ };
760
+
761
+ // src/features/git-hub-actions/index.ts
762
+ var GITHUB_ACTIONS_OPTIONS = [new None7(), new CiAndRelease(), new CiWorkflow()];
763
+ async function promptGitHubActions(savedValue) {
764
+ return promptFeature({ defaultOption: new CiAndRelease(), message: "GitHub Actions", options: GITHUB_ACTIONS_OPTIONS, savedValue });
765
+ }
766
+
767
+ // src/features/git-hub-issue-templates/bug-and-feature.ts
768
+ var BugAndFeature = class extends FeatureOption {
769
+ constructor() {
770
+ super({ promptHint: "Bug report and feature request templates", promptLabel: "Bug & Feature", settingValue: "bug-and-feature" });
771
+ }
772
+ // The real plugins' names, underscores and all. They used to be emitted as `bug-report.yml` and
773
+ // `feature-request.yml`, because any `_` in a basename was read as the partial marker and the render
774
+ // Loop silently skipped both forms; `isPartialTemplatePath` now reads only a kebab-case tail as one.
775
+ configure(builder) {
776
+ builder.addFiles([
777
+ ".github/ISSUE_TEMPLATE/bug_report.yml",
778
+ ".github/ISSUE_TEMPLATE/config.yml",
779
+ ".github/ISSUE_TEMPLATE/feature_request.yml"
780
+ ]);
781
+ }
782
+ };
783
+
784
+ // src/features/git-hub-issue-templates/none.ts
785
+ var None8 = class extends FeatureOption {
786
+ constructor() {
787
+ super({ promptHint: "No issue templates", promptLabel: "(none)", settingValue: "none" });
788
+ }
789
+ };
790
+
791
+ // src/features/git-hub-issue-templates/index.ts
792
+ var GITHUB_ISSUE_TEMPLATES_OPTIONS = [new None8(), new BugAndFeature()];
793
+ async function promptGitHubIssueTemplates(savedValue) {
794
+ return promptFeature({ defaultOption: new BugAndFeature(), message: "GitHub issue templates", options: GITHUB_ISSUE_TEMPLATES_OPTIONS, savedValue });
795
+ }
796
+
797
+ // src/features/hot-reload/hot-reload-plugin.ts
798
+ var HotReloadPlugin = class extends FeatureOption {
799
+ constructor() {
800
+ super({ promptHint: "Creates .hotreload marker for pjeby/hot-reload", promptLabel: "Hot Reload Plugin", settingValue: "hot-reload-plugin" });
801
+ }
802
+ };
803
+
804
+ // src/features/hot-reload/none.ts
805
+ var None9 = class extends FeatureOption {
806
+ constructor() {
807
+ super({ promptHint: "No hot reload support", promptLabel: "(none)", settingValue: "none" });
808
+ }
809
+ };
810
+
811
+ // src/features/hot-reload/obsidian-cli.ts
812
+ var ObsidianCli = class extends FeatureOption {
813
+ constructor() {
814
+ super({ promptHint: "Reloads via obsidian plugin:reload CLI command", promptLabel: "Obsidian CLI", settingValue: "obsidian-cli" });
815
+ }
816
+ };
817
+
818
+ // src/features/hot-reload/index.ts
819
+ var HOT_RELOAD_OPTIONS = [new None9(), new HotReloadPlugin(), new ObsidianCli()];
820
+ async function promptHotReload(savedValue) {
821
+ return promptFeature({ defaultOption: new ObsidianCli(), message: "Hot reload", options: HOT_RELOAD_OPTIONS, savedValue });
822
+ }
823
+
824
+ // src/features/internationalization/i18next.ts
825
+ var I18next = class extends FeatureOption {
826
+ constructor() {
827
+ super({ promptHint: "Popular i18n framework with JSON translations", promptLabel: "i18next", settingValue: "i18next" });
828
+ }
829
+ configure(builder, answers) {
830
+ builder.addPackage("i18next").addFiles([
831
+ "src/i18n/index.ts",
832
+ "src/i18n/locales/en.json"
833
+ ]);
834
+ if (answers.bundler === "rollup") {
835
+ builder.addPackage("@rollup/plugin-json").addPartial("rollup-json");
836
+ }
837
+ }
838
+ };
839
+
840
+ // src/features/internationalization/none.ts
841
+ var None10 = class extends FeatureOption {
842
+ constructor() {
843
+ super({ promptHint: "No internationalization", promptLabel: "(none)", settingValue: "none" });
844
+ }
845
+ };
846
+
847
+ // src/features/internationalization/typesafe-i18n.ts
848
+ var TypesafeI18n = class extends FeatureOption {
849
+ constructor() {
850
+ super({ promptHint: "Type-safe i18n with auto-generated types", promptLabel: "typesafe-i18n", settingValue: "typesafe-i18n" });
851
+ }
852
+ configure(builder) {
853
+ builder.addPackage("typesafe-i18n").addFiles([
854
+ "src/i18n/index.ts",
855
+ "src/i18n/en/index.ts",
856
+ ".typesafe-i18n.json"
857
+ ]);
858
+ }
859
+ };
860
+
861
+ // src/features/internationalization/index.ts
862
+ var INTERNATIONALIZATION_OPTIONS = [new None10(), new I18next(), new TypesafeI18n()];
863
+ async function promptInternationalization(savedValue) {
864
+ return promptFeature({ defaultOption: new None10(), message: "Internationalization", options: INTERNATIONALIZATION_OPTIONS, savedValue });
865
+ }
866
+
867
+ // src/features/linter/biome.ts
868
+ var Biome2 = class extends FeatureOption {
869
+ constructor() {
870
+ super({ partialName: "biome-linter", promptHint: "Fast linter and formatter", promptLabel: "Biome", settingValue: "biome" });
871
+ }
872
+ configure(builder) {
873
+ builder.addLintStagedCommand("*.{ts,tsx,mts}", "biome check --write --no-errors-on-unmatched").addPackage("@biomejs/biome").addDepcheckIgnore("@biomejs/biome", "run as the `biome` CLI by the format and lint scripts, configured by `biome.jsonc`.").addScript("lint").addScript("lint:fix").addFiles([
874
+ "biome.jsonc",
875
+ "scripts/lint.ts",
876
+ "scripts/lint-fix.ts"
877
+ ]);
878
+ }
879
+ };
880
+
881
+ // src/features/linter/eslint.ts
882
+ var Eslint = class extends FeatureOption {
883
+ constructor() {
884
+ super({ promptHint: "Industry standard for JavaScript/TypeScript", promptLabel: "ESLint", settingValue: "eslint" });
885
+ }
886
+ configure(builder, answers) {
887
+ builder.addLintStagedCommand("*.{ts,tsx,mts}", "eslint --fix").addPackage("eslint").addPackage("typescript-eslint").addScript("lint").addScript("lint:fix").addFiles([
888
+ "eslint.config.mts",
889
+ "scripts/lint.ts",
890
+ "scripts/lint-fix.ts"
891
+ ]);
892
+ if (isDevUtilsPreset(answers.preset)) {
893
+ builder.addDepcheckIgnore("typescript-eslint", "imported by nothing here -- the shared config brings its own -- but the `typescript` pin's check reads its `package.json` for the peer range.").addFiles(["scripts/eslint-config.ts"]);
894
+ return;
895
+ }
896
+ builder.addPackage("@eslint/js").addPackage("eslint-plugin-obsidianmd").addPackage("globals");
897
+ }
898
+ };
899
+
900
+ // src/features/linter/none.ts
901
+ var None11 = class extends FeatureOption {
902
+ constructor() {
903
+ super({ promptHint: "No linting", promptLabel: "(none)", settingValue: "none" });
904
+ }
905
+ };
906
+
907
+ // src/features/linter/index.ts
908
+ var LINTER_OPTIONS = [new None11(), new Biome2(), new Eslint()];
909
+ async function promptLinter(savedValue) {
910
+ return promptFeature({ defaultOption: new Eslint(), message: "Linter", options: LINTER_OPTIONS, savedValue });
911
+ }
912
+
913
+ // src/features/markdown-linter/markdownlint.ts
914
+ var Markdownlint = class extends FeatureOption {
915
+ constructor() {
916
+ super({ promptHint: "Lint Markdown files for style and consistency", promptLabel: "markdownlint", settingValue: "markdownlint" });
917
+ }
918
+ configure(builder, answers) {
919
+ builder.addLintStagedCommand("*.md", "markdownlint-cli2 --fix").addPackage("markdownlint-cli2").addDepcheckIgnore("markdownlint-cli2", "run as a CLI by the `lint:md` scripts, configured by `.markdownlint-cli2.mjs`.").addPackage("linkinator").addDepcheckIgnore("linkinator", "run as a CLI by the `lint:md` scripts.").addScript("lint:md").addScript("lint:md:fix").addFiles([
920
+ // `markdownlint-cli2` only discovers a config named `.markdownlint-cli2.{jsonc,yaml,cjs,mjs}`, and
921
+ // So does obsidian-dev-utils' runner -- which copies its own `.mjs` in when it finds none, pointing
922
+ // At a `scripts/markdownlint-cli2-config.ts` that then has to exist. Hence this shape rather than a
923
+ // Single `.mts` that nothing reads.
924
+ ".markdownlint-cli2.mjs",
925
+ "scripts/markdownlint-cli2-config.ts",
926
+ "scripts/lint-md.ts",
927
+ "scripts/lint-md-fix.ts",
928
+ // Read by linkinator from the project root under both presets; it skips the links to the plugin's own
929
+ // Repository, which 404 until the user pushes it.
930
+ "linkinator.config.json"
931
+ ]);
932
+ if (isDevUtilsPreset(answers.preset)) {
933
+ return;
934
+ }
935
+ builder.addPackage("markdownlint").addPackage("markdownlint-rule-relative-links");
936
+ }
937
+ };
938
+
939
+ // src/features/markdown-linter/none.ts
940
+ var None12 = class extends FeatureOption {
941
+ constructor() {
942
+ super({ promptHint: "No Markdown linting", promptLabel: "(none)", settingValue: "none" });
943
+ }
944
+ };
945
+
946
+ // src/features/markdown-linter/index.ts
947
+ var MARKDOWN_LINTER_OPTIONS = [new None12(), new Markdownlint()];
948
+ async function promptMarkdownLinter(savedValue) {
949
+ return promptFeature({ defaultOption: new Markdownlint(), message: "Markdown linter", options: MARKDOWN_LINTER_OPTIONS, savedValue });
950
+ }
951
+
952
+ // src/features/package-manager/bun.ts
953
+ var Bun = class extends FeatureOption {
954
+ constructor() {
955
+ super({ promptHint: "Fast all-in-one JavaScript toolkit", promptLabel: "Bun", settingValue: "bun" });
956
+ }
957
+ };
958
+
959
+ // src/features/package-manager/npm.ts
960
+ var Npm = class extends FeatureOption {
961
+ constructor() {
962
+ super({ promptHint: "Default, comes with Node.js", promptLabel: "npm", settingValue: "npm" });
963
+ }
964
+ };
965
+
966
+ // src/features/package-manager/pnpm.ts
967
+ var Pnpm = class extends FeatureOption {
968
+ constructor() {
969
+ super({ promptHint: "Fast, efficient disk space", promptLabel: "pnpm", settingValue: "pnpm" });
970
+ }
971
+ };
972
+
973
+ // src/features/package-manager/yarn.ts
974
+ var Yarn = class extends FeatureOption {
975
+ constructor() {
976
+ super({ promptHint: "Classic alternative with workspaces", promptLabel: "Yarn", settingValue: "yarn" });
977
+ }
978
+ };
979
+
980
+ // src/features/package-manager/index.ts
981
+ var PACKAGE_MANAGER_OPTIONS = [new Bun(), new Npm(), new Pnpm(), new Yarn()];
982
+ function getInstallCommand(pm) {
983
+ return `${pm} install`;
984
+ }
985
+ function getRunCommand(pm, script) {
986
+ if (pm === "npm") {
987
+ return `npm run ${script}`;
988
+ }
989
+ if (pm === "yarn") {
990
+ return `yarn ${script}`;
991
+ }
992
+ if (pm === "pnpm") {
993
+ return `pnpm ${script}`;
994
+ }
995
+ return `bun run ${script}`;
996
+ }
997
+ async function promptPackageManager(savedValue) {
998
+ return promptFeature({ defaultOption: new Npm(), message: "Package manager", options: PACKAGE_MANAGER_OPTIONS, savedValue });
999
+ }
1000
+
1001
+ // src/features/platform-support/desktop-and-mobile.ts
1002
+ var DesktopAndMobile = class extends FeatureOption {
1003
+ constructor() {
1004
+ super({ promptHint: "Plugin works on desktop and mobile", promptLabel: "Desktop and mobile", settingValue: "desktop-and-mobile" });
1005
+ }
1006
+ };
1007
+
1008
+ // src/features/platform-support/desktop-only.ts
1009
+ var DesktopOnly = class extends FeatureOption {
1010
+ constructor() {
1011
+ super({ promptHint: "Plugin works only on desktop", promptLabel: "Desktop only", settingValue: "desktop-only" });
1012
+ }
1013
+ };
1014
+
1015
+ // src/features/platform-support/index.ts
1016
+ var PLATFORM_SUPPORT_OPTIONS = [new DesktopAndMobile(), new DesktopOnly()];
1017
+ async function promptPlatformSupport(savedValue) {
1018
+ return promptFeature({ defaultOption: new DesktopOnly(), message: "Platform support", options: PLATFORM_SUPPORT_OPTIONS, savedValue });
1019
+ }
1020
+
1021
+ // src/features/preset/demo-vault.ts
1022
+ var DEMO_VAULT_TEMPLATE_FILES = [
1023
+ "demo-vault/.gitignore",
1024
+ "demo-vault/.obsidian/app.json",
1025
+ "demo-vault/.obsidian/community-plugins.json",
1026
+ "demo-vault/00 Start.md",
1027
+ "demo-vault/01 Sample commands.md",
1028
+ "demo-vault/02 Settings.md",
1029
+ "demo-vault/Materials/01 Sample commands/Scratch note.md",
1030
+ "demo-vault/README.md",
1031
+ "demo-vault/_assets/CodeScriptToolkit/demoSetup.ts",
1032
+ "demo-vault/_assets/CodeScriptToolkit/startup.ts"
1033
+ ];
1034
+
1035
+ // src/features/preset/demo.ts
1036
+ var Demo = class extends FeatureOption {
1037
+ constructor() {
1038
+ super({ promptHint: "All features enabled for demonstration", promptLabel: "Demo", settingValue: "demo" });
1039
+ }
1040
+ configure(builder) {
1041
+ builder.addPackage("@tsconfig/strictest").addPackage("moment").addDepcheckIgnore("moment", "imported through `obsidian` rather than directly, and the typings of `obsidian` resolve it by name.").addPackage("obsidian-dev-utils").addPackage("obsidian-integration-testing").addScript("dev").addScript("build").addScript("build:clean").addScript("build:compile").addScript("find-overexposed").addScript("find-overexposed:fix").addScript("version").addScript("gate").addFiles([
1042
+ // Not gated on the gitHubActions answer, and deliberately so: this attests the assets of a
1043
+ // Published RELEASE, and the release flow is the preset's, not CI's. Every plugin compared here
1044
+ // Ships this workflow -- they have no ci.yml at all.
1045
+ ".github/workflows/attest-release-assets.yml",
1046
+ // Not gated on the answers either: the shared ESLint config's `obsidianmd/validate-license` compares
1047
+ // The LICENSE year with the current one, so without this bump every project turns red on 1 January.
1048
+ ".github/workflows/update-license-year.yml",
1049
+ "scripts/build.ts",
1050
+ "scripts/build-clean.ts",
1051
+ "scripts/build-compile.ts",
1052
+ "scripts/find-overexposed.ts",
1053
+ "scripts/find-overexposed-fix.ts",
1054
+ "scripts/dev.ts",
1055
+ "scripts/gate.ts",
1056
+ "scripts/version.ts",
1057
+ "src/modals/sample-form-modal.ts",
1058
+ "src/modals/sample-suggest-modal.ts",
1059
+ "src/plugin-settings.ts",
1060
+ "src/plugin-settings-component.ts",
1061
+ "src/plugin-settings-tab.ts",
1062
+ "src/views/sample-view.ts",
1063
+ ...DEMO_VAULT_TEMPLATE_FILES
1064
+ ]).addPartial("dev-utils");
1065
+ }
1066
+ };
1067
+
1068
+ // src/features/preset/enhanced.ts
1069
+ var Enhanced = class extends FeatureOption {
1070
+ constructor() {
1071
+ super({ promptHint: "Recommended. Uses obsidian-dev-utils for settings, linting, and more", promptLabel: "Enhanced", settingValue: "enhanced" });
1072
+ }
1073
+ configure(builder) {
1074
+ builder.addPackage("@tsconfig/strictest").addPackage("obsidian-dev-utils").addPackage("obsidian-integration-testing").addScript("dev").addScript("build").addScript("build:clean").addScript("build:compile").addScript("find-overexposed").addScript("find-overexposed:fix").addScript("version").addScript("gate").addFiles([
1075
+ // Not gated on the gitHubActions answer, and deliberately so: this attests the assets of a
1076
+ // Published RELEASE, and the release flow is the preset's, not CI's. Every plugin compared here
1077
+ // Ships this workflow -- they have no ci.yml at all.
1078
+ ".github/workflows/attest-release-assets.yml",
1079
+ // Not gated on the answers either: the shared ESLint config's `obsidianmd/validate-license` compares
1080
+ // The LICENSE year with the current one, so without this bump every project turns red on 1 January.
1081
+ ".github/workflows/update-license-year.yml",
1082
+ "scripts/build.ts",
1083
+ "scripts/build-clean.ts",
1084
+ "scripts/build-compile.ts",
1085
+ "scripts/find-overexposed.ts",
1086
+ "scripts/find-overexposed-fix.ts",
1087
+ "scripts/dev.ts",
1088
+ "scripts/gate.ts",
1089
+ "scripts/version.ts",
1090
+ "src/plugin-settings.ts",
1091
+ "src/plugin-settings-component.ts",
1092
+ "src/plugin-settings-tab.ts",
1093
+ ...DEMO_VAULT_TEMPLATE_FILES
1094
+ ]).addPartial("dev-utils");
1095
+ }
1096
+ };
1097
+
1098
+ // src/features/preset/standalone.ts
1099
+ var Standalone = class extends FeatureOption {
1100
+ constructor() {
1101
+ super({ promptHint: "Standalone plugin without obsidian-dev-utils dependency", promptLabel: "Standalone", settingValue: "standalone" });
1102
+ }
1103
+ configure(builder) {
1104
+ builder.addScript("dev").addScript("build").addScript("version").addFiles([
1105
+ "scripts/build.ts",
1106
+ "scripts/dev.ts",
1107
+ "scripts/version.ts",
1108
+ "src/settings.ts"
1109
+ ]);
1110
+ }
1111
+ };
1112
+
1113
+ // src/features/preset/index.ts
1114
+ var PRESET_OPTIONS = [new Demo(), new Enhanced(), new Standalone()];
1115
+ async function promptPreset(savedValue) {
1116
+ return promptFeature({ defaultOption: new Enhanced(), message: "Choose a preset", options: PRESET_OPTIONS, savedValue });
1117
+ }
1118
+
1119
+ // src/features/spell-checker/cspell.ts
1120
+ var Cspell = class extends FeatureOption {
1121
+ constructor() {
1122
+ super({ promptHint: "Configurable spell checker for code", promptLabel: "cspell", settingValue: "cspell" });
1123
+ }
1124
+ configure(builder) {
1125
+ builder.addPackage("cspell").addDepcheckIgnore("cspell", "run as a CLI by `scripts/spellcheck.ts`, configured by `cspell.json`.").addScript("spellcheck").addFiles([
1126
+ "cspell.json",
1127
+ "scripts/spellcheck.ts"
1128
+ ]);
1129
+ }
1130
+ };
1131
+
1132
+ // src/features/spell-checker/none.ts
1133
+ var None13 = class extends FeatureOption {
1134
+ constructor() {
1135
+ super({ promptHint: "No spell checking", promptLabel: "(none)", settingValue: "none" });
1136
+ }
1137
+ };
1138
+
1139
+ // src/features/spell-checker/index.ts
1140
+ var SPELL_CHECKER_OPTIONS = [new None13(), new Cspell()];
1141
+ async function promptSpellChecker(savedValue) {
1142
+ return promptFeature({ defaultOption: new Cspell(), message: "Spell checker", options: SPELL_CHECKER_OPTIONS, savedValue });
1143
+ }
1144
+
1145
+ // src/features/styling/css-modules.ts
1146
+ var CssModules = class extends FeatureOption {
1147
+ constructor() {
1148
+ super({ promptHint: "Scoped CSS with .module.css files", promptLabel: "CSS Modules", settingValue: "css-modules" });
1149
+ }
1150
+ configure(builder, answers) {
1151
+ builder.addFiles([
1152
+ "src/styles/main.module.css",
1153
+ "src/styles/css-modules.d.ts"
1154
+ ]);
1155
+ if (answers.bundler === "rollup") {
1156
+ builder.addPackage("rollup-plugin-postcss").addPartial("rollup-postcss");
1157
+ }
1158
+ if (answers.bundler === "webpack") {
1159
+ builder.addPackage("css-loader").addDepcheckIgnore("css-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPackage("mini-css-extract-plugin").addPartial("webpack-css-extract");
1160
+ }
1161
+ }
1162
+ };
1163
+
1164
+ // src/features/styling/css.ts
1165
+ var Css = class extends FeatureOption {
1166
+ constructor() {
1167
+ super({ promptHint: "Plain CSS styles", promptLabel: "CSS", settingValue: "css" });
1168
+ }
1169
+ configure(builder, answers) {
1170
+ builder.addFiles(["src/styles/main.css", "src/styles/styles.d.ts"]);
1171
+ if (answers.bundler === "rollup") {
1172
+ builder.addPackage("rollup-plugin-postcss").addPartial("rollup-postcss");
1173
+ }
1174
+ if (answers.bundler === "webpack") {
1175
+ builder.addPackage("css-loader").addDepcheckIgnore("css-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPackage("mini-css-extract-plugin").addPartial("webpack-css-extract");
1176
+ }
1177
+ }
1178
+ };
1179
+
1180
+ // src/features/styling/none.ts
1181
+ var None14 = class extends FeatureOption {
1182
+ constructor() {
1183
+ super({ promptHint: "No custom styles", promptLabel: "(none)", settingValue: "none" });
1184
+ }
1185
+ };
1186
+
1187
+ // src/features/styling/post-css.ts
1188
+ var PostCss = class extends FeatureOption {
1189
+ constructor() {
1190
+ super({ promptHint: "CSS with plugins (autoprefixer, nesting)", promptLabel: "PostCSS", settingValue: "postcss" });
1191
+ }
1192
+ configure(builder, answers) {
1193
+ builder.addPackage("autoprefixer").addPackage("postcss").addFiles([
1194
+ "postcss.config.cjs",
1195
+ "scripts/postcss.config.ts",
1196
+ "src/styles/main.css",
1197
+ "src/styles/styles.d.ts"
1198
+ ]);
1199
+ if (answers.bundler === "esbuild") {
1200
+ builder.addPackage("esbuild-postcss");
1201
+ }
1202
+ if (answers.bundler === "rollup") {
1203
+ builder.addPackage("rollup-plugin-postcss").addPartial("rollup-postcss");
1204
+ }
1205
+ if (answers.bundler === "webpack") {
1206
+ builder.addPackage("css-loader").addDepcheckIgnore("css-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPackage("mini-css-extract-plugin").addPackage("postcss-loader").addDepcheckIgnore("postcss-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPartial("webpack-css-extract");
1207
+ }
1208
+ }
1209
+ };
1210
+
1211
+ // src/features/styling/scss.ts
1212
+ var SCSS_PLUGINS = {
1213
+ esbuild: "esbuild-sass-plugin",
1214
+ parcel: "@parcel/transformer-sass",
1215
+ rollup: "rollup-plugin-scss",
1216
+ vite: "sass-embedded",
1217
+ webpack: "sass-loader"
1218
+ };
1219
+ var SCSS_PLUGIN_DEPCHECK_REASONS = {
1220
+ parcel: "resolved by name by `@parcel/config-default` for `.scss`; nothing imports it.",
1221
+ vite: "the Sass compiler vite resolves by name for `.scss`; nothing imports it.",
1222
+ webpack: "named as a loader string in `scripts/webpack.config.ts`."
1223
+ };
1224
+ var Scss = class extends FeatureOption {
1225
+ constructor() {
1226
+ super({ promptHint: "Sass/SCSS preprocessor", promptLabel: "SCSS", settingValue: "scss" });
1227
+ }
1228
+ configure(builder, answers) {
1229
+ builder.addFiles(["src/styles/main.scss", "src/styles/styles.d.ts"]);
1230
+ const plugin = isDevUtilsPreset(answers.preset) && answers.bundler === "esbuild" ? void 0 : SCSS_PLUGINS[answers.bundler];
1231
+ if (plugin) {
1232
+ builder.addPackage(plugin);
1233
+ const depcheckReason = SCSS_PLUGIN_DEPCHECK_REASONS[answers.bundler];
1234
+ if (depcheckReason) {
1235
+ builder.addDepcheckIgnore(plugin, depcheckReason);
1236
+ }
1237
+ }
1238
+ if (answers.bundler === "esbuild") {
1239
+ builder.addPackage("sass-embedded").addDepcheckIgnore("sass-embedded", "the Sass compiler `esbuild-sass-plugin` peers on; nothing imports it directly.");
1240
+ }
1241
+ if (answers.bundler === "webpack") {
1242
+ builder.addPackage("css-loader").addDepcheckIgnore("css-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPackage("mini-css-extract-plugin").addPackage("sass").addDepcheckIgnore("sass", "the Sass compiler `sass-loader` resolves by name; nothing imports it directly.").addPartial("webpack-css-extract");
1243
+ }
1244
+ }
1245
+ };
1246
+
1247
+ // src/features/styling/tailwind.ts
1248
+ var Tailwind = class extends FeatureOption {
1249
+ constructor() {
1250
+ super({ promptHint: "Utility-first CSS framework", promptLabel: "Tailwind CSS", settingValue: "tailwind" });
1251
+ }
1252
+ configure(builder, answers) {
1253
+ builder.addPackage("@tailwindcss/cli").addDepcheckIgnore("@tailwindcss/cli", "run as a CLI by `scripts/build-styles.ts`.").addPackage("tailwindcss").addDepcheckIgnore("tailwindcss", 'imported by `src/styles/main.css` (`@import "tailwindcss"`), which depcheck does not read.').addScript("build:styles").addFiles([
1254
+ "scripts/build-styles.ts",
1255
+ "src/styles/main.css",
1256
+ "src/styles/styles.d.ts"
1257
+ ]);
1258
+ if (answers.bundler === "rollup") {
1259
+ builder.addPackage("rollup-plugin-postcss").addPartial("rollup-postcss");
1260
+ }
1261
+ if (answers.bundler === "webpack") {
1262
+ builder.addPackage("css-loader").addDepcheckIgnore("css-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPackage("mini-css-extract-plugin").addPartial("webpack-css-extract");
1263
+ }
1264
+ }
1265
+ };
1266
+
1267
+ // src/features/styling/index.ts
1268
+ var STYLING_OPTIONS = [new None14(), new Css(), new CssModules(), new PostCss(), new Scss(), new Tailwind()];
1269
+ async function promptStyling(savedValue) {
1270
+ return promptFeature({ defaultOption: new Scss(), message: "Styling", options: STYLING_OPTIONS, savedValue });
1271
+ }
1272
+
1273
+ // src/features/test-runner/sample-unit-tests.ts
1274
+ function addSampleUnitTests(builder, answers) {
1275
+ builder.addFiles(["src/main.test.ts"]);
1276
+ if (!isDevUtilsPreset(answers.preset)) {
1277
+ return;
1278
+ }
1279
+ builder.addFiles(["src/plugin-settings-component.test.ts"]);
1280
+ if (answers.preset === "demo") {
1281
+ builder.addFiles(["src/plugin-settings.test.ts"]);
1282
+ }
1283
+ }
1284
+
1285
+ // src/features/test-runner/jest.ts
1286
+ var Jest = class extends FeatureOption {
1287
+ constructor() {
1288
+ super({ promptHint: "Feature-rich, widely adopted", promptLabel: "Jest", settingValue: "jest" });
1289
+ }
1290
+ configure(builder, answers) {
1291
+ builder.addPackage("@types/jest").addPackage("jest").addPackage("jest-environment-jsdom").addDepcheckIgnore("jest-environment-jsdom", "named by `testEnvironment` in `jest.config.ts`, never imported.").addPackage("obsidian-test-mocks").addPackage("ts-jest").addScript("test").addScript("test:watch").addFiles([
1292
+ "jest.config.ts",
1293
+ "scripts/framework-component-stub.ts",
1294
+ "scripts/stylesheet-stub.ts",
1295
+ "scripts/wasm-module-stub.ts",
1296
+ "src/plugin.test.ts",
1297
+ "scripts/test.ts",
1298
+ "scripts/test-watch.ts"
1299
+ ]);
1300
+ addSampleUnitTests(builder, answers);
1301
+ }
1302
+ };
1303
+
1304
+ // src/features/test-runner/none.ts
1305
+ var None15 = class extends FeatureOption {
1306
+ constructor() {
1307
+ super({ promptHint: "No unit testing", promptLabel: "(none)", settingValue: "none" });
1308
+ }
1309
+ };
1310
+
1311
+ // src/features/test-runner/vitest.ts
1312
+ var Vitest = class extends FeatureOption {
1313
+ constructor() {
1314
+ super({ promptHint: "Fast, Vite-native, ESM-first", promptLabel: "Vitest", settingValue: "vitest" });
1315
+ }
1316
+ configure(builder, answers) {
1317
+ builder.addPackage("jsdom").addPackage("obsidian-test-mocks").addDepcheckIgnore(
1318
+ "obsidian-test-mocks",
1319
+ isDevUtilsPreset(answers.preset) ? "aliased and loaded as a setup file by name by `defineObsidianPluginVitestConfig` from obsidian-dev-utils." : "aliased and loaded as a setup file by name in `vitest.config.ts`."
1320
+ ).addPackage("vitest").addScript("test").addScript("test:watch").addFiles([
1321
+ "vitest.config.ts",
1322
+ "src/plugin.test.ts",
1323
+ "scripts/test.ts",
1324
+ "scripts/test-watch.ts",
1325
+ // Both presets, not just the dev-utils ones: the emitted `src/plugin.ts` reaches `src/wasm/answer.ts`
1326
+ // On every preset when the `wasm` answer is chosen, so every vitest config aliases `.wasm` to
1327
+ // This stub. `framework-component-stub.ts` is dev-utils-only because only those presets' `plugin.ts`
1328
+ // Reaches a single-file component.
1329
+ "scripts/wasm-module-stub.ts",
1330
+ // `src/main.test.ts` imports `src/main.ts`, which imports the stylesheet -- and on `tailwind` that
1331
+ // File does not exist until the first build. See the stub's own comment.
1332
+ "scripts/stylesheet-stub.ts"
1333
+ ]);
1334
+ addSampleUnitTests(builder, answers);
1335
+ if (!isDevUtilsPreset(answers.preset)) {
1336
+ return;
1337
+ }
1338
+ builder.addPackage("@vitest/coverage-v8").addScript("test:coverage").addScript("test:integration").addScript("test:integration:android").addScript("test:integration:demo-vault").addScript("test:integration:desktop").addScript("test:integration:desktop:performance").addScript("test:integration:no-app").addFiles([
1339
+ "scripts/test-integration-android.ts",
1340
+ "scripts/test-integration-desktop.ts",
1341
+ "scripts/test-integration-desktop-performance.ts",
1342
+ "scripts/demo-vault-global-setup.ts",
1343
+ "scripts/framework-component-stub.ts",
1344
+ "scripts/test-coverage.ts",
1345
+ "scripts/test-integration.ts",
1346
+ "scripts/test-integration-demo-vault.ts",
1347
+ "scripts/test-integration-no-app.ts",
1348
+ "scripts/vitest-config.ts",
1349
+ "src/demo-vault-buttons.demo-vault.integration.test.ts",
1350
+ "src/demo-vault.no-app.integration.test.ts"
1351
+ ]);
1352
+ }
1353
+ };
1354
+
1355
+ // src/features/test-runner/index.ts
1356
+ var TEST_RUNNER_OPTIONS = [new None15(), new Jest(), new Vitest()];
1357
+ async function promptTestRunner(savedValue) {
1358
+ return promptFeature({ defaultOption: new Vitest(), message: "Unit testing", options: TEST_RUNNER_OPTIONS, savedValue });
1359
+ }
1360
+
1361
+ // src/features/ui-framework/lit.ts
1362
+ var Lit = class extends FeatureOption {
1363
+ constructor() {
1364
+ super({ promptHint: "Web Components with declarative templates", promptLabel: "Lit", settingValue: "lit" });
1365
+ }
1366
+ configure(builder) {
1367
+ builder.addPackage("lit").addPartial("ui-view").addSentenceCaseBrand("Lit").addFiles([
1368
+ "src/lit-elements/sample-lit-element.ts",
1369
+ "src/views/sample-lit-view.ts"
1370
+ ]);
1371
+ }
1372
+ };
1373
+
1374
+ // src/features/ui-framework/none.ts
1375
+ var None16 = class extends FeatureOption {
1376
+ constructor() {
1377
+ super({ promptHint: "Plain TypeScript, no UI framework", promptLabel: "(none)", settingValue: "none" });
1378
+ }
1379
+ };
1380
+
1381
+ // src/features/ui-framework/preact.ts
1382
+ var BUILD_PLUGINS = {
1383
+ rollup: "@rollup/plugin-babel",
1384
+ vite: "@preact/preset-vite"
1385
+ };
1386
+ var Preact = class extends FeatureOption {
1387
+ constructor() {
1388
+ super({ jsxImportSource: "preact", promptHint: "Lightweight React alternative (3kB)", promptLabel: "Preact", settingValue: "preact" });
1389
+ }
1390
+ configure(builder, answers) {
1391
+ builder.addPackage("preact").addPartial("ui-view").addSentenceCaseBrand("Preact").addFiles([
1392
+ "src/preact-components/sample-preact-component.tsx",
1393
+ "src/views/sample-preact-view.tsx"
1394
+ ]);
1395
+ const plugin = BUILD_PLUGINS[answers.bundler];
1396
+ if (plugin) {
1397
+ builder.addPackage(plugin);
1398
+ }
1399
+ if (answers.bundler === "rollup") {
1400
+ builder.addPackage("@babel/core").addPackage("@babel/plugin-transform-react-jsx").addDepcheckIgnore("@babel/plugin-transform-react-jsx", "named as a string in `scripts/babel.config.ts`, never imported.").addFiles([
1401
+ "babel.config.mjs",
1402
+ "scripts/babel.config.ts"
1403
+ ]).addPartial("rollup-babel");
1404
+ }
1405
+ }
1406
+ };
1407
+
1408
+ // src/features/ui-framework/react.ts
1409
+ var BUILD_PLUGINS2 = {
1410
+ rollup: "@rollup/plugin-babel",
1411
+ vite: "@vitejs/plugin-react"
1412
+ };
1413
+ var React = class extends FeatureOption {
1414
+ constructor() {
1415
+ super({ jsxImportSource: "react", promptHint: "Component-based UI with JSX", promptLabel: "React", settingValue: "react" });
1416
+ }
1417
+ configure(builder, answers) {
1418
+ builder.addPackage("@types/react").addPackage("@types/react-dom").addPackage("react").addPackage("react-dom").addPartial("ui-view").addSentenceCaseBrand("React").addFiles([
1419
+ "src/react-components/sample-react-component.tsx",
1420
+ "src/views/sample-react-view.tsx"
1421
+ ]);
1422
+ const plugin = BUILD_PLUGINS2[answers.bundler];
1423
+ if (plugin) {
1424
+ builder.addPackage(plugin);
1425
+ }
1426
+ if (answers.bundler === "rollup") {
1427
+ builder.addPackage("@babel/core").addPackage("@babel/preset-react").addDepcheckIgnore("@babel/preset-react", "named as a string in `scripts/babel.config.ts`, never imported.").addFiles([
1428
+ "babel.config.mjs",
1429
+ "scripts/babel.config.ts"
1430
+ ]).addPartial("rollup-babel");
1431
+ }
1432
+ }
1433
+ };
1434
+
1435
+ // src/features/ui-framework/solid.ts
1436
+ var BUILD_PLUGINS3 = {
1437
+ rollup: "@rollup/plugin-babel",
1438
+ vite: "vite-plugin-solid"
1439
+ };
1440
+ var Solid = class extends FeatureOption {
1441
+ constructor() {
1442
+ super({ jsxImportSource: "solid-js", promptHint: "Fine-grained reactivity, no virtual DOM", promptLabel: "Solid", settingValue: "solid" });
1443
+ }
1444
+ configure(builder, answers) {
1445
+ builder.addPackage("solid-js").addPartial("ui-view").addSentenceCaseBrand("Solid").addFiles([
1446
+ "src/solid-components/sample-solid-component.tsx",
1447
+ "src/views/sample-solid-view.tsx"
1448
+ ]);
1449
+ const plugin = BUILD_PLUGINS3[answers.bundler];
1450
+ if (plugin) {
1451
+ builder.addPackage(plugin);
1452
+ }
1453
+ if (answers.bundler === "rollup") {
1454
+ builder.addPackage("@babel/core").addPackage("babel-preset-solid").addDepcheckIgnore("babel-preset-solid", "named as a string in `scripts/babel.config.ts`, never imported.").addFiles([
1455
+ "babel.config.mjs",
1456
+ "scripts/babel.config.ts"
1457
+ ]).addPartial("rollup-babel");
1458
+ }
1459
+ if (answers.bundler === "webpack") {
1460
+ builder.addPackage("@babel/core").addDepcheckIgnore("@babel/core", "the peer dependency `babel-loader` loads, never imported.").addPackage("babel-loader").addDepcheckIgnore("babel-loader", "named as a loader string in `scripts/webpack.config.ts`.").addPackage("babel-preset-solid").addDepcheckIgnore("babel-preset-solid", "named as a string in `scripts/webpack.config.ts`, never imported.");
1461
+ }
1462
+ if (answers.bundler === "parcel") {
1463
+ builder.addPackage("babel-preset-solid").addDepcheckIgnore("babel-preset-solid", "named as a string in `babel.config.json`, never imported.").addFiles(["babel.config.json"]);
1464
+ }
1465
+ if (answers.bundler === "esbuild") {
1466
+ builder.addPackage("esbuild-plugin-solid");
1467
+ }
1468
+ }
1469
+ };
1470
+
1471
+ // src/features/ui-framework/svelte.ts
1472
+ var BUILD_PLUGINS4 = {
1473
+ esbuild: "esbuild-svelte",
1474
+ parcel: null,
1475
+ rollup: "rollup-plugin-svelte",
1476
+ vite: "@sveltejs/vite-plugin-svelte",
1477
+ webpack: "svelte-loader"
1478
+ };
1479
+ var SVELTE_PREPROCESS_BUNDLERS = /* @__PURE__ */ new Set(["esbuild", "rollup", "webpack"]);
1480
+ var Svelte = class extends FeatureOption {
1481
+ constructor() {
1482
+ super({ promptHint: "Lightweight reactive components", promptLabel: "Svelte", settingValue: "svelte" });
1483
+ }
1484
+ configure(builder, answers) {
1485
+ builder.addPackage("svelte").addPartial("ui-view").addSentenceCaseBrand("Svelte").addFiles([
1486
+ "src/svelte-components/sample-svelte-component.d.ts",
1487
+ "src/svelte-components/sample-svelte-component.svelte",
1488
+ "src/views/sample-svelte-view.ts"
1489
+ ]);
1490
+ const isDevUtilsEsbuild = isDevUtilsPreset(answers.preset) && answers.bundler === "esbuild";
1491
+ const plugin = getBuildPlugin(answers.bundler);
1492
+ if (plugin !== null && !isDevUtilsEsbuild) {
1493
+ builder.addPackage(plugin);
1494
+ }
1495
+ builder.addPackage("svelte-check").addDepcheckIgnore(
1496
+ "svelte-check",
1497
+ isDevUtilsEsbuild ? "run as a CLI by obsidian-dev-utils' `build:compile`, which refuses to build without it declared." : "run as a CLI by `scripts/build.ts`."
1498
+ );
1499
+ if (!isDevUtilsEsbuild && SVELTE_PREPROCESS_BUNDLERS.has(answers.bundler)) {
1500
+ builder.addPackage("svelte-preprocess");
1501
+ }
1502
+ if (answers.bundler === "webpack") {
1503
+ builder.addDepcheckIgnore("svelte-loader", "named as a loader string in `scripts/webpack.config.ts`.");
1504
+ }
1505
+ if (answers.bundler === "parcel") {
1506
+ builder.addFiles(["parcel-transformer-svelte.cjs"]);
1507
+ }
1508
+ }
1509
+ };
1510
+ function getBuildPlugin(bundler) {
1511
+ if (!Object.hasOwn(BUILD_PLUGINS4, bundler)) {
1512
+ throw new Error(`Unsupported bundler for Svelte: ${bundler}`);
1513
+ }
1514
+ return BUILD_PLUGINS4[bundler] ?? null;
1515
+ }
1516
+
1517
+ // src/features/ui-framework/vue.ts
1518
+ var BUILD_PLUGINS5 = {
1519
+ esbuild: "esbuild-plugin-vue3",
1520
+ parcel: "@parcel/transformer-vue",
1521
+ rollup: "rollup-plugin-vue",
1522
+ vite: "@vitejs/plugin-vue",
1523
+ webpack: "vue-loader"
1524
+ };
1525
+ var Vue = class extends FeatureOption {
1526
+ constructor() {
1527
+ super({ promptHint: "Progressive framework with SFC", promptLabel: "Vue", settingValue: "vue" });
1528
+ }
1529
+ configure(builder, answers) {
1530
+ builder.addPackage("vue").addPackage(getBuildPlugin2(answers.bundler)).addPartial("ui-view").addSentenceCaseBrand("Vue").addFiles([
1531
+ "src/vue-components/sample-vue-component.d.ts",
1532
+ "src/vue-components/sample-vue-component.vue",
1533
+ "src/views/sample-vue-view.ts"
1534
+ ]);
1535
+ if (answers.bundler === "rollup") {
1536
+ builder.addPackage("@babel/core").addPackage("@babel/preset-typescript").addPackage("@rollup/plugin-babel").addDepcheckIgnore("@babel/preset-typescript", "named as a string in `scripts/rollup.config.ts`, never imported.").addPartial("rollup-babel");
1537
+ }
1538
+ if (answers.bundler === "parcel") {
1539
+ builder.addDepcheckIgnore("@parcel/transformer-vue", "resolved by name by `@parcel/config-default` for `.vue`; nothing imports it.");
1540
+ }
1541
+ }
1542
+ };
1543
+ function getBuildPlugin2(bundler) {
1544
+ const plugin = BUILD_PLUGINS5[bundler];
1545
+ if (!plugin) {
1546
+ throw new Error(`Unsupported bundler for Vue: ${bundler}`);
1547
+ }
1548
+ return plugin;
1549
+ }
1550
+
1551
+ // src/features/ui-framework/index.ts
1552
+ var UI_FRAMEWORK_OPTIONS = [new None16(), new Lit(), new Preact(), new React(), new Solid(), new Svelte(), new Vue()];
1553
+ async function promptUiFramework(savedValue) {
1554
+ return promptFeature({ defaultOption: new None16(), message: "UI framework", options: UI_FRAMEWORK_OPTIONS, savedValue });
1555
+ }
1556
+
1557
+ // src/features/wasm-support/none.ts
1558
+ var None17 = class extends FeatureOption {
1559
+ constructor() {
1560
+ super({ promptHint: "No WASM", promptLabel: "(none)", settingValue: "none" });
1561
+ }
1562
+ };
1563
+
1564
+ // src/features/wasm-support/wasm.ts
1565
+ var WASM_PLUGINS = {
1566
+ esbuild: null,
1567
+ parcel: null,
1568
+ rollup: "@rollup/plugin-wasm",
1569
+ vite: null,
1570
+ webpack: null
1571
+ };
1572
+ var Wasm = class extends FeatureOption {
1573
+ constructor() {
1574
+ super({ promptHint: "Import and use .wasm modules", promptLabel: "WASM", settingValue: "wasm" });
1575
+ }
1576
+ configure(builder, answers) {
1577
+ const plugin = getWasmPlugin(answers.bundler);
1578
+ if (plugin !== null) {
1579
+ builder.addPackage(plugin);
1580
+ }
1581
+ builder.addSentenceCaseBrand("WASM");
1582
+ builder.addFiles([
1583
+ // The one binary in the template tree, copied verbatim (see `ASSET_EXTENSIONS`). 39 bytes, one
1584
+ // Export, `answer()`, returning 42 -- the sample module from CodeScript Toolkit's demo vault.
1585
+ "src/wasm/module.wasm",
1586
+ // Its source in WebAssembly text format. Documentation only: nothing in the build reads it, and it
1587
+ // Is here so the binary beside it is not opaque.
1588
+ "src/wasm/module.wat",
1589
+ "src/wasm.d.ts",
1590
+ "src/wasm/answer.ts",
1591
+ "src/wasm/sample-command.ts",
1592
+ "src/wasm/README.md"
1593
+ ]);
1594
+ if (isDevUtilsPreset(answers.preset)) {
1595
+ builder.addFiles(["demo-vault/03 WebAssembly.md"]);
1596
+ }
1597
+ }
1598
+ };
1599
+ function getWasmPlugin(bundler) {
1600
+ if (!Object.hasOwn(WASM_PLUGINS, bundler)) {
1601
+ throw new Error(`Unsupported bundler for WASM: ${bundler}`);
1602
+ }
1603
+ return WASM_PLUGINS[bundler] ?? null;
1604
+ }
1605
+
1606
+ // src/features/wasm-support/index.ts
1607
+ var WASM_SUPPORT_OPTIONS = [new None17(), new Wasm()];
1608
+ async function promptWasmSupport(savedValue) {
1609
+ return promptFeature({ defaultOption: new None17(), message: "WebAssembly support", options: WASM_SUPPORT_OPTIONS, savedValue });
1610
+ }
1611
+
1612
+ // src/answer-space.ts
1613
+ var SAMPLE_FUNDING_URL = "https://example.com/support-testuser";
1614
+ var SAMPLE_OBSIDIAN_CONFIG_FOLDER = "demo-vault/.obsidian";
1615
+ var ANSWER_SPACE = [
1616
+ toDimension("apiSubset", API_SUBSET_OPTIONS),
1617
+ toDimension("bundler", BUNDLER_OPTIONS),
1618
+ toDimension("commitLinting", COMMIT_LINTING_OPTIONS),
1619
+ toDimension("coverageBadge", COVERAGE_BADGE_OPTIONS),
1620
+ toDimension("e2eTestRunner", E2E_TEST_RUNNER_OPTIONS),
1621
+ toDimension("editorExtensions", EDITOR_EXTENSIONS_OPTIONS),
1622
+ toDimension("formatter", FORMATTER_OPTIONS),
1623
+ toDimension("fundingPlatform", FUNDING_PLATFORM_OPTIONS),
1624
+ toValueDimension("fundingUrl", ["", SAMPLE_FUNDING_URL]),
1625
+ toDimension("gitHubActions", GITHUB_ACTIONS_OPTIONS),
1626
+ toDimension("gitHubIssueTemplates", GITHUB_ISSUE_TEMPLATES_OPTIONS),
1627
+ toDimension("hotReload", HOT_RELOAD_OPTIONS),
1628
+ toDimension("internationalization", INTERNATIONALIZATION_OPTIONS),
1629
+ toDimension("linter", LINTER_OPTIONS),
1630
+ toDimension("markdownLinter", MARKDOWN_LINTER_OPTIONS),
1631
+ toValueDimension("obsidianConfigFolder", ["", SAMPLE_OBSIDIAN_CONFIG_FOLDER]),
1632
+ toDimension("packageManager", PACKAGE_MANAGER_OPTIONS),
1633
+ toDimension("platformSupport", PLATFORM_SUPPORT_OPTIONS),
1634
+ toDimension("preset", PRESET_OPTIONS),
1635
+ toDimension("spellChecker", SPELL_CHECKER_OPTIONS),
1636
+ toDimension("styling", STYLING_OPTIONS),
1637
+ toDimension("testRunner", TEST_RUNNER_OPTIONS),
1638
+ toDimension("uiFramework", UI_FRAMEWORK_OPTIONS),
1639
+ toDimension("wasmSupport", WASM_SUPPORT_OPTIONS)
1640
+ ];
1641
+ var ANSWER_SPACE_SIZE = ANSWER_SPACE.reduce((size, dimension) => size * dimension.values.length, 1);
1642
+ var ANSWER_SPACE_DIMENSION_SIZES = ANSWER_SPACE.map((dimension) => dimension.values.length);
1643
+ function toDimension(answerKey, options) {
1644
+ return {
1645
+ answerKey,
1646
+ values: options.map((option) => option.settingValue)
1647
+ };
1648
+ }
1649
+ function toValueDimension(answerKey, values) {
1650
+ return { answerKey, values };
1651
+ }
1652
+
1653
+ // src/answers.ts
1654
+ var CONFIG_FILE_NAME = ".create-obsidian-plugin.json";
1655
+ function getPluginShortName(pluginId) {
1656
+ return pluginId.split("-").map((word) => (word[0] ?? "").toUpperCase() + word.slice(1)).join("");
1657
+ }
1658
+
1659
+ // src/directory-constraints.ts
1660
+ var CORE_FEATURE_NAMES = [
1661
+ "Audio recorder",
1662
+ "Backlinks",
1663
+ "Bases",
1664
+ "Bookmarks",
1665
+ "Canvas",
1666
+ "Command palette",
1667
+ "Daily notes",
1668
+ "File recovery",
1669
+ "Files",
1670
+ "Footnotes",
1671
+ "Format converter",
1672
+ "Graph view",
1673
+ "Live Preview",
1674
+ "Note composer",
1675
+ "Outgoing links",
1676
+ "Outline",
1677
+ "Page preview",
1678
+ "Properties view",
1679
+ "Publish",
1680
+ "Quick Switcher",
1681
+ "Random note",
1682
+ "Reading view",
1683
+ "Search",
1684
+ "Slash commands",
1685
+ "Slides",
1686
+ "Source mode",
1687
+ "Sync",
1688
+ "Tags view",
1689
+ "Templates",
1690
+ "Unique note creator",
1691
+ "Web viewer",
1692
+ "Word count",
1693
+ "Workspaces"
1694
+ ];
1695
+ var MAX_DESCRIPTION_LENGTH = 250;
1696
+ var MIN_DESCRIPTION_LENGTH = 10;
1697
+ var ALLOWED_ID_CHARACTERS = /^[a-z0-9-]+$/;
1698
+ var ALLOWED_NAME_CHARACTERS = /^[A-Za-z0-9 \-+()]+$/;
1699
+ var EMOJI = /\p{Extended_Pictographic}/u;
1700
+ var OBSIDIAN_WORD = /\bobsidian\b/i;
1701
+ var OBSIDIAN_NAME_VARIANTS = /obsi|sidian/i;
1702
+ var SELF_REFERENCE_PATTERNS = [
1703
+ /\bthis plugin\b/i,
1704
+ /\bthis is a plugin\b/i,
1705
+ /\ba plugin (?:that|which)\b/i,
1706
+ /^the plugin\b/i,
1707
+ /^plugin\b/i
1708
+ ];
1709
+ function findUnlistableManifestAnswers(answers) {
1710
+ const checked = [
1711
+ { key: "pluginId", message: validatePluginId(answers.pluginId), value: answers.pluginId },
1712
+ { key: "pluginName", message: validatePluginName(answers.pluginName), value: answers.pluginName },
1713
+ { key: "pluginDescription", message: validatePluginDescription(answers.pluginDescription), value: answers.pluginDescription }
1714
+ ];
1715
+ const problems = [];
1716
+ for (const { key, message, value } of checked) {
1717
+ if (message !== void 0) {
1718
+ problems.push({ key, message, value });
1719
+ }
1720
+ }
1721
+ return problems;
1722
+ }
1723
+ function validatePluginDescription(input) {
1724
+ if (!input) {
1725
+ return "Should not be empty";
1726
+ }
1727
+ if (EMOJI.test(input)) {
1728
+ return "Should not contain emoji -- the Community directory rejects them, though it does accept ordinary punctuation such as backticks, em dashes and parentheses";
1729
+ }
1730
+ if (OBSIDIAN_WORD.test(input)) {
1731
+ return 'Should not contain the word "Obsidian" -- the Community directory rejects it as implied by the plugin directory, even though no Obsidian document says so';
1732
+ }
1733
+ if (SELF_REFERENCE_PATTERNS.some((pattern) => pattern.test(input))) {
1734
+ return 'Should say what the plugin does rather than refer to itself -- the Community directory rejects "This plugin..." and "a plugin that...". Naming another plugin is fine';
1735
+ }
1736
+ if (input.length < MIN_DESCRIPTION_LENGTH) {
1737
+ return `Should be at least ${String(MIN_DESCRIPTION_LENGTH)} characters, so it says what the plugin does`;
1738
+ }
1739
+ if (input.length > MAX_DESCRIPTION_LENGTH) {
1740
+ return `Should be at most ${String(MAX_DESCRIPTION_LENGTH)} characters -- the Community directory rejects a longer one`;
1741
+ }
1742
+ if (!/^[A-Z]/.test(input)) {
1743
+ return 'Should start with a capital letter, ideally on an action verb: "Translates selected text...", "Imports notes from..."';
1744
+ }
1745
+ if (!input.endsWith(".")) {
1746
+ return "Should end with a dot";
1747
+ }
1748
+ return void 0;
1749
+ }
1750
+ function validatePluginId(input) {
1751
+ if (!input) {
1752
+ return "Should not be empty";
1753
+ }
1754
+ if (!ALLOWED_ID_CHARACTERS.test(input)) {
1755
+ return "Should contain only lowercase English letters, digits and hyphens";
1756
+ }
1757
+ if (!/^[a-z]/.test(input)) {
1758
+ return "Should start with a letter";
1759
+ }
1760
+ if (!/[a-z0-9]$/.test(input)) {
1761
+ return "Should end with a letter or digit";
1762
+ }
1763
+ if (input.includes("obsidian")) {
1764
+ return 'Should not contain "obsidian" anywhere -- the Community directory rejects it as implied by the plugin directory, and an id can never be changed once the plugin is published';
1765
+ }
1766
+ if (input.endsWith("plugin")) {
1767
+ return 'Should not end with "plugin" -- the Community directory rejects it as implied, and an id can never be changed once the plugin is published';
1768
+ }
1769
+ return void 0;
1770
+ }
1771
+ function validatePluginName(input) {
1772
+ if (!input) {
1773
+ return "Should not be empty";
1774
+ }
1775
+ if (OBSIDIAN_NAME_VARIANTS.test(input)) {
1776
+ return 'Should not contain "Obsidian" or an abbreviation of it ("Obsi", "sidian") -- the Community directory rejects it as implied by the plugin directory';
1777
+ }
1778
+ if (input.toLowerCase().includes("plugin")) {
1779
+ return 'Should not contain "Plugin" -- the Community directory rejects it as implied by the plugin directory';
1780
+ }
1781
+ const coreName = findCoreFeatureName(input);
1782
+ if (coreName !== void 0) {
1783
+ return `Should not be the name of an Obsidian core plugin or core feature -- "${coreName}" is one, and the Community directory rejects a name that takes it`;
1784
+ }
1785
+ if (!ALLOWED_NAME_CHARACTERS.test(input)) {
1786
+ return "Should use Basic Latin letters and digits only, with hyphens, plus signs and parentheses the only punctuation the Community directory accepts";
1787
+ }
1788
+ return void 0;
1789
+ }
1790
+ function findCoreFeatureName(name) {
1791
+ const normalized = name.trim().toLowerCase();
1792
+ return CORE_FEATURE_NAMES.find((candidate) => candidate.toLowerCase() === normalized);
1793
+ }
1794
+
1795
+ // src/prompts.ts
1796
+ import {
1797
+ cancel as cancel2,
1798
+ log
1799
+ } from "@clack/prompts";
1800
+ import { styleText as styleText3 } from "node:util";
1801
+
1802
+ // src/clack-text.ts
1803
+ import {
1804
+ settings as settings2,
1805
+ TextPrompt
1806
+ } from "@clack/core";
1807
+ import {
1808
+ S_BAR as S_BAR2,
1809
+ S_BAR_END as S_BAR_END2,
1810
+ symbol as symbol2
1811
+ } from "@clack/prompts";
1812
+ import { styleText as styleText2 } from "node:util";
1813
+ var TextPromptWithCompletion = class extends TextPrompt {
1814
+ completionValue;
1815
+ constructor(opts) {
1816
+ super(opts);
1817
+ this.completionValue = opts.defaultValue ?? opts.placeholder ?? "";
1818
+ }
1819
+ _isActionKey(char, key) {
1820
+ if (this.shouldComplete(key)) {
1821
+ this._clearUserInput();
1822
+ this._setUserInput(this.completionValue, true);
1823
+ return false;
1824
+ }
1825
+ return super._isActionKey(char, key);
1826
+ }
1827
+ shouldComplete(key) {
1828
+ if (this.userInput || !this.completionValue) {
1829
+ return false;
1830
+ }
1831
+ return key.name === "tab" || key.name === "right" || key.name === "end";
1832
+ }
1833
+ };
1834
+ async function text(opts) {
1835
+ const textOpts = {
1836
+ render() {
1837
+ const withGuide = settings2.withGuide;
1838
+ const header = `${withGuide ? `${styleText2("gray", S_BAR2)}
1839
+ ` : ""}${symbol2(this.state)} ${opts.message}
1840
+ `;
1841
+ const placeholderDisplay = opts.placeholder ? styleText2("inverse", opts.placeholder[0] ?? "") + styleText2("dim", opts.placeholder.slice(1)) : styleText2(["inverse", "hidden"], "_");
1842
+ const input = this.userInput ? this.userInputWithCursor : placeholderDisplay;
1843
+ const val = this.value ?? "";
1844
+ switch (this.state) {
1845
+ case "cancel": {
1846
+ const strikeVal = val ? ` ${styleText2(["strikethrough", "dim"], val)}` : "";
1847
+ const bar = withGuide ? styleText2("gray", S_BAR2) : "";
1848
+ return `${header}${bar}${strikeVal}${val.trim() ? `
1849
+ ${bar}` : ""}`;
1850
+ }
1851
+ case "error": {
1852
+ const errorMsg = this.error ? ` ${styleText2("yellow", this.error)}` : "";
1853
+ const bar = withGuide ? `${styleText2("yellow", S_BAR2)} ` : "";
1854
+ const barEnd = withGuide ? styleText2("yellow", S_BAR_END2) : "";
1855
+ return `${header.trim()}
1856
+ ${bar}${input}
1857
+ ${barEnd}${errorMsg}
1858
+ `;
1859
+ }
1860
+ case "submit": {
1861
+ const dimVal = val ? ` ${styleText2("dim", val)}` : "";
1862
+ const bar = withGuide ? styleText2("gray", S_BAR2) : "";
1863
+ return `${header}${bar}${dimVal}`;
1864
+ }
1865
+ default: {
1866
+ const bar = withGuide ? `${styleText2("cyan", S_BAR2)} ` : "";
1867
+ const barEnd = withGuide ? styleText2("cyan", S_BAR_END2) : "";
1868
+ return `${header}${bar}${input}
1869
+ ${barEnd}
1870
+ `;
1871
+ }
1872
+ }
1873
+ },
1874
+ validate: opts.validate
1875
+ };
1876
+ if (opts.defaultValue !== void 0) {
1877
+ textOpts.defaultValue = opts.defaultValue;
1878
+ }
1879
+ if (opts.placeholder !== void 0) {
1880
+ textOpts.placeholder = opts.placeholder;
1881
+ }
1882
+ const prompt = new TextPromptWithCompletion(textOpts);
1883
+ prompt.on("key", (_char, key) => {
1884
+ if (key.name === "escape") {
1885
+ setEscapeCancel();
1886
+ }
1887
+ });
1888
+ resetCancelFlag();
1889
+ const result = await prompt.prompt();
1890
+ return throwGoBackOnCancel(result) ?? "";
1891
+ }
1892
+
1893
+ // src/prompts.ts
1894
+ var DEFAULT_PLUGIN_ID = "my-awesome-helper";
1895
+ function getDefaultAnswers(defaults) {
1896
+ const pluginId = defaults?.pluginId ?? DEFAULT_PLUGIN_ID;
1897
+ const base = getDefaultAnswersBase(pluginId);
1898
+ if (!defaults) {
1899
+ return base;
1900
+ }
1901
+ const overrides = { fundingUsername: defaults.authorGitHubName ?? base.fundingUsername };
1902
+ for (const [key, value] of Object.entries(defaults)) {
1903
+ if (value !== void 0) {
1904
+ overrides[key] = value;
1905
+ }
1906
+ }
1907
+ return { ...base, ...overrides };
1908
+ }
1909
+ function makePluginName(pluginId) {
1910
+ return extractWords(pluginId).join(" ");
1911
+ }
1912
+ async function promptAnswers(defaults) {
1913
+ showHotkeyHints();
1914
+ const defaultTooling = getDefaultTooling();
1915
+ const steps = skipSuppliedAnswers(buildPromptSteps(defaults ?? {}, defaultTooling), defaults ?? {});
1916
+ const answers = await runPromptSteps(steps);
1917
+ return buildAnswers(answers, defaultTooling);
1918
+ }
1919
+ function validateNotEmpty(value) {
1920
+ if (!value) {
1921
+ return "Should not be empty";
1922
+ }
1923
+ return void 0;
1924
+ }
1925
+ function buildAnswers(answers, defaultTooling) {
1926
+ function get(key, fallback) {
1927
+ return answers.get(key) ?? fallback;
1928
+ }
1929
+ const pluginId = get("pluginId", DEFAULT_PLUGIN_ID);
1930
+ return {
1931
+ apiSubset: get("apiSubset", defaultTooling.apiSubset),
1932
+ authorGitHubName: get("authorGitHubName", "johndoe"),
1933
+ authorName: get("authorName", "John Doe"),
1934
+ bundler: get("bundler", "esbuild"),
1935
+ commitLinting: get("commitLinting", defaultTooling.commitLinting),
1936
+ coverageBadge: get("coverageBadge", defaultTooling.coverageBadge),
1937
+ currentYear: (/* @__PURE__ */ new Date()).getFullYear(),
1938
+ defaultBranch: get("defaultBranch", "main"),
1939
+ e2eTestRunner: get("e2eTestRunner", defaultTooling.e2eTestRunner),
1940
+ editorExtensions: get("editorExtensions", defaultTooling.editorExtensions),
1941
+ formatter: get("formatter", defaultTooling.formatter),
1942
+ fundingPlatform: get("fundingPlatform", "buy-me-a-coffee"),
1943
+ fundingUrl: get("fundingUrl", ""),
1944
+ fundingUsername: get("fundingUsername", get("authorGitHubName", "johndoe")),
1945
+ gitHubActions: get("gitHubActions", defaultTooling.gitHubActions),
1946
+ gitHubIssueTemplates: get("gitHubIssueTemplates", defaultTooling.gitHubIssueTemplates),
1947
+ hotReload: get("hotReload", defaultTooling.hotReload),
1948
+ internationalization: get("internationalization", defaultTooling.internationalization),
1949
+ linter: get("linter", defaultTooling.linter),
1950
+ markdownLinter: get("markdownLinter", defaultTooling.markdownLinter),
1951
+ obsidianConfigFolder: get("obsidianConfigFolder", ""),
1952
+ packageManager: get("packageManager", "npm"),
1953
+ platformSupport: get("platformSupport", "desktop-only"),
1954
+ pluginDescription: get("pluginDescription", "Does something awesome."),
1955
+ pluginId,
1956
+ pluginName: get("pluginName", makePluginName(pluginId)),
1957
+ preset: get("preset", "enhanced"),
1958
+ spellChecker: get("spellChecker", defaultTooling.spellChecker),
1959
+ styling: get("styling", defaultTooling.styling),
1960
+ testRunner: get("testRunner", defaultTooling.testRunner),
1961
+ uiFramework: get("uiFramework", "none"),
1962
+ wasmSupport: get("wasmSupport", defaultTooling.wasmSupport)
1963
+ };
1964
+ }
1965
+ function buildPromptSteps(d, defaultTooling) {
1966
+ function isCustomize(answers) {
1967
+ return answers.get("toolingMode") === "customize";
1968
+ }
1969
+ function skipUnlessCustomize(answers) {
1970
+ return !isCustomize(answers);
1971
+ }
1972
+ return [
1973
+ {
1974
+ defaultValue: () => d.preset ?? "enhanced",
1975
+ key: "preset",
1976
+ prompt: (saved) => promptPreset(saved)
1977
+ },
1978
+ {
1979
+ defaultValue: () => "defaults",
1980
+ key: "toolingMode",
1981
+ prompt: () => promptToolingMode(),
1982
+ skip: (answers) => answers.get("preset") === "demo"
1983
+ },
1984
+ {
1985
+ defaultValue: (answers) => answers.get("preset") === "demo" ? "esbuild" : d.bundler ?? "esbuild",
1986
+ key: "bundler",
1987
+ prompt: (saved) => promptBundler(saved),
1988
+ skip: skipUnlessCustomize
1989
+ },
1990
+ {
1991
+ defaultValue: (answers) => answers.get("preset") === "demo" ? "none" : d.uiFramework ?? "none",
1992
+ key: "uiFramework",
1993
+ prompt: (saved) => promptUiFramework(saved),
1994
+ skip: skipUnlessCustomize
1995
+ },
1996
+ {
1997
+ defaultValue: () => d.linter ?? defaultTooling.linter,
1998
+ key: "linter",
1999
+ prompt: (saved) => promptLinter(saved),
2000
+ skip: skipUnlessCustomize
2001
+ },
2002
+ {
2003
+ defaultValue: () => d.formatter ?? defaultTooling.formatter,
2004
+ key: "formatter",
2005
+ prompt: (saved) => promptFormatter(saved),
2006
+ skip: skipUnlessCustomize
2007
+ },
2008
+ {
2009
+ defaultValue: () => d.spellChecker ?? defaultTooling.spellChecker,
2010
+ key: "spellChecker",
2011
+ prompt: (saved) => promptSpellChecker(saved),
2012
+ skip: skipUnlessCustomize
2013
+ },
2014
+ {
2015
+ defaultValue: () => d.markdownLinter ?? defaultTooling.markdownLinter,
2016
+ key: "markdownLinter",
2017
+ prompt: (saved) => promptMarkdownLinter(saved),
2018
+ skip: skipUnlessCustomize
2019
+ },
2020
+ {
2021
+ defaultValue: () => d.testRunner ?? defaultTooling.testRunner,
2022
+ key: "testRunner",
2023
+ prompt: (saved) => promptTestRunner(saved),
2024
+ skip: skipUnlessCustomize
2025
+ },
2026
+ {
2027
+ defaultValue: () => d.e2eTestRunner ?? defaultTooling.e2eTestRunner,
2028
+ key: "e2eTestRunner",
2029
+ prompt: (saved) => promptE2eTestRunner(saved),
2030
+ skip: skipUnlessCustomize
2031
+ },
2032
+ {
2033
+ defaultValue: () => d.editorExtensions ?? defaultTooling.editorExtensions,
2034
+ key: "editorExtensions",
2035
+ prompt: (saved) => promptEditorExtensions(saved),
2036
+ skip: skipUnlessCustomize
2037
+ },
2038
+ {
2039
+ defaultValue: () => d.styling ?? defaultTooling.styling,
2040
+ key: "styling",
2041
+ prompt: (saved) => promptStyling(saved),
2042
+ skip: skipUnlessCustomize
2043
+ },
2044
+ {
2045
+ defaultValue: () => d.wasmSupport ?? defaultTooling.wasmSupport,
2046
+ key: "wasmSupport",
2047
+ prompt: (saved) => promptWasmSupport(saved),
2048
+ skip: skipUnlessCustomize
2049
+ },
2050
+ {
2051
+ defaultValue: () => d.commitLinting ?? defaultTooling.commitLinting,
2052
+ key: "commitLinting",
2053
+ prompt: (saved) => promptCommitLinting(saved),
2054
+ skip: skipUnlessCustomize
2055
+ },
2056
+ {
2057
+ defaultValue: () => d.hotReload ?? defaultTooling.hotReload,
2058
+ key: "hotReload",
2059
+ prompt: (saved) => promptHotReload(saved),
2060
+ skip: skipUnlessCustomize
2061
+ },
2062
+ {
2063
+ defaultValue: () => d.internationalization ?? defaultTooling.internationalization,
2064
+ key: "internationalization",
2065
+ prompt: (saved) => promptInternationalization(saved),
2066
+ skip: skipUnlessCustomize
2067
+ },
2068
+ {
2069
+ defaultValue: () => d.gitHubActions ?? defaultTooling.gitHubActions,
2070
+ key: "gitHubActions",
2071
+ prompt: (saved) => promptGitHubActions(saved),
2072
+ skip: skipUnlessCustomize
2073
+ },
2074
+ {
2075
+ defaultValue: () => d.gitHubIssueTemplates ?? defaultTooling.gitHubIssueTemplates,
2076
+ key: "gitHubIssueTemplates",
2077
+ prompt: (saved) => promptGitHubIssueTemplates(saved),
2078
+ skip: skipUnlessCustomize
2079
+ },
2080
+ {
2081
+ defaultValue: () => d.coverageBadge ?? defaultTooling.coverageBadge,
2082
+ key: "coverageBadge",
2083
+ prompt: (saved) => promptCoverageBadge(saved),
2084
+ skip: skipUnlessCustomize
2085
+ },
2086
+ {
2087
+ defaultValue: () => d.apiSubset ?? defaultTooling.apiSubset,
2088
+ key: "apiSubset",
2089
+ prompt: (saved) => promptApiSubset(saved),
2090
+ skip: (answers) => !(isCustomize(answers) && answers.get("preset") === "enhanced")
2091
+ },
2092
+ {
2093
+ defaultValue: () => d.packageManager ?? "npm",
2094
+ key: "packageManager",
2095
+ prompt: (saved) => promptPackageManager(saved),
2096
+ skip: skipUnlessCustomize
2097
+ },
2098
+ {
2099
+ defaultValue: () => d.platformSupport ?? "desktop-only",
2100
+ key: "platformSupport",
2101
+ prompt: (saved) => promptPlatformSupport(saved),
2102
+ skip: skipUnlessCustomize
2103
+ },
2104
+ {
2105
+ defaultValue: () => d.pluginId ?? "",
2106
+ key: "pluginId",
2107
+ prompt: (saved) => text({
2108
+ defaultValue: saved || void 0,
2109
+ message: "Plugin id (lowercase, hyphens allowed)",
2110
+ placeholder: saved || DEFAULT_PLUGIN_ID,
2111
+ validate: validatePluginId
2112
+ })
2113
+ },
2114
+ {
2115
+ defaultValue: (answers) => d.pluginName ?? makePluginName(answers.get("pluginId") ?? DEFAULT_PLUGIN_ID),
2116
+ key: "pluginName",
2117
+ prompt: (saved) => promptPluginName(saved)
2118
+ },
2119
+ {
2120
+ defaultValue: () => d.pluginDescription ?? "Does something awesome.",
2121
+ key: "pluginDescription",
2122
+ prompt: (saved) => promptPluginDescription(saved)
2123
+ },
2124
+ {
2125
+ defaultValue: () => d.authorName ?? "John Doe",
2126
+ key: "authorName",
2127
+ prompt: (saved) => text({
2128
+ defaultValue: saved,
2129
+ message: "Your full name",
2130
+ placeholder: saved,
2131
+ validate: validateNotEmpty
2132
+ })
2133
+ },
2134
+ {
2135
+ defaultValue: () => d.authorGitHubName ?? "johndoe",
2136
+ key: "authorGitHubName",
2137
+ prompt: (saved) => text({
2138
+ defaultValue: saved,
2139
+ message: "Your GitHub username",
2140
+ placeholder: saved,
2141
+ validate: validateNotEmpty
2142
+ })
2143
+ },
2144
+ {
2145
+ defaultValue: () => d.defaultBranch ?? "main",
2146
+ key: "defaultBranch",
2147
+ prompt: (saved) => text({
2148
+ defaultValue: saved,
2149
+ message: "Default branch name. `git init` creates it, and the CI workflow triggers on it.",
2150
+ placeholder: saved,
2151
+ validate: validateNotEmpty
2152
+ })
2153
+ },
2154
+ // Asked outside Customize: it is a fact about the author, not a tooling choice, and choosing `none`
2155
+ // Here is the only way the recommended-defaults path has to opt out of funding altogether.
2156
+ {
2157
+ defaultValue: () => d.fundingPlatform ?? "buy-me-a-coffee",
2158
+ key: "fundingPlatform",
2159
+ prompt: (saved) => promptFundingPlatform(saved)
2160
+ },
2161
+ {
2162
+ defaultValue: (answers) => d.fundingUsername ?? answers.get("authorGitHubName") ?? "johndoe",
2163
+ key: "fundingUsername",
2164
+ prompt: (saved) => text({
2165
+ defaultValue: saved,
2166
+ message: "Your username on that funding platform",
2167
+ placeholder: saved,
2168
+ validate: validateFundingUsername
2169
+ }),
2170
+ skip: (answers) => !asksFundingUsername(answers.get("fundingPlatform"))
2171
+ },
2172
+ {
2173
+ defaultValue: () => d.fundingUrl ?? "",
2174
+ key: "fundingUrl",
2175
+ prompt: (saved) => text({
2176
+ defaultValue: saved || void 0,
2177
+ message: "Funding URL",
2178
+ placeholder: saved || "https://example.com/support-me",
2179
+ validate: validateNotEmpty
2180
+ }),
2181
+ skip: (answers) => answers.get("fundingPlatform") !== "custom"
2182
+ },
2183
+ {
2184
+ defaultValue: () => d.obsidianConfigFolder ?? "",
2185
+ key: "obsidianConfigFolder",
2186
+ prompt: (saved) => text({
2187
+ defaultValue: saved || void 0,
2188
+ message: "Path to test vault config folder (.obsidian, by default, but it is configurable in Obsidian settings). This enables auto-update of your plugin in your test vault during dev. Leave empty if you prefer to update the plugin manually.",
2189
+ placeholder: saved || "path/to/test-vault/.obsidian"
2190
+ })
2191
+ }
2192
+ ];
2193
+ }
2194
+ function extractWords(pluginId) {
2195
+ return pluginId.split("-").map((w) => (w[0] ?? "").toUpperCase() + w.slice(1));
2196
+ }
2197
+ function getDefaultAnswersBase(pluginId) {
2198
+ return {
2199
+ ...getDefaultTooling(),
2200
+ authorGitHubName: "johndoe",
2201
+ authorName: "John Doe",
2202
+ bundler: "esbuild",
2203
+ currentYear: (/* @__PURE__ */ new Date()).getFullYear(),
2204
+ defaultBranch: "main",
2205
+ fundingPlatform: "buy-me-a-coffee",
2206
+ fundingUrl: "",
2207
+ fundingUsername: "johndoe",
2208
+ obsidianConfigFolder: "",
2209
+ packageManager: "npm",
2210
+ platformSupport: "desktop-only",
2211
+ pluginDescription: "Does something awesome.",
2212
+ pluginId,
2213
+ pluginName: makePluginName(pluginId),
2214
+ preset: "enhanced",
2215
+ uiFramework: "none"
2216
+ };
2217
+ }
2218
+ function getDefaultTooling() {
2219
+ return {
2220
+ apiSubset: "official",
2221
+ commitLinting: "conventional-commits",
2222
+ coverageBadge: "none",
2223
+ e2eTestRunner: "none",
2224
+ editorExtensions: "none",
2225
+ formatter: "dprint",
2226
+ gitHubActions: "ci-and-release",
2227
+ gitHubIssueTemplates: "bug-and-feature",
2228
+ hotReload: "obsidian-cli",
2229
+ internationalization: "none",
2230
+ linter: "eslint",
2231
+ markdownLinter: "markdownlint",
2232
+ spellChecker: "cspell",
2233
+ styling: "scss",
2234
+ testRunner: "vitest",
2235
+ wasmSupport: "none"
2236
+ };
2237
+ }
2238
+ function promptPluginDescription(saved) {
2239
+ const value = saved || void 0;
2240
+ return text({
2241
+ defaultValue: value,
2242
+ message: "Plugin description",
2243
+ placeholder: value ?? "Does something awesome.",
2244
+ validate: validatePluginDescription
2245
+ });
2246
+ }
2247
+ function promptPluginName(saved) {
2248
+ const value = saved || void 0;
2249
+ return text({
2250
+ defaultValue: value,
2251
+ message: "Plugin display name",
2252
+ placeholder: value ?? makePluginName(DEFAULT_PLUGIN_ID),
2253
+ validate: validatePluginName
2254
+ });
2255
+ }
2256
+ async function promptToolingMode() {
2257
+ return select({
2258
+ initialValue: "defaults",
2259
+ message: "Tooling",
2260
+ options: [
2261
+ { hint: "Choose each tool individually", label: "Customize", value: "customize" },
2262
+ { hint: "ESLint, dprint, SCSS, Vitest, and more", label: "Use recommended defaults", value: "defaults" }
2263
+ ]
2264
+ });
2265
+ }
2266
+ async function runPromptSteps(steps) {
2267
+ const answers = /* @__PURE__ */ new Map();
2268
+ let i = 0;
2269
+ while (i < steps.length) {
2270
+ const step = steps[i];
2271
+ if (step.skip?.(answers)) {
2272
+ answers.set(step.key, step.defaultValue(answers));
2273
+ i++;
2274
+ continue;
2275
+ }
2276
+ try {
2277
+ const savedValue = answers.get(step.key) ?? step.defaultValue(answers);
2278
+ answers.set(step.key, await step.prompt(savedValue));
2279
+ i++;
2280
+ } catch (error) {
2281
+ if (!(error instanceof GoBackError)) {
2282
+ throw error;
2283
+ }
2284
+ answers.delete(step.key);
2285
+ i--;
2286
+ while (i > 0 && steps[i]?.skip?.(answers)) {
2287
+ const skippedStep = steps[i];
2288
+ answers.delete(skippedStep.key);
2289
+ i--;
2290
+ }
2291
+ if (i < 0) {
2292
+ cancel2("Operation cancelled.");
2293
+ process.exit(0);
2294
+ }
2295
+ }
2296
+ }
2297
+ return answers;
2298
+ }
2299
+ function showHotkeyHints() {
2300
+ function dim(s) {
2301
+ return styleText3("dim", s);
2302
+ }
2303
+ function key(s) {
2304
+ return styleText3("cyan", s);
2305
+ }
2306
+ log.info([
2307
+ `${key("\u2191\u2193")} Navigate ${key("Enter")} Confirm ${key("Esc")} Go back`,
2308
+ `${dim("Text inputs:")} ${key("Tab")}/${key("\u2192")}/${key("End")} Accept suggestion`
2309
+ ].join("\n"));
2310
+ }
2311
+ function skipSuppliedAnswers(steps, supplied) {
2312
+ return steps.map((step) => ({
2313
+ ...step,
2314
+ skip: (answers) => supplied[step.key] !== void 0 || (step.skip?.(answers) ?? false)
2315
+ }));
2316
+ }
2317
+
2318
+ // src/cli-args.ts
2319
+ var COMPUTED_ANSWER_KEYS = ["currentYear"];
2320
+ var DERIVED_TEMPLATE_KEYS = ["pluginShortName"];
2321
+ var REFUSED_KEYS = [...COMPUTED_ANSWER_KEYS, ...DERIVED_TEMPLATE_KEYS];
2322
+ var ANSWERS_FILE_PREFIX = "--answersFile=";
2323
+ var CUSTOM_TEMPLATE_PREFIX = "--customTemplate=";
2324
+ var MODE_PREFIX = "--mode=";
2325
+ var MODES = ["create" /* Create */, "update" /* Update */];
2326
+ var FLAG_PREFIX_LENGTH = 2;
2327
+ var FREE_TEXT_VALIDATORS = {
2328
+ authorGitHubName: validateNotEmpty,
2329
+ authorName: validateNotEmpty,
2330
+ defaultBranch: validateNotEmpty,
2331
+ fundingUsername: validateFundingUsername,
2332
+ pluginDescription: validatePluginDescription,
2333
+ pluginId: validatePluginId,
2334
+ pluginName: validatePluginName
2335
+ };
2336
+ var DEMO_PINNED_ANSWER_KEYS = ["bundler", "uiFramework"];
2337
+ function assertValidAnswer(key, value) {
2338
+ const dimension = findChoiceDimension(key);
2339
+ if (dimension) {
2340
+ if (!dimension.values.includes(value)) {
2341
+ throw new Error(`"${value}" is not an answer to ${key}. It accepts: ${dimension.values.join(", ")}.`);
2342
+ }
2343
+ return;
2344
+ }
2345
+ const message = FREE_TEXT_VALIDATORS[key]?.(value);
2346
+ if (message !== void 0) {
2347
+ throw new Error(`"${value}" is not a valid ${key}. ${message}.`);
2348
+ }
2349
+ }
2350
+ function getAnswerableKeys() {
2351
+ return Object.keys(getDefaultAnswers()).filter((key) => !COMPUTED_ANSWER_KEYS.includes(key)).sort();
2352
+ }
2353
+ function getHelpText() {
2354
+ const lines = [
2355
+ "Usage: npm create @mnaoumov/obsidian-plugin [-- <options>]",
2356
+ "",
2357
+ "Options:",
2358
+ " -y, --yes Take every default and skip the post-scaffold prompts. Never asks:",
2359
+ " a question no flag answers stops the run and names the flag.",
2360
+ " -h, --help Show this help.",
2361
+ ` --mode=${MODES.join("|")} Create a new plugin, or update the project in the current`,
2362
+ " directory, without being asked which.",
2363
+ " --force Scaffold into an obsidian-<pluginId> directory that already exists.",
2364
+ " --answersFile=<path> Read answers from a JSON file. Accepts a bare answers object or a",
2365
+ " `.create-obsidian-plugin.json`. Individual flags override it.",
2366
+ " --customTemplate=<dir> Layer your own templates over the built-in ones: a directory",
2367
+ " mirroring templates/default, plus an overlay.json. Recorded in the",
2368
+ " project and re-applied on update; pass it empty to drop it.",
2369
+ "",
2370
+ "Answers (any of these may be given as --<key>=<value>):"
2371
+ ];
2372
+ for (const key of getAnswerableKeys()) {
2373
+ const dimension = findChoiceDimension(key);
2374
+ lines.push(dimension ? ` --${key}=${dimension.values.join("|")}` : ` --${key}=<text>`);
2375
+ }
2376
+ return lines.join("\n");
2377
+ }
2378
+ function parseCliArgs(argv) {
2379
+ const flagAnswers = {};
2380
+ let answersFilePath = null;
2381
+ let customTemplate;
2382
+ let force = false;
2383
+ let mode;
2384
+ let showHelp = false;
2385
+ let useDefaults = false;
2386
+ for (const argument of argv) {
2387
+ if (argument === "--yes" || argument === "-y") {
2388
+ useDefaults = true;
2389
+ continue;
2390
+ }
2391
+ if (argument === "--help" || argument === "-h") {
2392
+ showHelp = true;
2393
+ continue;
2394
+ }
2395
+ if (argument.startsWith(ANSWERS_FILE_PREFIX)) {
2396
+ answersFilePath = argument.slice(ANSWERS_FILE_PREFIX.length);
2397
+ continue;
2398
+ }
2399
+ if (argument.startsWith(CUSTOM_TEMPLATE_PREFIX)) {
2400
+ customTemplate = argument.slice(CUSTOM_TEMPLATE_PREFIX.length);
2401
+ continue;
2402
+ }
2403
+ if (argument === "--force") {
2404
+ force = true;
2405
+ continue;
2406
+ }
2407
+ if (argument.startsWith(MODE_PREFIX)) {
2408
+ mode = toMode(argument.slice(MODE_PREFIX.length));
2409
+ continue;
2410
+ }
2411
+ const [rawKey, value] = splitFlag(argument);
2412
+ const key = toAnswerKey(rawKey);
2413
+ assertValidAnswer(key, value);
2414
+ flagAnswers[key] = value;
2415
+ }
2416
+ const fileAnswers = answersFilePath === null ? {} : readAnswersFile(answersFilePath);
2417
+ const answers = { ...fileAnswers, ...flagAnswers };
2418
+ assertNoDemoConflict(answers);
2419
+ return {
2420
+ answers,
2421
+ customTemplate,
2422
+ force,
2423
+ mode,
2424
+ showHelp,
2425
+ useDefaults
2426
+ };
2427
+ }
2428
+ function toAnswerKey(key) {
2429
+ if (REFUSED_KEYS.includes(key)) {
2430
+ throw new Error(`${key} is computed by the generator, not answered, so it cannot be set.`);
2431
+ }
2432
+ const answerable = getAnswerableKeys();
2433
+ if (!answerable.includes(key)) {
2434
+ throw new Error(`Unknown answer "${key}". The answers are: ${answerable.join(", ")}.`);
2435
+ }
2436
+ return key;
2437
+ }
2438
+ function assertNoDemoConflict(answers) {
2439
+ if (answers.preset !== "demo") {
2440
+ return;
2441
+ }
2442
+ for (const key of DEMO_PINNED_ANSWER_KEYS) {
2443
+ if (answers[key] !== void 0) {
2444
+ throw new Error(`preset=demo pins ${key}, so the two cannot be given together. Drop --${key}, or choose another preset.`);
2445
+ }
2446
+ }
2447
+ }
2448
+ function findChoiceDimension(key) {
2449
+ const dimension = ANSWER_SPACE.find((candidate) => candidate.answerKey === key);
2450
+ if (!dimension || dimension.values.includes("")) {
2451
+ return null;
2452
+ }
2453
+ return dimension;
2454
+ }
2455
+ function readAnswersFile(path) {
2456
+ if (!existsSync(path)) {
2457
+ throw new Error(`Answers file not found: ${path}`);
2458
+ }
2459
+ let parsed;
2460
+ try {
2461
+ parsed = JSON.parse(readFileSync(path, "utf-8"));
2462
+ } catch {
2463
+ throw new Error(`Answers file is not valid JSON: ${path}`);
2464
+ }
2465
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2466
+ throw new Error(`Answers file should contain a JSON object: ${path}`);
2467
+ }
2468
+ const record = parsed;
2469
+ const wrapped = record["answers"];
2470
+ const source = typeof wrapped === "object" && wrapped !== null ? wrapped : record;
2471
+ const answers = {};
2472
+ for (const [rawKey, value] of Object.entries(source)) {
2473
+ if (REFUSED_KEYS.includes(rawKey)) {
2474
+ continue;
2475
+ }
2476
+ if (typeof value !== "string") {
2477
+ throw new Error(`${rawKey} in ${path} should be a string, got ${typeof value}.`);
2478
+ }
2479
+ const key = toAnswerKey(rawKey);
2480
+ assertValidAnswer(key, value);
2481
+ answers[key] = value;
2482
+ }
2483
+ return answers;
2484
+ }
2485
+ function splitFlag(argument) {
2486
+ if (!argument.startsWith("--")) {
2487
+ throw new Error(`Expected --<answer>=<value>, got "${argument}".`);
2488
+ }
2489
+ const separatorIndex = argument.indexOf("=");
2490
+ if (separatorIndex === -1) {
2491
+ throw new Error(`Expected --<answer>=<value>, got "${argument}".`);
2492
+ }
2493
+ return [argument.slice(FLAG_PREFIX_LENGTH, separatorIndex), argument.slice(separatorIndex + 1)];
2494
+ }
2495
+ function toMode(value) {
2496
+ if (!MODES.includes(value)) {
2497
+ throw new Error(`"${value}" is not a mode. It accepts: ${MODES.join(", ")}.`);
2498
+ }
2499
+ return value;
2500
+ }
2501
+
2502
+ // src/answers-export.ts
2503
+ var CREATE_COMMANDS = {
2504
+ bun: { prefix: "bun create @mnaoumov/obsidian-plugin", separator: "" },
2505
+ npm: { prefix: "npm create @mnaoumov/obsidian-plugin", separator: " --" },
2506
+ pnpm: { prefix: "pnpm create @mnaoumov/obsidian-plugin", separator: "" },
2507
+ yarn: { prefix: "yarn create @mnaoumov/obsidian-plugin", separator: "" }
2508
+ };
2509
+ var JSON_INDENT_SPACES = 2;
2510
+ function formatAnswersJson(answers) {
2511
+ const exported = {};
2512
+ for (const key of getExportableKeys(answers)) {
2513
+ exported[key] = String(answers[key]);
2514
+ }
2515
+ return `${JSON.stringify(exported, null, JSON_INDENT_SPACES)}
2516
+ `;
2517
+ }
2518
+ function formatCreateCommand(answers, shell, customTemplate) {
2519
+ const create = CREATE_COMMANDS[answers.packageManager] ?? CREATE_COMMANDS["npm"];
2520
+ const head = `${create?.prefix ?? ""}${create?.separator ?? ""} --yes --mode=create`;
2521
+ const flags = getExportableKeys(answers).map((key) => `--${key}=${quote(String(answers[key]), shell)}`);
2522
+ if (customTemplate) {
2523
+ flags.push(`--customTemplate=${quote(customTemplate, shell)}`);
2524
+ }
2525
+ if (shell === "cmd") {
2526
+ return [head, ...flags].join(" ");
2527
+ }
2528
+ return [head, ...flags].join(" \\\n ");
2529
+ }
2530
+ function formatCreateScript(answers, shell, customTemplate) {
2531
+ const command = formatCreateCommand(answers, shell, customTemplate);
2532
+ if (shell === "cmd") {
2533
+ return ["@echo off", command, ""].join("\r\n");
2534
+ }
2535
+ return ["#!/usr/bin/env sh", "set -e", "", command, ""].join("\n");
2536
+ }
2537
+ function getExportableKeys(answers) {
2538
+ const keys = getAnswerableKeys();
2539
+ if (answers.preset !== "demo") {
2540
+ return keys;
2541
+ }
2542
+ return keys.filter((key) => !DEMO_PINNED_ANSWER_KEYS.includes(key));
2543
+ }
2544
+ function getScriptExtension(shell) {
2545
+ return shell === "cmd" ? ".cmd" : ".sh";
2546
+ }
2547
+ function getShellForPlatform(platform) {
2548
+ return platform === "win32" ? "cmd" : "sh";
2549
+ }
2550
+ function quote(value, shell) {
2551
+ if (shell === "cmd") {
2552
+ return `"${value.replaceAll("%", "%%").replaceAll('"', '""')}"`;
2553
+ }
2554
+ return `'${value.replaceAll("'", "'\\''")}'`;
2555
+ }
2556
+
2557
+ // src/directory-registry.ts
2558
+ var COMMUNITY_PLUGINS_JSON_URL = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/community-plugins.json";
2559
+ async function fetchCommunityPluginIds() {
2560
+ try {
2561
+ const response = await fetch(COMMUNITY_PLUGINS_JSON_URL);
2562
+ if (!response.ok) {
2563
+ return null;
2564
+ }
2565
+ return parseCommunityPluginIds(await response.json());
2566
+ } catch {
2567
+ return null;
2568
+ }
2569
+ }
2570
+ function makeUnlistedPluginIdValidator(listedIds) {
2571
+ return (input) => validatePluginId(input) ?? validatePluginIdIsUnlisted(input ?? "", listedIds);
2572
+ }
2573
+ function parseCommunityPluginIds(json) {
2574
+ if (!Array.isArray(json)) {
2575
+ return null;
2576
+ }
2577
+ const ids = /* @__PURE__ */ new Set();
2578
+ for (const entry of json) {
2579
+ if (typeof entry.id === "string") {
2580
+ ids.add(entry.id.toLowerCase());
2581
+ }
2582
+ }
2583
+ return ids.size === 0 ? null : ids;
2584
+ }
2585
+ function validatePluginIdIsUnlisted(pluginId, listedIds) {
2586
+ if (!listedIds.has(pluginId.toLowerCase())) {
2587
+ return void 0;
2588
+ }
2589
+ return `"${pluginId}" is already the id of a plugin in the Community directory -- the directory requires every id to be unique, and an id can never be changed once the plugin is published`;
2590
+ }
2591
+
2592
+ // src/overlay.ts
2593
+ import {
2594
+ existsSync as existsSync4,
2595
+ readdirSync as readdirSync2,
2596
+ readFileSync as readFileSync4,
2597
+ statSync as statSync2
2598
+ } from "node:fs";
2599
+ import {
2600
+ isAbsolute,
2601
+ join as join3,
2602
+ relative,
2603
+ resolve
2604
+ } from "node:path";
2605
+
2606
+ // src/plan-checks.ts
2607
+ import {
2608
+ existsSync as existsSync3,
2609
+ readdirSync,
2610
+ readFileSync as readFileSync3,
2611
+ statSync
2612
+ } from "node:fs";
2613
+ import {
2614
+ extname as extname2,
2615
+ join as join2
2616
+ } from "node:path";
2617
+
2618
+ // src/templates.ts
2619
+ import { log as log3 } from "@clack/prompts";
2620
+ import ejs from "ejs";
2621
+ import { createHash } from "node:crypto";
2622
+ import {
2623
+ existsSync as existsSync2,
2624
+ mkdirSync,
2625
+ readFileSync as readFileSync2,
2626
+ writeFileSync
2627
+ } from "node:fs";
2628
+ import {
2629
+ dirname,
2630
+ extname,
2631
+ join
2632
+ } from "node:path";
2633
+ import { fileURLToPath } from "node:url";
2634
+
2635
+ // src/template-builder.ts
2636
+ var PARTIAL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2637
+ var Dependency = class {
2638
+ packageName;
2639
+ /**
2640
+ * The spec to write into `package.json`, or `null` to let it be resolved -- from the pin table if the
2641
+ * package is pinned, otherwise from the registry. Set one only when the package must carry that exact
2642
+ * spec regardless of what either would produce.
2643
+ */
2644
+ version;
2645
+ constructor(packageName, version = null) {
2646
+ this.packageName = packageName;
2647
+ this.version = version;
2648
+ }
2649
+ };
2650
+ var TemplateBuilder = class {
2651
+ get badges() {
2652
+ return [...this._badges];
2653
+ }
2654
+ get depcheckIgnores() {
2655
+ return [...this._depcheckIgnores.entries()].map(([packageName, reason]) => ({ packageName, reason })).sort((a, b) => a.packageName.localeCompare(b.packageName));
2656
+ }
2657
+ get dependencies() {
2658
+ return [...this._dependencies.values()].sort((a, b) => a.packageName.localeCompare(b.packageName));
2659
+ }
2660
+ get lintStagedPatterns() {
2661
+ return [...this._lintStagedPatterns.entries()].map(([pattern, commands]) => ({ commands: [...commands], pattern }));
2662
+ }
2663
+ get partials() {
2664
+ return new Set(this._partials);
2665
+ }
2666
+ get scripts() {
2667
+ return Object.fromEntries(Object.entries(this._scripts).sort(([a], [b]) => a.localeCompare(b)));
2668
+ }
2669
+ get sentenceCaseBrands() {
2670
+ return [...this._sentenceCaseBrands].sort((a, b) => a.localeCompare(b));
2671
+ }
2672
+ get templateFiles() {
2673
+ return new Set(this._templateFiles);
2674
+ }
2675
+ _badges = [];
2676
+ _depcheckIgnores = /* @__PURE__ */ new Map();
2677
+ _dependencies = /* @__PURE__ */ new Map();
2678
+ _lintStagedPatterns = /* @__PURE__ */ new Map();
2679
+ _partials = /* @__PURE__ */ new Set();
2680
+ _scripts = {};
2681
+ _sentenceCaseBrands = /* @__PURE__ */ new Set();
2682
+ _templateFiles = /* @__PURE__ */ new Set();
2683
+ /**
2684
+ * Registers a README badge, rendered in registration order on the line under the title.
2685
+ *
2686
+ * Collected here rather than composed from a `render('badges')` seam because the badges have to stay on
2687
+ * ONE source line: Obsidian renders markdown with `breaks: true`, and each partial would end in the
2688
+ * newline that turns into a `<br>` between two badges.
2689
+ */
2690
+ addBadge(markdown) {
2691
+ this._badges.push(markdown);
2692
+ return this;
2693
+ }
2694
+ /**
2695
+ * Registers a declared package that depcheck reports as unused although it is not -- one only ever
2696
+ * invoked as a CLI, named as a string in a config, or reached by a compiler option.
2697
+ *
2698
+ * Called beside the `addPackage` that declares it, so an ignore exists exactly when its package does.
2699
+ * Collected here rather than concatenated from template partials for the same reason as
2700
+ * {@link addLintStagedCommand}: a partial can only emit `'<name>',`, and the last trailing comma is
2701
+ * invalid JSON. The reason is kept because the emitted file records one per entry: once the file
2702
+ * exists, a dependency sweep treats anything depcheck still reports as a failure, so an entry without
2703
+ * its reason is a false positive nobody can re-verify.
2704
+ */
2705
+ addDepcheckIgnore(packageName, reason) {
2706
+ this._depcheckIgnores.set(packageName, reason);
2707
+ return this;
2708
+ }
2709
+ addFiles(paths) {
2710
+ for (const p of paths) {
2711
+ this._templateFiles.add(p);
2712
+ }
2713
+ return this;
2714
+ }
2715
+ /**
2716
+ * Registers a `.lintstagedrc` command.
2717
+ *
2718
+ * The commands are collected here rather than concatenated from template partials because a partial can
2719
+ * only ever emit `'<command>',` -- and the trailing comma the last one leaves behind is exactly what the
2720
+ * generated formatter config strips, so the file failed its own `format:check`.
2721
+ */
2722
+ addLintStagedCommand(pattern, command) {
2723
+ const commands = this._lintStagedPatterns.get(pattern) ?? [];
2724
+ commands.push(command);
2725
+ this._lintStagedPatterns.set(pattern, commands);
2726
+ return this;
2727
+ }
2728
+ addPackage(packageName, version) {
2729
+ this._dependencies.set(packageName, new Dependency(packageName, version));
2730
+ return this;
2731
+ }
2732
+ addPartial(name) {
2733
+ if (!PARTIAL_NAME_PATTERN.test(name)) {
2734
+ throw new Error(`Partial name "${name}" is not kebab-case, so its template files would not be recognized as partials.`);
2735
+ }
2736
+ this._partials.add(name);
2737
+ return this;
2738
+ }
2739
+ addScript(name, command) {
2740
+ if (!command) {
2741
+ const scriptName = name.replaceAll(":", "-");
2742
+ command = `jiti scripts/${scriptName}.ts`;
2743
+ }
2744
+ this._scripts[name] = command;
2745
+ return this;
2746
+ }
2747
+ /**
2748
+ * Registers a proper noun that `obsidianmd/ui/sentence-case` must not flag.
2749
+ *
2750
+ * Collected here rather than concatenated from template partials for the same reason as
2751
+ * {@link addLintStagedCommand}: a partial can only emit `'<brand>',`, and the last trailing comma is
2752
+ * what the generated formatter config strips.
2753
+ */
2754
+ addSentenceCaseBrand(brand) {
2755
+ this._sentenceCaseBrands.add(brand);
2756
+ return this;
2757
+ }
2758
+ };
2759
+
2760
+ // src/versions.ts
2761
+ import { log as log2 } from "@clack/prompts";
2762
+ var PINNED_VERSIONS = {
2763
+ "@babel/core": {
2764
+ check: `node -e "process.stdout.write(require('@rollup/plugin-babel/package.json').peerDependencies['@babel/core'])"`,
2765
+ checkRequires: "@rollup/plugin-babel",
2766
+ expect: "^7.0.0",
2767
+ manualCheck: null,
2768
+ needsOverride: false,
2769
+ section: "devDependencies",
2770
+ version: "^7.29.7",
2771
+ why: "@babel/core is only ever added alongside @rollup/plugin-babel, whose newest release still peers on `^7.0.0` while the registry tags 8.x as latest. Resolving latest therefore produced an `npm install` that fails outright with ERESOLVE for rollup with preact, react or solid. The check reads the peer range @rollup/plugin-babel itself declares, so the pin retires itself when that widens to ^8."
2772
+ },
2773
+ "@babel/plugin-transform-react-jsx": {
2774
+ check: `node -e "process.stdout.write(require('@rollup/plugin-babel/package.json').peerDependencies['@babel/core'])"`,
2775
+ checkRequires: "@rollup/plugin-babel",
2776
+ expect: "^7.0.0",
2777
+ manualCheck: null,
2778
+ needsOverride: false,
2779
+ section: "devDependencies",
2780
+ version: "^7.29.7",
2781
+ why: "Held on 7.x for the same reason as @babel/core, and it has to move as one set: 8.0.1 peers on `@babel/core@^8.0.0`, so pinning core to 7 without pinning this one just moves the ERESOLVE. Only ever added for rollup + preact, which is exactly where @rollup/plugin-babel forces 7."
2782
+ },
2783
+ "@babel/preset-react": {
2784
+ check: `node -e "process.stdout.write(require('@rollup/plugin-babel/package.json').peerDependencies['@babel/core'])"`,
2785
+ checkRequires: "@rollup/plugin-babel",
2786
+ expect: "^7.0.0",
2787
+ manualCheck: null,
2788
+ needsOverride: false,
2789
+ section: "devDependencies",
2790
+ version: "^7.29.7",
2791
+ why: "Held on 7.x for the same reason as @babel/core, and it has to move as one set: 8.0.1 peers on `@babel/core@^8.0.0`. Only ever added for rollup + react, which is exactly where @rollup/plugin-babel forces 7."
2792
+ },
2793
+ "@babel/preset-typescript": {
2794
+ check: `node -e "process.stdout.write(require('@rollup/plugin-babel/package.json').peerDependencies['@babel/core'])"`,
2795
+ checkRequires: "@rollup/plugin-babel",
2796
+ expect: "^7.0.0",
2797
+ manualCheck: null,
2798
+ needsOverride: false,
2799
+ section: "devDependencies",
2800
+ version: "^7.29.7",
2801
+ why: "Held on 7.x for the same reason as @babel/core, and it has to move as one set: 8.0.1 peers on `@babel/core@^8.0.0`. Only ever added for rollup + vue, which is exactly where @rollup/plugin-babel forces 7."
2802
+ },
2803
+ "@codemirror/language": {
2804
+ check: null,
2805
+ checkRequires: null,
2806
+ expect: null,
2807
+ manualCheck: "Must be the @codemirror/language release built against the @codemirror/state version Obsidian peers on. Bump it only together with @codemirror/state.",
2808
+ needsOverride: false,
2809
+ section: "devDependencies",
2810
+ version: "6.12.4",
2811
+ why: "Obsidian bundles its own CodeMirror. A @codemirror/language built against a different @codemirror/state duplicates the state package at runtime, and two copies of a CodeMirror facet do not interoperate."
2812
+ },
2813
+ "@codemirror/state": {
2814
+ check: `node -e "process.stdout.write(require('obsidian/package.json').peerDependencies['@codemirror/state'])"`,
2815
+ checkRequires: "obsidian",
2816
+ expect: "6.5.0",
2817
+ manualCheck: null,
2818
+ needsOverride: true,
2819
+ section: "devDependencies",
2820
+ version: "6.5.0",
2821
+ why: "obsidian peers on this exact version. Anything else fails npm install with ERESOLVE, and a second copy in the tree breaks editor extensions at runtime."
2822
+ },
2823
+ "@codemirror/view": {
2824
+ check: `node -e "process.stdout.write(require('obsidian/package.json').peerDependencies['@codemirror/view'])"`,
2825
+ checkRequires: "obsidian",
2826
+ expect: "6.38.6",
2827
+ manualCheck: null,
2828
+ needsOverride: true,
2829
+ section: "devDependencies",
2830
+ version: "6.38.6",
2831
+ why: "obsidian peers on this exact version. Anything else fails npm install with ERESOLVE, and a second copy in the tree breaks editor extensions at runtime."
2832
+ },
2833
+ "moment": {
2834
+ check: null,
2835
+ checkRequires: null,
2836
+ expect: null,
2837
+ manualCheck: "Must stay on the version Obsidian bundles, so the plugin and the app share one moment instance. Check Obsidian's bundled moment before changing it.",
2838
+ needsOverride: false,
2839
+ section: "devDependencies",
2840
+ version: "2.29.4",
2841
+ why: "Obsidian bundles moment 2.29.4 and re-exports it. Type-checking against a newer moment describes an API the running app does not have."
2842
+ },
2843
+ "typescript": {
2844
+ check: `node -e "process.stdout.write(require('typescript-eslint/package.json').peerDependencies.typescript)"`,
2845
+ checkRequires: "typescript-eslint",
2846
+ expect: ">=4.8.4 <6.1.0",
2847
+ manualCheck: "TypeScript 7 (tsgo) is a native rewrite much of this toolchain has not migrated to. Re-check that every type-aware tool in the project supports 7 before lifting the pin.",
2848
+ needsOverride: true,
2849
+ section: "devDependencies",
2850
+ version: "6.0.3",
2851
+ why: "TypeScript 7 (tsgo) breaks this toolchain: typescript-eslint's parser crashes on the native API, so type-aware ESLint cannot run on 7. typescript-eslint peer-requires the range below, so 6.x is the last usable major."
2852
+ }
2853
+ };
2854
+ var ADVISORY_OVERRIDES = {
2855
+ "@puppeteer/browsers": {
2856
+ check: `node -e "const fs=require('node:fs');process.stdout.write(JSON.parse(fs.readFileSync('node_modules/@wdio/utils/package.json','utf8')).dependencies['@puppeteer/browsers'])"`,
2857
+ expect: "^2.2.0",
2858
+ manualCheck: "The check reads @wdio/utils's own declared range, not a version, because that range is what makes the override necessary. When it moves to ^3 or later, @wdio/utils no longer pulls extract-zip in and both this override and this entry can go.",
2859
+ requires: ["obsidian-dev-utils", "wdio-obsidian-service"],
2860
+ spec: "^3.2.1",
2861
+ why: "Clears GHSA-jmr9-qjv8-65gv (extract-zip unvalidated symlink path traversal). extract-zip is vulnerable at every published version, so there is nothing to override it to, and the direct dependency cannot be bumped either: even the newest webdriverio reaches extract-zip through @wdio/utils -> @puppeteer/browsers ^2.x. @puppeteer/browsers@3 replaced extract-zip with modern-tar, so forcing 3.x on @wdio/utils, the only consumer left on 2.x, removes the subtree. @wdio/utils imports only install, canDownload, resolveBuildId, detectBrowserPlatform, Browser, ChromeReleaseChannel, computeExecutablePath and the InstallOptions type, all still exported by 3.x, and both packages are ESM-only. Never take the `npm audit fix --force` remedy: it downgrades obsidian-integration-testing to 1.1.2."
2862
+ },
2863
+ "deepmerge-ts": {
2864
+ check: `node -e "const fs=require('node:fs');process.stdout.write(JSON.parse(fs.readFileSync('node_modules/@wdio/utils/package.json','utf8')).dependencies['deepmerge-ts'])"`,
2865
+ expect: "^7.0.3",
2866
+ manualCheck: "The check reads the range @wdio/utils declares, which is what forces the override. When it moves to ^8 or later the override and this entry can go. Before lifting it, re-run the deepmergeCustom probe: @wdio/config passes a `mergeArrays` handler that reads `meta.key`, and that is the API 8.0.0 renamed.",
2867
+ requires: ["obsidian-dev-utils", "wdio-obsidian-service"],
2868
+ spec: "^8.0.2",
2869
+ why: "Clears GHSA-ggr8-5vv4-36mx (stack exhaustion merging recursive object graphs), patched in 8.0.0. Every wdio package still declares ^7.0.3, including the newest, so no direct bump reaches it and `npm audit fix --force` only offers a downgrade of obsidian-integration-testing to 1.1.2. The forced major is safe for these consumers: 8.0.0 breaks by renaming the mergeInfo system and aligning the customization shorthand, and both surfaces were measured to behave identically on 7.1.6 and 8.0.1 in the exact shapes the tree uses -- @wdio/config's `deepmergeCustom` with a `mergeArrays` handler reading `meta.key` and returning `utils.actions.defaultMerge`, and webdriver's `deepmergeCustom({ mergeArrays: false })`. A scan of the whole installed tree found no other binding than `deepmerge` and `deepmergeCustom`."
2870
+ },
2871
+ "serialize-javascript": {
2872
+ check: `node -e "const fs=require('node:fs');process.stdout.write(JSON.parse(fs.readFileSync('node_modules/mocha/package.json','utf8')).dependencies['serialize-javascript'])"`,
2873
+ expect: "^6.0.2",
2874
+ manualCheck: "The check reads mocha's own declared range, not a version, because that range is what makes the override necessary. When mocha moves to ^7 or later the override and this entry can go.",
2875
+ requires: ["@wdio/mocha-framework"],
2876
+ spec: "^7.1.1",
2877
+ why: "Clears GHSA-5c6j-r48x-rmvq (RCE via RegExp.flags and Date.prototype.toISOString) and GHSA-qj8w-gfj5-8c6v (CPU-exhaustion denial of service via crafted array-likes), both fixed in 7.0.5. No direct bump reaches it: mocha declares `serialize-javascript@^6.0.2` at every published version including 11.8.0, and the only consumer here is @wdio/mocha-framework -> mocha. `npm audit fix --force` offers a DOWNGRADE to @wdio/mocha-framework@7.7.3 instead. The forced major is safe for the one call site that exists: mocha's `buffered-worker-pool.js` calls `serializeJavascript(opts, { unsafe: true, ignoreFunction: true })`, and 6.0.2 and 7.1.1 were measured to return byte-identical output for that exact option pair across RegExp, Date, Map, Set, undefined, BigInt, functions and a script-tag string. Note the call is on mocha's parallel-run path, which wdio does not take."
2878
+ }
2879
+ };
2880
+ var DESKTOP_RELEASES_JSON_URL = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/desktop-releases.json";
2881
+ var FALLBACK_MIN_APP_VERSION = "1.13.0";
2882
+ var FALLBACK_VERSION = "latest";
2883
+ var JSON_INDENT_SPACES2 = 2;
2884
+ var REGISTRY_URL = "https://registry.npmjs.org";
2885
+ function buildOverrides(dependencies) {
2886
+ const overrides = {};
2887
+ for (const packageName of getPinnedPackageNames(dependencies)) {
2888
+ if (PINNED_VERSIONS[packageName]?.needsOverride) {
2889
+ overrides[packageName] = `$${packageName}`;
2890
+ }
2891
+ }
2892
+ for (const [packageName, advisoryOverride] of getAdvisoryOverrides(dependencies)) {
2893
+ overrides[packageName] = advisoryOverride.spec;
2894
+ }
2895
+ return overrides;
2896
+ }
2897
+ function buildPinnedVersionsJson(dependencies) {
2898
+ const pins = {};
2899
+ for (const packageName of getPinnedPackageNames(dependencies)) {
2900
+ const pin = PINNED_VERSIONS[packageName];
2901
+ if (!pin) {
2902
+ continue;
2903
+ }
2904
+ const canCheck = pin.check !== null && (pin.checkRequires === null || hasPackage(dependencies, pin.checkRequires));
2905
+ pins[packageName] = canCheck ? {
2906
+ check: pin.check,
2907
+ expect: pin.expect,
2908
+ section: pin.section,
2909
+ why: pin.why
2910
+ } : {
2911
+ check: null,
2912
+ manualCheck: pin.manualCheck ?? pin.why,
2913
+ section: pin.section,
2914
+ why: pin.why
2915
+ };
2916
+ }
2917
+ for (const [packageName, advisoryOverride] of getAdvisoryOverrides(dependencies)) {
2918
+ pins[packageName] = {
2919
+ check: advisoryOverride.check,
2920
+ expect: advisoryOverride.expect,
2921
+ manualCheck: advisoryOverride.manualCheck,
2922
+ section: "overrides",
2923
+ why: advisoryOverride.why
2924
+ };
2925
+ }
2926
+ const content = {
2927
+ comment: [
2928
+ "Release conditions for every exact-pinned version in package.json (dependencies, devDependencies and",
2929
+ "overrides), plus every override added to clear an npm audit advisory reached only transitively.",
2930
+ "An exact pin is invisible to a caret-range dependency sweep, so nothing would ever remind you",
2931
+ "that a pin is stale. This file is what makes that check happen: run each `check` and compare its trimmed",
2932
+ "stdout to `expect`. Equal means the pin is still required; different means the condition that justified it",
2933
+ "has changed and the pin should be re-examined. `check: null` means no mechanical test exists - follow",
2934
+ "`manualCheck` instead. Every exact pin belongs here, and every entry must name a package that is actually",
2935
+ "present in the declared section."
2936
+ ],
2937
+ pins
2938
+ };
2939
+ return `${JSON.stringify(content, null, JSON_INDENT_SPACES2)}
2940
+ `;
2941
+ }
2942
+ function buildResolutions(dependencies) {
2943
+ const resolutions = {};
2944
+ for (const [packageName, spec] of Object.entries(buildOverrides(dependencies))) {
2945
+ resolutions[packageName] = spec.startsWith("$") ? PINNED_VERSIONS[spec.slice(1)]?.version ?? spec : spec;
2946
+ }
2947
+ return resolutions;
2948
+ }
2949
+ async function fetchLatestObsidianVersion() {
2950
+ try {
2951
+ const response = await fetch(DESKTOP_RELEASES_JSON_URL);
2952
+ if (!response.ok) {
2953
+ return FALLBACK_MIN_APP_VERSION;
2954
+ }
2955
+ const desktopReleasesJson = await response.json();
2956
+ return desktopReleasesJson.latestVersion ?? FALLBACK_MIN_APP_VERSION;
2957
+ } catch {
2958
+ return FALLBACK_MIN_APP_VERSION;
2959
+ }
2960
+ }
2961
+ async function fetchLatestVersion(packageName) {
2962
+ try {
2963
+ const response = await fetch(`${REGISTRY_URL}/${packageName}/latest`);
2964
+ if (!response.ok) {
2965
+ return null;
2966
+ }
2967
+ const packument = await response.json();
2968
+ return packument.version;
2969
+ } catch {
2970
+ return null;
2971
+ }
2972
+ }
2973
+ async function resolveVersions(dependencies) {
2974
+ const resolved = /* @__PURE__ */ new Map();
2975
+ const unresolved = [];
2976
+ for (const dependency of dependencies) {
2977
+ if (dependency.version !== null) {
2978
+ resolved.set(dependency.packageName, dependency.version);
2979
+ continue;
2980
+ }
2981
+ const pin = PINNED_VERSIONS[dependency.packageName];
2982
+ if (pin) {
2983
+ resolved.set(dependency.packageName, pin.version);
2984
+ continue;
2985
+ }
2986
+ unresolved.push(dependency.packageName);
2987
+ }
2988
+ const fetched = await Promise.all(unresolved.map(fetchLatestVersion));
2989
+ const failed = [];
2990
+ for (const [index, version] of fetched.entries()) {
2991
+ const packageName = unresolved[index] ?? "";
2992
+ if (version === null) {
2993
+ failed.push(packageName);
2994
+ resolved.set(packageName, FALLBACK_VERSION);
2995
+ continue;
2996
+ }
2997
+ resolved.set(packageName, `^${version}`);
2998
+ }
2999
+ if (failed.length > 0) {
3000
+ log2.warn(`Could not look up the latest version of ${failed.join(", ")}. Falling back to "${FALLBACK_VERSION}".`);
3001
+ }
3002
+ return resolved;
3003
+ }
3004
+ function getAdvisoryOverrides(dependencies) {
3005
+ return Object.entries(ADVISORY_OVERRIDES).filter(([, advisoryOverride]) => advisoryOverride.requires.some((packageName) => hasPackage(dependencies, packageName))).sort(([a], [b]) => a.localeCompare(b));
3006
+ }
3007
+ function getPinnedPackageNames(dependencies) {
3008
+ return dependencies.map((dependency) => dependency.packageName).filter((packageName) => packageName in PINNED_VERSIONS).sort((a, b) => a.localeCompare(b));
3009
+ }
3010
+ function hasPackage(dependencies, packageName) {
3011
+ return dependencies.some((dependency) => dependency.packageName === packageName);
3012
+ }
3013
+
3014
+ // src/templates.ts
3015
+ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([".wasm"]);
3016
+ var JSON_INDENT_SPACES3 = 2;
3017
+ var UNRESOLVED_VERSION = "latest";
3018
+ var BASE_TEMPLATE_FILES = [
3019
+ // Never empty: `tslib` and `typescript` below are false positives on every answer set.
3020
+ ".depcheckrc.json",
3021
+ ".editorconfig",
3022
+ ".env",
3023
+ ".gitattributes",
3024
+ ".gitignore",
3025
+ ".npmrc",
3026
+ "CONTRIBUTING.md",
3027
+ "LICENSE",
3028
+ "README.md",
3029
+ "manifest.json",
3030
+ "package.json",
3031
+ "pinned-versions.json",
3032
+ // Emitted for every project, not only the pnpm answer, for the same reason `resolutions` is: which
3033
+ // Tool installs a checkout is not a property of the checkout. Without it `pnpm install` FAILS with
3034
+ // ERR_PNPM_IGNORED_BUILDS -- pnpm blocks dependency install scripts by default and, since 10, treats
3035
+ // That as an error rather than a warning.
3036
+ "pnpm-workspace.yaml",
3037
+ "src/main.ts",
3038
+ "src/plugin.ts",
3039
+ "tsconfig.json",
3040
+ "versions.json"
3041
+ ];
3042
+ var FEATURE_REGISTRIES = [
3043
+ { answerKey: "preset", options: PRESET_OPTIONS },
3044
+ { answerKey: "bundler", options: BUNDLER_OPTIONS },
3045
+ { answerKey: "uiFramework", options: UI_FRAMEWORK_OPTIONS },
3046
+ { answerKey: "linter", options: LINTER_OPTIONS },
3047
+ { answerKey: "formatter", options: FORMATTER_OPTIONS },
3048
+ { answerKey: "spellChecker", options: SPELL_CHECKER_OPTIONS },
3049
+ { answerKey: "markdownLinter", options: MARKDOWN_LINTER_OPTIONS },
3050
+ { answerKey: "testRunner", options: TEST_RUNNER_OPTIONS },
3051
+ { answerKey: "e2eTestRunner", options: E2E_TEST_RUNNER_OPTIONS },
3052
+ { answerKey: "editorExtensions", options: EDITOR_EXTENSIONS_OPTIONS },
3053
+ { answerKey: "styling", options: STYLING_OPTIONS },
3054
+ { answerKey: "commitLinting", options: COMMIT_LINTING_OPTIONS },
3055
+ { answerKey: "hotReload", options: HOT_RELOAD_OPTIONS },
3056
+ { answerKey: "internationalization", options: INTERNATIONALIZATION_OPTIONS },
3057
+ { answerKey: "gitHubActions", options: GITHUB_ACTIONS_OPTIONS },
3058
+ { answerKey: "gitHubIssueTemplates", options: GITHUB_ISSUE_TEMPLATES_OPTIONS },
3059
+ { answerKey: "fundingPlatform", options: FUNDING_PLATFORM_OPTIONS },
3060
+ { answerKey: "coverageBadge", options: COVERAGE_BADGE_OPTIONS },
3061
+ { answerKey: "wasmSupport", options: WASM_SUPPORT_OPTIONS },
3062
+ { answerKey: "apiSubset", options: API_SUBSET_OPTIONS },
3063
+ // Both of these were missing, and a question absent from this list is a question the user is asked
3064
+ // And whose answer is then discarded. `platformSupport` was the live one: `manifest.json.ejs` calls
3065
+ // `render('platform')` and both partials sit on disk, but with nothing contributing `desktop-only` or
3066
+ // `desktop-and-mobile` the section rendered as nothing, so EVERY generated manifest shipped without
3067
+ // `isDesktopOnly` -- a required field. `packageManager` contributes no partial today and so changes
3068
+ // No output; it is registered anyway, because "every question is here" is the invariant that keeps
3069
+ // The next one from going the same way, and because a `X_npm.ejs` added later should just work.
3070
+ { answerKey: "platformSupport", options: PLATFORM_SUPPORT_OPTIONS },
3071
+ { answerKey: "packageManager", options: PACKAGE_MANAGER_OPTIONS }
3072
+ ];
3073
+ var DEMO_OVERRIDES = [
3074
+ { answerKey: "uiFramework", demoValue: "react", options: UI_FRAMEWORK_OPTIONS },
3075
+ { answerKey: "uiFramework", demoValue: "svelte", options: UI_FRAMEWORK_OPTIONS },
3076
+ { answerKey: "uiFramework", demoValue: "vue", options: UI_FRAMEWORK_OPTIONS },
3077
+ { answerKey: "editorExtensions", demoValue: "codemirror", options: EDITOR_EXTENSIONS_OPTIONS },
3078
+ { answerKey: "linter", demoValue: "eslint", exclusive: true, options: LINTER_OPTIONS },
3079
+ { answerKey: "markdownLinter", demoValue: "markdownlint", options: MARKDOWN_LINTER_OPTIONS },
3080
+ { answerKey: "spellChecker", demoValue: "cspell", options: SPELL_CHECKER_OPTIONS },
3081
+ { answerKey: "styling", demoValue: "scss", options: STYLING_OPTIONS }
3082
+ ];
3083
+ function buildTemplate(answers, overlay = null) {
3084
+ const builder = new TemplateBuilder();
3085
+ builder.addFiles(BASE_TEMPLATE_FILES).addPackage("@types/node").addPackage("jiti").addPackage("obsidian").addPackage("tslib").addDepcheckIgnore("tslib", "never imported by name: `importHelpers: true` in `tsconfig.json` makes the compiled output import it.").addPackage("typescript").addDepcheckIgnore("typescript", "the compiler `tsconfig.json` configures, reached by `tsc`, by the TypeScript step of the bundler and by the editor rather than by an import.").addSentenceCaseBrand(answers.pluginName).addPartial("common");
3086
+ const funding = resolveFunding(answers);
3087
+ if (funding) {
3088
+ builder.addFiles([".github/FUNDING.yml"]).addBadge(funding.getBadge(answers)).addPartial("has-funding");
3089
+ }
3090
+ const repoUrl = `https://github.com/${answers.authorGitHubName}/obsidian-${answers.pluginId}`;
3091
+ builder.addBadge(`[![GitHub release](https://img.shields.io/github/v/release/${answers.authorGitHubName}/obsidian-${answers.pluginId})](${repoUrl}/releases)`).addBadge(`[![GitHub downloads](https://img.shields.io/github/downloads/${answers.authorGitHubName}/obsidian-${answers.pluginId}/total)](${repoUrl}/releases)`);
3092
+ if (answers.obsidianConfigFolder) {
3093
+ builder.addPartial("has-vault-true");
3094
+ } else {
3095
+ builder.addPartial("has-vault-false");
3096
+ }
3097
+ for (const registry of FEATURE_REGISTRIES) {
3098
+ const value = String(answers[registry.answerKey]);
3099
+ const option = resolveFeature(registry.options, value);
3100
+ option.configure(builder, answers);
3101
+ builder.addPartial(option.partialName);
3102
+ }
3103
+ if (answers.preset === "demo") {
3104
+ for (const override of DEMO_OVERRIDES) {
3105
+ const chosenValue = String(answers[override.answerKey]);
3106
+ if (chosenValue === override.demoValue || override.exclusive && chosenValue !== "none") {
3107
+ continue;
3108
+ }
3109
+ const option = resolveFeature(override.options, override.demoValue);
3110
+ if (conflictsOverJsxRuntime(resolveFeature(override.options, chosenValue), option)) {
3111
+ continue;
3112
+ }
3113
+ option.configure(builder, answers);
3114
+ builder.addPartial(option.partialName);
3115
+ }
3116
+ }
3117
+ if (overlay) {
3118
+ applyOverlay(builder, overlay);
3119
+ }
3120
+ if (builder.lintStagedPatterns.length > 0) {
3121
+ addStagedFilesHook(builder);
3122
+ }
3123
+ return builder;
3124
+ }
3125
+ function copyTemplates(answers, targetDir, currentVersion, existingConfig, resolvedVersions = /* @__PURE__ */ new Map(), minAppVersion = FALLBACK_MIN_APP_VERSION, overlay = null, projectFormatter) {
3126
+ const templatesDir = join(getScriptDir(), "..", "templates", "default");
3127
+ function findTemplate(relativePath) {
3128
+ if (overlay) {
3129
+ const overlayPath = join(overlay.dir, relativePath);
3130
+ if (existsSync2(overlayPath)) {
3131
+ return overlayPath;
3132
+ }
3133
+ }
3134
+ const builtInPath = join(templatesDir, relativePath);
3135
+ return existsSync2(builtInPath) ? builtInPath : null;
3136
+ }
3137
+ const newConfig = {
3138
+ fileHashes: {},
3139
+ generatorVersion: currentVersion
3140
+ };
3141
+ const builder = buildTemplate(answers, overlay);
3142
+ const templateFiles = builder.templateFiles;
3143
+ const partials = builder.partials;
3144
+ const dependencies = builder.dependencies;
3145
+ let currentTemplatePath = "";
3146
+ let renderRoot = "";
3147
+ const templateContext = {
3148
+ ...answers,
3149
+ ...getFundingTemplateContext(answers),
3150
+ _badges: builder.badges,
3151
+ _depcheckIgnores: builder.depcheckIgnores,
3152
+ _dependencies: dependencies.map((dependency) => ({
3153
+ packageName: dependency.packageName,
3154
+ version: dependency.version ?? resolvedVersions.get(dependency.packageName) ?? PINNED_VERSIONS[dependency.packageName]?.version ?? UNRESOLVED_VERSION
3155
+ })),
3156
+ _lintStagedPatterns: builder.lintStagedPatterns,
3157
+ _minAppVersion: minAppVersion,
3158
+ _overrides: Object.entries(buildOverrides(dependencies)).map(([packageName, spec]) => ({ packageName, spec })),
3159
+ _ownRepoLinkPattern: getOwnRepoLinkPattern(answers),
3160
+ _pinnedVersionsJson: buildPinnedVersionsJson(dependencies),
3161
+ _resolutions: Object.entries(buildResolutions(dependencies)).map(([packageName, spec]) => ({ packageName, spec })),
3162
+ _scripts: builder.scripts,
3163
+ _sentenceCaseBrands: builder.sentenceCaseBrands,
3164
+ pluginShortName: getPluginShortName(answers.pluginId),
3165
+ render(options) {
3166
+ const { indentLevel, section } = typeof options === "string" ? { indentLevel: 0, section: options } : { indentLevel: 0, section: void 0, ...options };
3167
+ const basePath = (renderRoot || currentTemplatePath).replace(/\.ejs$/, "");
3168
+ const previousTemplatePath = currentTemplatePath;
3169
+ const previousRenderRoot = renderRoot;
3170
+ let result = "";
3171
+ for (const partial of partials) {
3172
+ const partialPath = section ? `${basePath}@${section}_${partial}.ejs` : `${basePath}_${partial}.ejs`;
3173
+ const fullPath = findTemplate(partialPath);
3174
+ if (fullPath === null) {
3175
+ continue;
3176
+ }
3177
+ currentTemplatePath = partialPath;
3178
+ renderRoot = previousRenderRoot || partialPath;
3179
+ let rendered = ejs.render(readFileSync2(fullPath, "utf-8"), templateContext);
3180
+ if (indentLevel > 0) {
3181
+ const indent = " ".repeat(indentLevel);
3182
+ rendered = rendered.replaceAll(/^(?<Content>.+)$/gm, `${indent}$<Content>`);
3183
+ }
3184
+ result += rendered;
3185
+ }
3186
+ currentTemplatePath = previousTemplatePath;
3187
+ renderRoot = previousRenderRoot;
3188
+ return result;
3189
+ },
3190
+ /**
3191
+ * Sorts a block of `import` statements a template composed from partials, by the module each names.
3192
+ *
3193
+ * A section that contributes imports emits them in PARTIAL order, which is the order the answers were
3194
+ * registered in {@link FEATURE_REGISTRIES} -- and that has nothing to do with the order an
3195
+ * import-sorting lint rule wants. The obsidian-dev-utils presets now adopt that package's shared
3196
+ * ESLint config, which sorts imports as an ERROR, so `uiFramework=lit editorExtensions=codemirror`
3197
+ * emitted a `src/plugin.ts` that was red the moment it was generated. The sorting cannot be pushed
3198
+ * into the partials: each one knows only itself, and which others are beside it is the answer set.
3199
+ */
3200
+ sortImports(text3) {
3201
+ return sortImportStatements(text3);
3202
+ }
3203
+ };
3204
+ const skipped = [];
3205
+ const updated = [];
3206
+ const created = [];
3207
+ const written = [];
3208
+ for (const registeredPath of templateFiles) {
3209
+ const destinationPath = getDestinationPath(registeredPath, answers);
3210
+ const fullDestinationPath = join(targetDir, destinationPath);
3211
+ const isAsset = ASSET_EXTENSIONS.has(extname(registeredPath));
3212
+ const ejsPath = findTemplate(`${registeredPath}.ejs`);
3213
+ let rendered;
3214
+ if (isAsset) {
3215
+ currentTemplatePath = registeredPath;
3216
+ rendered = readFileSync2(findTemplate(registeredPath) ?? join(templatesDir, registeredPath));
3217
+ } else if (ejsPath === null) {
3218
+ currentTemplatePath = `${registeredPath}.ejs`;
3219
+ rendered = templateContext["render"]();
3220
+ } else {
3221
+ currentTemplatePath = `${registeredPath}.ejs`;
3222
+ try {
3223
+ rendered = ejs.render(readFileSync2(ejsPath, "utf-8"), templateContext);
3224
+ } catch (error) {
3225
+ if (overlay && ejsPath.startsWith(overlay.dir)) {
3226
+ throw new Error(`${ejsPath} failed to render: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3227
+ }
3228
+ rendered = readFileSync2(ejsPath, "utf-8");
3229
+ }
3230
+ }
3231
+ const newHash = sha256(rendered);
3232
+ if (existingConfig && existsSync2(fullDestinationPath)) {
3233
+ const currentContent = isAsset ? readFileSync2(fullDestinationPath) : readFileSync2(fullDestinationPath, "utf-8");
3234
+ const currentHash = sha256(currentContent);
3235
+ const originalHash = existingConfig.fileHashes[destinationPath];
3236
+ if (matchesRender(currentHash, newHash, projectFormatter, destinationPath, rendered)) {
3237
+ newConfig.fileHashes[destinationPath] = currentHash;
3238
+ continue;
3239
+ }
3240
+ if (originalHash === void 0) {
3241
+ skipped.push(destinationPath);
3242
+ continue;
3243
+ }
3244
+ if (currentHash !== originalHash) {
3245
+ skipped.push(destinationPath);
3246
+ newConfig.fileHashes[destinationPath] = originalHash;
3247
+ continue;
3248
+ }
3249
+ updated.push(destinationPath);
3250
+ } else {
3251
+ created.push(destinationPath);
3252
+ }
3253
+ newConfig.fileHashes[destinationPath] = newHash;
3254
+ const destDir = dirname(fullDestinationPath);
3255
+ if (!existsSync2(destDir)) {
3256
+ mkdirSync(destDir, { recursive: true });
3257
+ }
3258
+ writeFileSync(fullDestinationPath, rendered);
3259
+ written.push(destinationPath);
3260
+ }
3261
+ formatWrittenFiles(projectFormatter, targetDir, written, newConfig);
3262
+ const configPath = join(targetDir, CONFIG_FILE_NAME);
3263
+ writeFileSync(configPath, `${JSON.stringify(newConfig, null, JSON_INDENT_SPACES3)}
3264
+ `);
3265
+ if (existingConfig) {
3266
+ logUpdateSummary(updated, created, skipped);
3267
+ }
3268
+ return newConfig;
3269
+ }
3270
+ function getDestinationPath(templatePath, answers) {
3271
+ return templatePath.replace(/%= (?<AnswerKey>.+?) %/g, (_match, ...args) => String(answers[String(args[0])]));
3272
+ }
3273
+ function getScriptDir() {
3274
+ return dirname(fileURLToPath(import.meta.url));
3275
+ }
3276
+ function isPartialTemplatePath(templatePath) {
3277
+ const fileName = (templatePath.split("/").pop() ?? "").replace(/\.ejs$/, "");
3278
+ const markerIndex = fileName.lastIndexOf("_");
3279
+ return markerIndex > 0 && PARTIAL_NAME_PATTERN.test(fileName.slice(markerIndex + 1));
3280
+ }
3281
+ function loadConfig(dir) {
3282
+ const configPath = join(dir, CONFIG_FILE_NAME);
3283
+ if (!existsSync2(configPath)) {
3284
+ return null;
3285
+ }
3286
+ const parsed = JSON.parse(readFileSync2(configPath, "utf-8"));
3287
+ const raw = parsed;
3288
+ migrateAnswers(raw);
3289
+ return parsed;
3290
+ }
3291
+ function recordHashesOnDisk(targetDir, config) {
3292
+ const fileHashes = {};
3293
+ for (const [destinationPath, hash] of Object.entries(config.fileHashes)) {
3294
+ const fullPath = join(targetDir, destinationPath);
3295
+ fileHashes[destinationPath] = existsSync2(fullPath) ? sha256(readFileSync2(fullPath)) : hash;
3296
+ }
3297
+ return { ...config, fileHashes };
3298
+ }
3299
+ function sortImportStatements(text3) {
3300
+ const blocks = [];
3301
+ let pendingComments = [];
3302
+ for (const line of text3.split("\n")) {
3303
+ if (line.trim() === "") {
3304
+ continue;
3305
+ }
3306
+ if (line.startsWith("import ")) {
3307
+ blocks.push([...pendingComments, line]);
3308
+ pendingComments = [];
3309
+ } else if (line.startsWith("//")) {
3310
+ pendingComments.push(line);
3311
+ } else {
3312
+ blocks.at(-1)?.push(line);
3313
+ }
3314
+ }
3315
+ if (blocks.length === 0) {
3316
+ return text3;
3317
+ }
3318
+ blocks.at(-1)?.push(...pendingComments);
3319
+ const statements = blocks.map((block) => block.join("\n"));
3320
+ statements.sort((a, b) => importedModule(a).localeCompare(importedModule(b)));
3321
+ return `${statements.join("\n")}
3322
+ `;
3323
+ }
3324
+ function applyOverlay(builder, overlay) {
3325
+ builder.addFiles([...overlay.files]);
3326
+ for (const packageName of overlay.packages) {
3327
+ builder.addPackage(packageName);
3328
+ }
3329
+ for (const badge of overlay.badges) {
3330
+ builder.addBadge(badge);
3331
+ }
3332
+ for (const partial of overlay.partials) {
3333
+ builder.addPartial(partial);
3334
+ }
3335
+ }
3336
+ function conflictsOverJsxRuntime(chosen, demo) {
3337
+ return chosen.jsxImportSource !== void 0 && demo.jsxImportSource !== void 0 && chosen.jsxImportSource !== demo.jsxImportSource;
3338
+ }
3339
+ function formatWrittenFiles(projectFormatter, targetDir, written, config) {
3340
+ const textFiles = written.filter((destinationPath) => !ASSET_EXTENSIONS.has(extname(destinationPath)));
3341
+ if (!projectFormatter || textFiles.length === 0) {
3342
+ return;
3343
+ }
3344
+ projectFormatter.formatFiles(textFiles);
3345
+ for (const destinationPath of textFiles) {
3346
+ config.fileHashes[destinationPath] = sha256(readFileSync2(join(targetDir, destinationPath)));
3347
+ }
3348
+ }
3349
+ function getFundingTemplateContext(answers) {
3350
+ const funding = resolveFunding(answers);
3351
+ return {
3352
+ _fundingYmlKey: funding?.fundingYmlKey ?? "",
3353
+ _fundingYmlValue: funding?.getFundingYmlValue(answers) ?? "",
3354
+ fundingUrl: funding?.getUrl(answers) ?? ""
3355
+ };
3356
+ }
3357
+ function getOwnRepoLinkPattern(answers) {
3358
+ const repoUrl = `https://github.com/${answers.authorGitHubName}/obsidian-${answers.pluginId}`;
3359
+ return `^${repoUrl.replaceAll(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`)}(?:[/.?#]|$)`;
3360
+ }
3361
+ function importedModule(block) {
3362
+ return /(?:from )?'(?<Module>[^']+)';$/.exec(block)?.groups?.["Module"] ?? block;
3363
+ }
3364
+ function logUpdateSummary(updated, created, skipped) {
3365
+ if (updated.length > 0) {
3366
+ log3.success(`Updated ${String(updated.length)} file(s):`);
3367
+ for (const f of updated) {
3368
+ log3.info(` ${f}`);
3369
+ }
3370
+ }
3371
+ if (created.length > 0) {
3372
+ log3.success(`Created ${String(created.length)} new file(s):`);
3373
+ for (const f of created) {
3374
+ log3.info(` ${f}`);
3375
+ }
3376
+ }
3377
+ if (skipped.length > 0) {
3378
+ log3.warn(`Skipped ${String(skipped.length)} file(s) with local modifications:`);
3379
+ for (const f of skipped) {
3380
+ log3.info(` ${f}`);
3381
+ }
3382
+ }
3383
+ if (updated.length === 0 && created.length === 0) {
3384
+ log3.info("Everything is already up to date.");
3385
+ }
3386
+ }
3387
+ function matchesRender(currentHash, newHash, projectFormatter, destinationPath, rendered) {
3388
+ if (currentHash === newHash) {
3389
+ return true;
3390
+ }
3391
+ if (!projectFormatter || typeof rendered !== "string") {
3392
+ return false;
3393
+ }
3394
+ const formatted = projectFormatter.formatContent(destinationPath, rendered);
3395
+ return formatted !== null && sha256(formatted) === currentHash;
3396
+ }
3397
+ function migrateAnswers(raw) {
3398
+ const answers = raw["answers"];
3399
+ if (!answers) {
3400
+ return;
3401
+ }
3402
+ if (answers["framework"] && !answers["uiFramework"]) {
3403
+ answers["uiFramework"] = answers["framework"];
3404
+ delete answers["framework"];
3405
+ }
3406
+ if (answers["cssMode"] && !answers["styling"]) {
3407
+ answers["styling"] = answers["cssMode"];
3408
+ delete answers["cssMode"];
3409
+ }
3410
+ if (answers["buildSystem"] && !answers["bundler"]) {
3411
+ answers["bundler"] = answers["buildSystem"];
3412
+ delete answers["buildSystem"];
3413
+ }
3414
+ answers["defaultBranch"] ??= "master";
3415
+ migrateFundingAnswers(answers);
3416
+ answers["fundingUsername"] ??= answers["authorGitHubName"];
3417
+ answers["coverageBadge"] ??= "none";
3418
+ delete answers["pluginShortName"];
3419
+ }
3420
+ function migrateFundingAnswers(answers) {
3421
+ if (answers["fundingPlatform"] !== void 0) {
3422
+ return;
3423
+ }
3424
+ delete answers["gitHubFunding"];
3425
+ const url = typeof answers["fundingUrl"] === "string" ? answers["fundingUrl"] : "";
3426
+ if (!url) {
3427
+ answers["fundingPlatform"] = "none";
3428
+ answers["fundingUrl"] = "";
3429
+ return;
3430
+ }
3431
+ function stripWww(value) {
3432
+ return value.replace("://www.", "://");
3433
+ }
3434
+ for (const option of FUNDING_PLATFORM_OPTIONS) {
3435
+ const prefix = stripWww(option.getUrl({ fundingUrl: "", fundingUsername: "" }));
3436
+ const normalizedUrl = stripWww(url);
3437
+ if (!prefix || !normalizedUrl.startsWith(prefix)) {
3438
+ continue;
3439
+ }
3440
+ const handle = normalizedUrl.slice(prefix.length).replace(/\/$/, "");
3441
+ if (!validateFundingUsername(handle)) {
3442
+ answers["fundingPlatform"] = option.settingValue;
3443
+ answers["fundingUsername"] = handle;
3444
+ answers["fundingUrl"] = "";
3445
+ return;
3446
+ }
3447
+ }
3448
+ answers["fundingPlatform"] = "custom";
3449
+ }
3450
+ function sha256(content) {
3451
+ return createHash("sha256").update(content).digest("hex");
3452
+ }
3453
+
3454
+ // src/plan-checks.ts
3455
+ var EJS_SUFFIX = ".ejs";
3456
+ var RENDER_SITE_PATTERN = /(?:<%[-=]?|\$\{)\s*render\(\s*(?:'(?<Section>[^']*)'|\{(?<Options>[^}]*)\})/g;
3457
+ var SECTION_OPTION_PATTERN = /section\s*:\s*'(?<Section>[^']*)'/;
3458
+ function collectRenderSections(templateBody) {
3459
+ const sections = [];
3460
+ for (const match of templateBody.matchAll(RENDER_SITE_PATTERN)) {
3461
+ const direct = match.groups?.["Section"];
3462
+ if (direct !== void 0) {
3463
+ sections.push(direct);
3464
+ continue;
3465
+ }
3466
+ const fromOptions = SECTION_OPTION_PATTERN.exec(match.groups?.["Options"] ?? "")?.groups?.["Section"];
3467
+ if (fromOptions !== void 0) {
3468
+ sections.push(fromOptions);
3469
+ }
3470
+ }
3471
+ return sections;
3472
+ }
3473
+ function loadTemplateInventory(templatesDir = defaultTemplatesDir()) {
3474
+ const assetTemplates = /* @__PURE__ */ new Set();
3475
+ const directTemplates = /* @__PURE__ */ new Set();
3476
+ const emptyTemplates = /* @__PURE__ */ new Set();
3477
+ const partials = [];
3478
+ const renderSites = [];
3479
+ for (const relativePath of walkTemplates(templatesDir, "")) {
3480
+ if (ASSET_EXTENSIONS.has(extname2(relativePath))) {
3481
+ assetTemplates.add(relativePath);
3482
+ continue;
3483
+ }
3484
+ if (!relativePath.endsWith(EJS_SUFFIX)) {
3485
+ continue;
3486
+ }
3487
+ const withoutSuffix = relativePath.slice(0, -EJS_SUFFIX.length);
3488
+ const body = readFileSync3(join2(templatesDir, relativePath), "utf-8");
3489
+ if (isPartialTemplatePath(relativePath)) {
3490
+ partials.push(parsePartialPath(relativePath));
3491
+ } else {
3492
+ directTemplates.add(withoutSuffix);
3493
+ if (body.trim() === "") {
3494
+ emptyTemplates.add(withoutSuffix);
3495
+ }
3496
+ }
3497
+ for (const section of collectRenderSections(body)) {
3498
+ renderSites.push({ basePath: getRenderBase(withoutSuffix), section });
3499
+ }
3500
+ }
3501
+ const wholeFilePartialsByBase = /* @__PURE__ */ new Map();
3502
+ for (const partial of partials) {
3503
+ if (partial.section !== null) {
3504
+ continue;
3505
+ }
3506
+ const names = wholeFilePartialsByBase.get(partial.basePath) ?? [];
3507
+ names.push(partial.partialName);
3508
+ wholeFilePartialsByBase.set(partial.basePath, names);
3509
+ }
3510
+ return {
3511
+ assetTemplates,
3512
+ directTemplates,
3513
+ emptyTemplates,
3514
+ partials,
3515
+ renderSites,
3516
+ wholeFilePartialsByBase
3517
+ };
3518
+ }
3519
+ function parsePartialPath(relativePath) {
3520
+ const withoutSuffix = relativePath.slice(0, -EJS_SUFFIX.length);
3521
+ const partialStart = withoutSuffix.lastIndexOf("_");
3522
+ const partialName = withoutSuffix.slice(partialStart + 1);
3523
+ const prefix = withoutSuffix.slice(0, partialStart);
3524
+ const sectionStart = prefix.lastIndexOf("@");
3525
+ if (sectionStart === -1) {
3526
+ return {
3527
+ basePath: prefix,
3528
+ partialName,
3529
+ path: relativePath,
3530
+ section: null
3531
+ };
3532
+ }
3533
+ return {
3534
+ basePath: prefix.slice(0, sectionStart),
3535
+ partialName,
3536
+ path: relativePath,
3537
+ section: prefix.slice(sectionStart + 1)
3538
+ };
3539
+ }
3540
+ function defaultTemplatesDir() {
3541
+ return join2(getScriptDir(), "..", "templates", "default");
3542
+ }
3543
+ function getRenderBase(templatePath) {
3544
+ if (!isPartialTemplatePath(templatePath)) {
3545
+ return templatePath;
3546
+ }
3547
+ const { basePath } = parsePartialPath(`${templatePath}${EJS_SUFFIX}`);
3548
+ return isPartialTemplatePath(basePath) ? getRenderBase(basePath) : templatePath;
3549
+ }
3550
+ function walkTemplates(templatesDir, relativeDir) {
3551
+ const absoluteDir = join2(templatesDir, relativeDir);
3552
+ if (!existsSync3(absoluteDir)) {
3553
+ return [];
3554
+ }
3555
+ const found = [];
3556
+ for (const entry of readdirSync(absoluteDir)) {
3557
+ const relativePath = relativeDir === "" ? entry : `${relativeDir}/${entry}`;
3558
+ if (statSync(join2(templatesDir, relativePath)).isDirectory()) {
3559
+ found.push(...walkTemplates(templatesDir, relativePath));
3560
+ } else {
3561
+ found.push(relativePath);
3562
+ }
3563
+ }
3564
+ return found;
3565
+ }
3566
+
3567
+ // src/overlay.ts
3568
+ var OVERLAY_MANIFEST_FILE_NAME = "overlay.json";
3569
+ var OVERLAY_README_FILE_NAME = "README.md";
3570
+ var EJS_SUFFIX2 = ".ejs";
3571
+ var MANIFEST_KEYS = ["badges", "files", "packages", "partials"];
3572
+ var PACKAGE_NAME_PATTERN = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
3573
+ function loadOverlay(dir, builtIn = loadTemplateInventory()) {
3574
+ const absoluteDir = resolve(dir);
3575
+ if (!existsSync4(absoluteDir) || !statSync2(absoluteDir).isDirectory()) {
3576
+ throw new Error(`Custom template directory not found: ${absoluteDir}`);
3577
+ }
3578
+ const manifestPath = join3(absoluteDir, OVERLAY_MANIFEST_FILE_NAME);
3579
+ if (!existsSync4(manifestPath)) {
3580
+ throw new Error(`Custom template directory has no ${OVERLAY_MANIFEST_FILE_NAME}: ${absoluteDir}`);
3581
+ }
3582
+ const manifest = readManifest(manifestPath);
3583
+ const overlay = { ...manifest, dir: absoluteDir };
3584
+ const problems = findOverlayProblems(overlay, loadTemplateInventory(absoluteDir), builtIn);
3585
+ if (problems.length > 0) {
3586
+ throw new Error(`Custom template ${absoluteDir} is not valid:
3587
+ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
3588
+ }
3589
+ return overlay;
3590
+ }
3591
+ function toRecordedOverlayPath(overlayDir, projectDir) {
3592
+ const relativePath = relative(projectDir, overlayDir);
3593
+ return isAbsolute(relativePath) ? relativePath : relativePath.replaceAll("\\", "/");
3594
+ }
3595
+ function findManifestProblems(overlay, own, builtIn) {
3596
+ const problems = [];
3597
+ const builtInPartialNames = new Set(builtIn.partials.map((partial) => partial.partialName));
3598
+ const builtInFiles = getEmittableFiles(builtIn);
3599
+ const ownFiles = getEmittableFiles(own);
3600
+ for (const name of overlay.partials) {
3601
+ if (!PARTIAL_NAME_PATTERN.test(name)) {
3602
+ problems.push(`Partial "${name}" is not kebab-case, so its files would not be recognized as partials.`);
3603
+ } else if (builtInPartialNames.has(name)) {
3604
+ problems.push(`Partial "${name}" is already a built-in partial name. Partial names are one namespace, so declaring it would pull the built-in "${name}" files into every project. Choose another name.`);
3605
+ } else if (!own.partials.some((partial) => partial.partialName === name)) {
3606
+ problems.push(`Partial "${name}" is declared, but no file in the overlay contributes it.`);
3607
+ }
3608
+ }
3609
+ for (const packageName of overlay.packages) {
3610
+ if (!PACKAGE_NAME_PATTERN.test(packageName)) {
3611
+ problems.push(`Package "${packageName}" is not a valid npm package name. List the name only; its version is resolved at generation time.`);
3612
+ }
3613
+ }
3614
+ for (const badge of overlay.badges) {
3615
+ if (badge.trim() === "" || /[\r\n]/.test(badge)) {
3616
+ problems.push(`Badge ${JSON.stringify(badge)} must be one non-empty line: every badge shares the README's badge line.`);
3617
+ }
3618
+ }
3619
+ for (const path of overlay.files) {
3620
+ if (path.endsWith(EJS_SUFFIX2)) {
3621
+ problems.push(`File "${path}" names a template. List the emitted path, without ${EJS_SUFFIX2}.`);
3622
+ } else if (builtInFiles.has(path)) {
3623
+ problems.push(`File "${path}" is a built-in file. To override it, place its template at the same path; listing it in "files" would emit it whatever the answers are.`);
3624
+ } else if (!ownFiles.has(path)) {
3625
+ problems.push(`File "${path}" is declared, but the overlay has no ${path}${EJS_SUFFIX2} for it.`);
3626
+ }
3627
+ }
3628
+ return problems;
3629
+ }
3630
+ function findOverlayProblems(overlay, own, builtIn) {
3631
+ return [
3632
+ ...findManifestProblems(overlay, own, builtIn),
3633
+ ...findTemplateProblems(overlay, own, builtIn),
3634
+ ...listStrayFiles(overlay.dir, own).map((path) => `${path} is neither a template (${EJS_SUFFIX2}), an asset, ${OVERLAY_MANIFEST_FILE_NAME} nor the overlay's own ${OVERLAY_README_FILE_NAME}, so it would be ignored.`)
3635
+ ];
3636
+ }
3637
+ function findTemplateProblems(overlay, own, builtIn) {
3638
+ const problems = [];
3639
+ const builtInPartialPaths = new Set(builtIn.partials.map((partial) => partial.path));
3640
+ const builtInFiles = getEmittableFiles(builtIn);
3641
+ const declaredPartials = new Set(overlay.partials);
3642
+ const declaredFiles = new Set(overlay.files);
3643
+ for (const path of own.directTemplates) {
3644
+ if (!builtInFiles.has(path) && !declaredFiles.has(path)) {
3645
+ problems.push(`${path}${EJS_SUFFIX2} overrides no built-in file and is not listed in "files", so nothing would ever emit it.`);
3646
+ }
3647
+ }
3648
+ for (const path of own.assetTemplates) {
3649
+ if (!builtInFiles.has(path) && !declaredFiles.has(path)) {
3650
+ problems.push(`${path} overrides no built-in file and is not listed in "files", so nothing would ever emit it.`);
3651
+ }
3652
+ }
3653
+ const renderSites = [...builtIn.renderSites, ...own.renderSites];
3654
+ for (const partial of own.partials) {
3655
+ if (builtInPartialPaths.has(partial.path)) {
3656
+ continue;
3657
+ }
3658
+ if (!declaredPartials.has(partial.partialName)) {
3659
+ problems.push(`${partial.path} contributes partial "${partial.partialName}", which is neither declared in "partials" nor a built-in partial file at that path.`);
3660
+ continue;
3661
+ }
3662
+ if (partial.section === null) {
3663
+ if (!builtInFiles.has(partial.basePath) && !declaredFiles.has(partial.basePath)) {
3664
+ problems.push(`${partial.path} composes ${partial.basePath}, which is neither a built-in file nor listed in "files".`);
3665
+ }
3666
+ } else if (!hasRenderSite(renderSites, partial.basePath, partial.section)) {
3667
+ problems.push(`${partial.path} answers render('${partial.section}') in ${partial.basePath}, and no template makes that call.`);
3668
+ }
3669
+ }
3670
+ return problems;
3671
+ }
3672
+ function getEmittableFiles(inventory) {
3673
+ return /* @__PURE__ */ new Set([...inventory.directTemplates, ...inventory.assetTemplates, ...inventory.wholeFilePartialsByBase.keys()]);
3674
+ }
3675
+ function hasRenderSite(renderSites, basePath, section) {
3676
+ return renderSites.some((site) => site.section === section && site.basePath === basePath);
3677
+ }
3678
+ function listStrayFiles(dir, own) {
3679
+ const accounted = /* @__PURE__ */ new Set([
3680
+ ...[...own.directTemplates].map((path) => `${path}${EJS_SUFFIX2}`),
3681
+ ...own.assetTemplates,
3682
+ ...own.partials.map((partial) => partial.path),
3683
+ OVERLAY_MANIFEST_FILE_NAME,
3684
+ OVERLAY_README_FILE_NAME
3685
+ ]);
3686
+ return walkFiles(dir, "").filter((path) => !accounted.has(path));
3687
+ }
3688
+ function readManifest(manifestPath) {
3689
+ let parsed;
3690
+ try {
3691
+ parsed = JSON.parse(readFileSync4(manifestPath, "utf-8"));
3692
+ } catch {
3693
+ throw new Error(`${manifestPath} is not valid JSON.`);
3694
+ }
3695
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3696
+ throw new Error(`${manifestPath} should contain a JSON object.`);
3697
+ }
3698
+ const record = parsed;
3699
+ for (const key of Object.keys(record)) {
3700
+ if (!MANIFEST_KEYS.includes(key)) {
3701
+ throw new Error(`${manifestPath} has an unknown key "${key}". The keys are: ${MANIFEST_KEYS.join(", ")}.`);
3702
+ }
3703
+ }
3704
+ function readList(key) {
3705
+ const value = record[key] ?? [];
3706
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
3707
+ throw new Error(`"${key}" in ${manifestPath} should be an array of strings.`);
3708
+ }
3709
+ return value;
3710
+ }
3711
+ return {
3712
+ badges: readList("badges"),
3713
+ files: readList("files"),
3714
+ packages: readList("packages"),
3715
+ partials: readList("partials")
3716
+ };
3717
+ }
3718
+ function walkFiles(root, relativeDir) {
3719
+ const found = [];
3720
+ for (const entry of readdirSync2(join3(root, relativeDir))) {
3721
+ const relativePath = relativeDir === "" ? entry : `${relativeDir}/${entry}`;
3722
+ if (statSync2(join3(root, relativePath)).isDirectory()) {
3723
+ found.push(...walkFiles(root, relativePath));
3724
+ } else {
3725
+ found.push(relativePath);
3726
+ }
3727
+ }
3728
+ return found;
3729
+ }
3730
+
3731
+ // src/project-formatter.ts
3732
+ import { spawnSync } from "node:child_process";
3733
+ import {
3734
+ existsSync as existsSync5,
3735
+ readFileSync as readFileSync5
3736
+ } from "node:fs";
3737
+ import { join as join4 } from "node:path";
3738
+ import process2 from "node:process";
3739
+ var FORMATTER_COMMANDS = {
3740
+ biome: {
3741
+ checkArgs: (destinationPath) => ["format", `--stdin-file-path=${destinationPath}`],
3742
+ packageName: "@biomejs/biome",
3743
+ writeArgs: (destinationPaths) => ["format", "--write", "--no-errors-on-unmatched", ...destinationPaths]
3744
+ },
3745
+ prettier: {
3746
+ checkArgs: (destinationPath) => ["--stdin-filepath", destinationPath],
3747
+ packageName: "prettier",
3748
+ writeArgs: (destinationPaths) => ["--write", "--ignore-unknown", ...destinationPaths]
3749
+ }
3750
+ };
3751
+ function createProjectFormatter(targetDir, formatter) {
3752
+ const cli = FORMATTER_COMMANDS[formatter];
3753
+ if (!needsInitialFormat(formatter) || cli === void 0) {
3754
+ return null;
3755
+ }
3756
+ const binPath = resolveBin(targetDir, cli.packageName);
3757
+ if (binPath === null) {
3758
+ return null;
3759
+ }
3760
+ return {
3761
+ formatContent(destinationPath, content) {
3762
+ return runCli(binPath, targetDir, cli.checkArgs(destinationPath), content);
3763
+ },
3764
+ formatFiles(destinationPaths) {
3765
+ if (destinationPaths.length > 0) {
3766
+ runCli(binPath, targetDir, cli.writeArgs(destinationPaths));
3767
+ }
3768
+ }
3769
+ };
3770
+ }
3771
+ function resolveBin(targetDir, packageName) {
3772
+ const packageDir = join4(targetDir, "node_modules", packageName);
3773
+ const packageJsonPath = join4(packageDir, "package.json");
3774
+ if (!existsSync5(packageJsonPath)) {
3775
+ return null;
3776
+ }
3777
+ const { bin } = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
3778
+ const relativeBin = typeof bin === "string" ? bin : Object.values(bin ?? {})[0];
3779
+ return relativeBin === void 0 ? null : join4(packageDir, relativeBin);
3780
+ }
3781
+ function runCli(binPath, targetDir, args, input) {
3782
+ const result = spawnSync(process2.execPath, [binPath, ...args], { cwd: targetDir, encoding: "utf-8", input });
3783
+ return result.status === 0 ? result.stdout : null;
3784
+ }
3785
+
3786
+ // src/main.ts
3787
+ var ARGV_OFFSET = 2;
3788
+ var JSON_INDENT_SPACES4 = 2;
3789
+ var SCRIPT_MODE = 493;
3790
+ function assertManifestAnswersAreListable(answers) {
3791
+ const problems = findUnlistableManifestAnswers(answers);
3792
+ if (problems.length === 0) {
3793
+ return answers;
3794
+ }
3795
+ const lines = problems.map((problem) => `"${problem.value}" is not a valid ${problem.key}. ${problem.message}.
3796
+ Pass --${problem.key}=<value> to set it yourself.`);
3797
+ exitWithUsageError(lines.join("\n\n"));
3798
+ }
3799
+ function buildSavedConfig(newConfig, answers, overlay, targetDir) {
3800
+ return overlay ? { ...newConfig, answers, customTemplate: toRecordedOverlayPath(overlay.dir, targetDir) } : { ...newConfig, answers };
3801
+ }
3802
+ async function checkForUpdates(currentVersion) {
3803
+ const latestVer = await fetchLatestVersion("@mnaoumov/create-obsidian-plugin");
3804
+ if (latestVer !== null && compare(currentVersion, latestVer) < 0) {
3805
+ log4.warn(`Your version is outdated. Latest: ${latestVer}. Update with:
3806
+ npm install -g @mnaoumov/create-obsidian-plugin`);
3807
+ }
3808
+ }
3809
+ async function detectMode(cliArgs) {
3810
+ if (cliArgs.mode) {
3811
+ return cliArgs.mode;
3812
+ }
3813
+ const configPath = join5(process.cwd(), CONFIG_FILE_NAME);
3814
+ if (existsSync6(configPath)) {
3815
+ if (cliArgs.useDefaults) {
3816
+ exitWithUsageError(`Existing project detected (${CONFIG_FILE_NAME}), and --yes cannot ask whether to update it.
3817
+ Pass --mode=update to update it, or --mode=create to scaffold a new plugin beside it.`);
3818
+ }
3819
+ const shouldUpdate = await confirm({
3820
+ message: "Existing project detected. Would you like to update it?"
3821
+ });
3822
+ assertNotCancelled(shouldUpdate);
3823
+ return shouldUpdate ? "update" /* Update */ : "create" /* Create */;
3824
+ }
3825
+ return "create" /* Create */;
3826
+ }
3827
+ async function ensurePluginIdIsUnlisted(answers, useDefaults) {
3828
+ const s = spinner();
3829
+ s.start("Checking the plugin id against the Community directory...");
3830
+ const listedIds = await fetchCommunityPluginIds();
3831
+ if (listedIds === null) {
3832
+ s.stop("Could not check the plugin id against the Community directory.");
3833
+ log4.warn(`Could not fetch community-plugins.json, so "${answers.pluginId}" was not checked for a collision with a published plugin. The directory rejects a duplicate id, and an id can never be changed once published: check it by hand before submitting.`);
3834
+ return answers;
3835
+ }
3836
+ const collision = validatePluginIdIsUnlisted(answers.pluginId, listedIds);
3837
+ if (collision === void 0) {
3838
+ s.stop("The plugin id is free in the Community directory.");
3839
+ return answers;
3840
+ }
3841
+ s.stop("The plugin id is already taken in the Community directory.");
3842
+ if (useDefaults) {
3843
+ exitWithUsageError(`${collision}.
3844
+ Pass --pluginId=<value> to choose another.`);
3845
+ }
3846
+ log4.warn(collision);
3847
+ const choice = await select2({
3848
+ initialValue: "reenter",
3849
+ message: `What should happen to "${answers.pluginId}"?`,
3850
+ options: [
3851
+ { hint: "Every other answer is kept", label: "Choose a different id", value: "reenter" },
3852
+ { hint: "Only if that listed plugin is your own", label: "Keep it anyway", value: "keep" },
3853
+ { label: "Cancel", value: "cancel" }
3854
+ ]
3855
+ });
3856
+ assertNotCancelled(choice);
3857
+ if (choice === "cancel") {
3858
+ cancel3("Aborted.");
3859
+ process.exit(0);
3860
+ }
3861
+ if (choice === "keep") {
3862
+ return answers;
3863
+ }
3864
+ const pluginId = await text2({
3865
+ message: "Plugin id (lowercase, hyphens allowed)",
3866
+ validate: makeUnlistedPluginIdValidator(listedIds)
3867
+ });
3868
+ assertNotCancelled(pluginId);
3869
+ const pluginName = answers.pluginName === makePluginName(answers.pluginId) ? makePluginName(pluginId) : answers.pluginName;
3870
+ return { ...answers, pluginId, pluginName };
3871
+ }
3872
+ function execAsync(command, cwd) {
3873
+ return new Promise((resolve2, reject) => {
3874
+ const child = spawn(command, [], {
3875
+ cwd,
3876
+ shell: true,
3877
+ stdio: "pipe"
3878
+ });
3879
+ let stdout = "";
3880
+ let stderr = "";
3881
+ child.stdout.on("data", (data) => {
3882
+ stdout += data.toString("utf-8");
3883
+ });
3884
+ child.stderr.on("data", (data) => {
3885
+ stderr += data.toString("utf-8");
3886
+ });
3887
+ child.on("close", (exitCode) => {
3888
+ if (exitCode !== 0) {
3889
+ const error = new Error(`Command failed with exit code ${String(exitCode)}`);
3890
+ error.stderr = stderr;
3891
+ reject(error);
3892
+ return;
3893
+ }
3894
+ resolve2({ stderr, stdout });
3895
+ });
3896
+ child.on("error", reject);
3897
+ });
3898
+ }
3899
+ function exitWithUsageError(message) {
3900
+ process.stderr.write(`${message}
3901
+ `);
3902
+ process.exit(1);
3903
+ }
3904
+ function loadOverlayOrExit(dir, hint = "") {
3905
+ try {
3906
+ return loadOverlay(dir);
3907
+ } catch (error) {
3908
+ exitWithUsageError(`${error instanceof Error ? error.message : String(error)}${hint}`);
3909
+ }
3910
+ }
3911
+ async function main() {
3912
+ const packageJsonPath = join5(getScriptDir(), "..", "package.json");
3913
+ const packageJson = JSON.parse(readFileSync6(packageJsonPath, "utf-8"));
3914
+ const currentVersion = packageJson.version ?? "0.0.0";
3915
+ const m = "\x1B[38;5;135m";
3916
+ const r = "\x1B[0m";
3917
+ const banner = `
3918
+ ${m}\u25C6${r}
3919
+ ${m}\u25C6 \u25C6${r}
3920
+ ${m}\u25C6 \u25C6 \u25C6${r} ${m}create-obsidian-plugin${r}
3921
+ ${m}\u25C6 \u25C6${r} ${m}v${currentVersion}${r}
3922
+ ${m}\u25C6${r} ${m}@mnaoumov${r}
3923
+
3924
+ `;
3925
+ let cliArgs;
3926
+ try {
3927
+ cliArgs = parseCliArgs(process.argv.slice(ARGV_OFFSET));
3928
+ } catch (error) {
3929
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
3930
+ `);
3931
+ process.exit(1);
3932
+ }
3933
+ if (cliArgs.showHelp) {
3934
+ process.stdout.write(`${getHelpText()}
3935
+ `);
3936
+ return;
3937
+ }
3938
+ process.stdout.write(banner);
3939
+ intro("Let's build an Obsidian plugin!");
3940
+ await checkForUpdates(currentVersion);
3941
+ const mode = await detectMode(cliArgs);
3942
+ if (mode === "create" /* Create */) {
3943
+ await runCreate(currentVersion, cliArgs);
3944
+ } else {
3945
+ await runUpdate(currentVersion, cliArgs);
3946
+ }
3947
+ }
3948
+ async function offerAnswersExport(answers, customTemplate) {
3949
+ const shell = getShellForPlatform(process.platform);
3950
+ const scriptName = `obsidian-${answers.pluginId}-create${getScriptExtension(shell)}`;
3951
+ const answersFileName = `obsidian-${answers.pluginId}-answers.json`;
3952
+ async function ask() {
3953
+ const choice2 = await select2({
3954
+ initialValue: "generate",
3955
+ message: "Your answers are collected.",
3956
+ options: [
3957
+ { hint: "Scaffold the plugin now", label: "Start generation", value: "generate" },
3958
+ { hint: scriptName, label: "Save a script for non-interactive generation", value: "script" },
3959
+ { hint: answersFileName, label: "Save an answers file for non-interactive generation", value: "answers" }
3960
+ ]
3961
+ });
3962
+ assertNotCancelled(choice2);
3963
+ return choice2;
3964
+ }
3965
+ let choice = await ask();
3966
+ while (choice !== "generate") {
3967
+ if (choice === "script") {
3968
+ const scriptPath = join5(process.cwd(), scriptName);
3969
+ writeFileSync2(scriptPath, formatCreateScript(answers, shell, customTemplate));
3970
+ if (shell === "sh") {
3971
+ chmodSync(scriptPath, SCRIPT_MODE);
3972
+ }
3973
+ log4.success(`Wrote ${scriptName}`);
3974
+ note(formatCreateCommand(answers, shell, customTemplate), "Non-interactive command");
3975
+ } else {
3976
+ writeFileSync2(join5(process.cwd(), answersFileName), formatAnswersJson(answers));
3977
+ log4.success(`Wrote ${answersFileName}`);
3978
+ const overlayFlag = customTemplate ? ` --customTemplate=${customTemplate}` : "";
3979
+ note(`npm create @mnaoumov/obsidian-plugin -- --yes --mode=create --answersFile=${answersFileName}${overlayFlag}`, "Non-interactive command");
3980
+ }
3981
+ choice = await ask();
3982
+ }
3983
+ }
3984
+ async function resolveExternalVersions(answers, overlay) {
3985
+ const s = spinner();
3986
+ s.start("Resolving versions...");
3987
+ const [resolvedVersions, minAppVersion] = await Promise.all([
3988
+ resolveVersions(buildTemplate(answers, overlay).dependencies),
3989
+ fetchLatestObsidianVersion()
3990
+ ]);
3991
+ s.stop("Versions resolved.");
3992
+ return { minAppVersion, resolvedVersions };
3993
+ }
3994
+ async function runCreate(currentVersion, cliArgs) {
3995
+ const { answers: suppliedAnswers, customTemplate, useDefaults } = cliArgs;
3996
+ const overlay = customTemplate ? loadOverlayOrExit(resolvePath(customTemplate)) : null;
3997
+ const answers = await ensurePluginIdIsUnlisted(
3998
+ assertManifestAnswersAreListable(useDefaults ? getDefaultAnswers(suppliedAnswers) : await promptAnswers(suppliedAnswers)),
3999
+ useDefaults
4000
+ );
4001
+ if (!useDefaults) {
4002
+ await offerAnswersExport(answers, customTemplate);
4003
+ }
4004
+ const targetDir = join5(process.cwd(), `obsidian-${answers.pluginId}`);
4005
+ if (existsSync6(targetDir) && !cliArgs.force) {
4006
+ if (useDefaults) {
4007
+ exitWithUsageError(`Directory obsidian-${answers.pluginId} already exists, and --yes cannot ask whether to scaffold into it.
4008
+ Pass --force to scaffold into it anyway.`);
4009
+ }
4010
+ const shouldContinue = await confirm({
4011
+ initialValue: false,
4012
+ message: `Directory obsidian-${answers.pluginId} already exists. Continue anyway?`
4013
+ });
4014
+ assertNotCancelled(shouldContinue);
4015
+ if (!shouldContinue) {
4016
+ cancel3("Aborted.");
4017
+ process.exit(0);
4018
+ }
4019
+ }
4020
+ const { minAppVersion, resolvedVersions } = await resolveExternalVersions(answers, overlay);
4021
+ const s = spinner();
4022
+ s.start("Scaffolding plugin...");
4023
+ const newConfig = copyTemplates(answers, targetDir, currentVersion, null, resolvedVersions, minAppVersion, overlay);
4024
+ const configPath = join5(targetDir, CONFIG_FILE_NAME);
4025
+ const configWithAnswers = buildSavedConfig(newConfig, answers, overlay, targetDir);
4026
+ writeFileSync2(configPath, `${JSON.stringify(configWithAnswers, null, JSON_INDENT_SPACES4)}
4027
+ `);
4028
+ s.stop("Plugin scaffolded.");
4029
+ const isFormatted = useDefaults ? false : await runPostScaffold(targetDir, answers);
4030
+ const pm = answers.packageManager;
4031
+ const dirName = `obsidian-${answers.pluginId}`;
4032
+ const needsInstall = !existsSync6(join5(targetDir, "node_modules"));
4033
+ const needsFormat = needsInitialFormat(answers.formatter) && !isFormatted;
4034
+ const steps = [
4035
+ ...needsInstall ? [getInstallCommand(pm)] : [],
4036
+ ...needsFormat ? [getRunCommand(pm, "format")] : [],
4037
+ getRunCommand(pm, "dev")
4038
+ ];
4039
+ note(`cd ${dirName} && ${steps.join(" && ")}`, "Next steps");
4040
+ outro("Happy coding!");
4041
+ }
4042
+ async function runInitialFormat(targetDir, answers, isInstalled) {
4043
+ if (!isInstalled || answers.formatter === "none") {
4044
+ return false;
4045
+ }
4046
+ const s = spinner();
4047
+ s.start("Formatting...");
4048
+ try {
4049
+ await execAsync(getRunCommand(answers.packageManager, "format"), targetDir);
4050
+ const configPath = join5(targetDir, CONFIG_FILE_NAME);
4051
+ const config = JSON.parse(readFileSync6(configPath, "utf-8"));
4052
+ writeFileSync2(configPath, `${JSON.stringify(recordHashesOnDisk(targetDir, config), null, JSON_INDENT_SPACES4)}
4053
+ `);
4054
+ s.stop("Formatted.");
4055
+ return true;
4056
+ } catch {
4057
+ s.stop("Failed to format. Run `format` manually.");
4058
+ return false;
4059
+ }
4060
+ }
4061
+ async function runPostScaffold(targetDir, answers) {
4062
+ const pm = answers.packageManager;
4063
+ const installCmd = getInstallCommand(pm);
4064
+ const shouldInstall = await confirm({
4065
+ initialValue: true,
4066
+ message: `Install dependencies with ${pm}?`
4067
+ });
4068
+ assertNotCancelled(shouldInstall);
4069
+ let isInstalled = false;
4070
+ if (shouldInstall) {
4071
+ const s = spinner();
4072
+ s.start("Installing dependencies...");
4073
+ try {
4074
+ await execAsync(installCmd, targetDir);
4075
+ isInstalled = true;
4076
+ s.stop("Dependencies installed.");
4077
+ } catch (error) {
4078
+ s.stop(`Failed to install dependencies. Run \`${installCmd}\` manually.`);
4079
+ if (error instanceof Error && "stderr" in error) {
4080
+ log4.error(String(error.stderr));
4081
+ }
4082
+ }
4083
+ }
4084
+ const isFormatted = await runInitialFormat(targetDir, answers, isInstalled);
4085
+ const shouldGitInit = await confirm({
4086
+ initialValue: true,
4087
+ message: "Initialize a git repository?"
4088
+ });
4089
+ assertNotCancelled(shouldGitInit);
4090
+ if (shouldGitInit) {
4091
+ const s = spinner();
4092
+ s.start("Initializing git repository...");
4093
+ try {
4094
+ await execAsync(`git init -b ${answers.defaultBranch}`, targetDir);
4095
+ await execAsync("git add -A", targetDir);
4096
+ await execAsync('git commit -m "Initial commit from create-obsidian-plugin"', targetDir);
4097
+ s.stop("Git repository initialized with initial commit.");
4098
+ } catch {
4099
+ s.stop("Failed to initialize git. Run `git init` manually.");
4100
+ }
4101
+ }
4102
+ const shouldCreateGitHubRepo = await confirm({
4103
+ initialValue: false,
4104
+ message: "Create a GitHub repository?"
4105
+ });
4106
+ assertNotCancelled(shouldCreateGitHubRepo);
4107
+ if (shouldCreateGitHubRepo) {
4108
+ const s = spinner();
4109
+ s.start("Creating GitHub repository...");
4110
+ try {
4111
+ await execAsync(`gh repo create obsidian-${answers.pluginId} --public --source=. --push`, targetDir);
4112
+ s.stop("GitHub repository created and pushed.");
4113
+ } catch {
4114
+ s.stop("Failed to create GitHub repo. Make sure `gh` CLI is installed and authenticated.");
4115
+ }
4116
+ }
4117
+ return isFormatted;
4118
+ }
4119
+ async function runUpdate(currentVersion, cliArgs) {
4120
+ const { answers: suppliedAnswers, customTemplate, useDefaults } = cliArgs;
4121
+ const targetDir = process.cwd();
4122
+ const existingConfig = loadConfig(targetDir);
4123
+ if (!existingConfig) {
4124
+ log4.error("No config file found. Cannot update.");
4125
+ process.exit(1);
4126
+ }
4127
+ log4.info(`Current project was generated with v${existingConfig.generatorVersion}`);
4128
+ let overlay = null;
4129
+ if (customTemplate !== void 0) {
4130
+ overlay = customTemplate ? loadOverlayOrExit(resolvePath(customTemplate)) : null;
4131
+ } else if (existingConfig.customTemplate) {
4132
+ overlay = loadOverlayOrExit(
4133
+ resolvePath(targetDir, existingConfig.customTemplate),
4134
+ `
4135
+ This project records it in ${CONFIG_FILE_NAME}. Pass --customTemplate=<dir> to point at where it is now, or --customTemplate= to stop using it.`
4136
+ );
4137
+ log4.info(`Applying the custom template recorded for this project: ${existingConfig.customTemplate}`);
4138
+ }
4139
+ const configPath = join5(targetDir, CONFIG_FILE_NAME);
4140
+ const savedConfig = JSON.parse(readFileSync6(configPath, "utf-8"));
4141
+ let answers;
4142
+ if (savedConfig.answers) {
4143
+ const saved = { ...savedConfig.answers, ...suppliedAnswers };
4144
+ log4.info("Using saved answers from previous generation.");
4145
+ const shouldRePrompt = useDefaults ? false : await confirm({
4146
+ initialValue: false,
4147
+ message: "Would you like to change any settings?"
4148
+ });
4149
+ assertNotCancelled(shouldRePrompt);
4150
+ if (shouldRePrompt) {
4151
+ answers = await promptAnswers(saved);
4152
+ } else {
4153
+ answers = saved;
4154
+ }
4155
+ } else {
4156
+ if (useDefaults) {
4157
+ exitWithUsageError(`${CONFIG_FILE_NAME} records no answers, so --yes has none to reuse.
4158
+ Run without --yes to answer them again.`);
4159
+ }
4160
+ log4.warn("No saved answers found. Please provide the settings again.");
4161
+ answers = await promptAnswers(suppliedAnswers);
4162
+ }
4163
+ const savedPluginId = savedConfig.answers?.pluginId;
4164
+ if (savedPluginId !== void 0 && answers.pluginId !== savedPluginId) {
4165
+ answers = await ensurePluginIdIsUnlisted(answers, useDefaults);
4166
+ }
4167
+ const { minAppVersion, resolvedVersions } = await resolveExternalVersions(answers, overlay);
4168
+ const projectFormatter = createProjectFormatter(targetDir, answers.formatter);
4169
+ if (projectFormatter === null && needsInitialFormat(answers.formatter)) {
4170
+ log4.warn(`${answers.formatter} is not installed, so the update cannot tell the files it formatted from hand edits and skips them. Install the dependencies and update again to have them updated.`);
4171
+ }
4172
+ const s = spinner();
4173
+ s.start("Updating project files...");
4174
+ copyTemplates(answers, targetDir, currentVersion, existingConfig, resolvedVersions, minAppVersion, overlay, projectFormatter);
4175
+ s.stop("Update complete.");
4176
+ const newConfig = loadConfig(targetDir);
4177
+ if (newConfig) {
4178
+ const configWithAnswers = buildSavedConfig(newConfig, answers, overlay, targetDir);
4179
+ writeFileSync2(configPath, `${JSON.stringify(configWithAnswers, null, JSON_INDENT_SPACES4)}
4180
+ `);
4181
+ }
4182
+ outro("Project updated successfully!");
4183
+ }
4184
+ await main();