@mnaoumov/create-obsidian-plugin 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +137 -0
- package/README.md +24 -332
- package/dist/main.js +48 -117
- package/package.json +13 -4
- package/src/main.ts +2 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 1.4.0 (2026-11-01)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- The `official-sample` preset writes the `LICENSE` copyright line as `Copyright (C) <year> by <author>`, keeping upstream's 0BSD text.
|
|
10
|
+
- A See also section compares similar scaffolders, in the README and on the docs site.
|
|
11
|
+
- `CHANGELOG.md`, back-filled from the tags, linked from the README and the docs site.
|
|
12
|
+
- Tool names are marked as code in all Markdown prose, with a test that catches a bare one.
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
|
|
16
|
+
- The `enhanced` preset alias: `--preset=enhanced` now fails validation like any unknown value.
|
|
17
|
+
- Every migration of an older `.create-obsidian-plugin.json` answer shape (`framework`, `cssMode`, `buildSystem`, `gitHubFunding`, a missing `defaultBranch` or `coverageBadge`, a stored `pluginShortName`).
|
|
18
|
+
|
|
19
|
+
[Full diff](https://github.com/mnaoumov/create-obsidian-plugin/compare/1.3.0...1.4.0)
|
|
20
|
+
## 1.3.0 (2026-11-01)
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
- **cli:** quote the -- separator in --help and the answers-file hint
|
|
25
|
+
|
|
26
|
+
[Full diff](https://github.com/mnaoumov/create-obsidian-plugin/compare/1.2.0...1.3.0)
|
|
27
|
+
|
|
28
|
+
## 1.2.0 (2026-11-01)
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
- **template:** add a Similar plugins section to the generated README
|
|
33
|
+
- **template:** let a parenthesized folder group templates without entering their path
|
|
34
|
+
- **issue-templates:** give the bug form field ids that bug-report links prefill
|
|
35
|
+
- tell AI agents how to file an issue when they skip the forms
|
|
36
|
+
- scaffold the Report a bug command whenever issue forms are chosen
|
|
37
|
+
|
|
38
|
+
### Fixes
|
|
39
|
+
|
|
40
|
+
- **deps:** float brace-expansion to 5.0.12 and fast-uri to 3.1.9 past their advisories
|
|
41
|
+
- **versions:** drop the smol-toml advisory override now that typescript-dev-toolkit 3.7.3 pins 1.9.1
|
|
42
|
+
- **templates:** select .obsidian-lib in the framework stylesheets
|
|
43
|
+
- **templates:** show the plugin name as code in the demo sample view
|
|
44
|
+
- **gate:** report a missing package manager as could not run, not a failed install
|
|
45
|
+
- **bundler:** leave esbuild to obsidian-dev-toolkit on the framework presets
|
|
46
|
+
- **lint:** discover root ESLint config for scripts
|
|
47
|
+
- **lint:** reconcile unused binding checks with ESLint 10
|
|
48
|
+
- **templates:** format Solid readonly configuration comment
|
|
49
|
+
- override vulnerable development KaTeX dependency
|
|
50
|
+
- **demo:** avoid keyword identifier in number setting callback
|
|
51
|
+
- **demo:** stop scaffolding the gitignored scratch note
|
|
52
|
+
- **versions:** override re2js to ^2.9.1 in scaffolded projects for GHSA-gg6h-894w-4m9c
|
|
53
|
+
- **spell-checker:** know "toolkits" in the emitted cspell.json
|
|
54
|
+
- **versions:** hold toolkit-declared packages in the toolkit's range
|
|
55
|
+
|
|
56
|
+
[Full diff](https://github.com/mnaoumov/create-obsidian-plugin/compare/1.1.0...1.2.0)
|
|
57
|
+
|
|
58
|
+
## 1.1.0 (2026-10-21)
|
|
59
|
+
|
|
60
|
+
### Features
|
|
61
|
+
|
|
62
|
+
- scaffold onto @mnaoumov/obsidian-lib and the @mnaoumov toolkits
|
|
63
|
+
- **templates:** take commitlint and nano-staged configs from typescript-dev-toolkit
|
|
64
|
+
- **preset:** keep preset "enhanced" working as a deprecated alias of all-in-one
|
|
65
|
+
- **preset:** label the preset All-in-one Framework and lead its hint with the helper library
|
|
66
|
+
- **preset:** add the Official sample plugin preset
|
|
67
|
+
- **docs-showcase:** extract, wrap and type-check the developer docs' code blocks
|
|
68
|
+
- **preset:** add the Developer docs showcase preset
|
|
69
|
+
- **upstream-sync:** watch every upstream and open one update PR per change
|
|
70
|
+
- **docs-showcase:** link each snippet's docs page from what its command shows
|
|
71
|
+
- **templates:** link Discussions from the issue chooser
|
|
72
|
+
- **formatter:** run prettier and biome through the toolkit runners
|
|
73
|
+
- **test-runner:** run jest through the toolkits on the framework presets
|
|
74
|
+
- **e2e:** run wdio-obsidian and obsidian-test through obsidian-dev-toolkit
|
|
75
|
+
- **github-actions:** call obsidian-dev-toolkit's reusable workflows
|
|
76
|
+
- **bundler:** build every bundler through obsidian-dev-toolkit's adapters
|
|
77
|
+
- **scaffold:** emit .nvmrc so CI runs the project's Node, not the latest LTS
|
|
78
|
+
- **docs-showcase:** recognize a code-examples-only LICENSE and refuse unknown ones
|
|
79
|
+
|
|
80
|
+
### Fixes
|
|
81
|
+
|
|
82
|
+
- **demo-vault:** import configureCommunityPlugin from @mnaoumov/obsidian-lib
|
|
83
|
+
- **dprint:** keep JSX attributes double-quoted
|
|
84
|
+
- **versions:** override smol-toml to clear GHSA-r4xh-jqrq-34v2
|
|
85
|
+
- **verify-tarball:** read npm 12's keyed npm pack --json output as well as the array
|
|
86
|
+
- **templates:** emit nano-staged config patterns in sorted order
|
|
87
|
+
- **templates:** require CodeScript Toolkit 13.8.0 for the demo-vault injection
|
|
88
|
+
- **templates:** drop the preact view's import-x/no-deprecated directives for preact 11
|
|
89
|
+
- **templates:** remove the three directives the previous commit left staged out
|
|
90
|
+
- **cli:** let render:case and gate:case read a deprecated answer value as the CLI does
|
|
91
|
+
- **update:** ask every question again when the user chooses to change settings
|
|
92
|
+
- **templates:** pass the framework scaffolds' own lint, format and audit gate
|
|
93
|
+
- **verify:** read the pins step's checks from under the pins key
|
|
94
|
+
- **versions:** pin @codemirror/state and view to obsidian 1.14.4's peers
|
|
95
|
+
- **prompts:** offer "Keep current tooling" on update and make "Use recommended defaults" apply them
|
|
96
|
+
- **cli-args:** look the mode up instead of asserting a string to the Mode enum
|
|
97
|
+
- **templates:** clear what the toolkit runners and verify:projects found
|
|
98
|
+
- **templates:** keep biome and dprint agreeing on the framework configs
|
|
99
|
+
- **standalone:** clear the gate failures the wdio install used to hide
|
|
100
|
+
- **templates:** reach the CSS module through its class names so vite keeps it
|
|
101
|
+
- **test:** declare sweepCases' void return so tsc accepts the generator
|
|
102
|
+
- **templates:** ignore coverage/ in git and cspell, spell-check staged dotfiles
|
|
103
|
+
- **scaffold:** install the git hooks after git init and make the first commit conventional
|
|
104
|
+
- **demo-vault:** stop linking to the gitignored scratch note
|
|
105
|
+
- **cspell:** know the word the template's word list adds for the demo vault .gitignore
|
|
106
|
+
- **plugin-drift:** leave generator-scaffolded plugins out of the real-plugin set
|
|
107
|
+
- **versions:** read the smol-toml pin off typescript-dev-toolkit's markdownlint-cli2
|
|
108
|
+
- **templates:** mark the plugin name as code in the README and demo-vault prose
|
|
109
|
+
|
|
110
|
+
[Full diff](https://github.com/mnaoumov/create-obsidian-plugin/compare/1.0.2...1.1.0)
|
|
111
|
+
|
|
112
|
+
## 1.0.2 (2026-10-10)
|
|
113
|
+
|
|
114
|
+
### Features
|
|
115
|
+
|
|
116
|
+
- verify obsidian-dev-utils source links against its checkout
|
|
117
|
+
|
|
118
|
+
### Fixes
|
|
119
|
+
|
|
120
|
+
- **verify-tarball:** pack with --ignore-scripts so HUSKY=0 output cannot break the JSON
|
|
121
|
+
- **templates:** point dead upstream links at their current homes
|
|
122
|
+
|
|
123
|
+
[Full diff](https://github.com/mnaoumov/create-obsidian-plugin/compare/1.0.1...1.0.2)
|
|
124
|
+
|
|
125
|
+
## 1.0.1 (2026-10-07)
|
|
126
|
+
|
|
127
|
+
### Other
|
|
128
|
+
|
|
129
|
+
- publish to npm from GitHub Actions with trusted publishing
|
|
130
|
+
|
|
131
|
+
[Full diff](https://github.com/mnaoumov/create-obsidian-plugin/compare/1.0.0...1.0.1)
|
|
132
|
+
|
|
133
|
+
## 1.0.0 (2026-10-07)
|
|
134
|
+
|
|
135
|
+
Initial release.
|
|
136
|
+
|
|
137
|
+
[Commits](https://github.com/mnaoumov/create-obsidian-plugin/commits/1.0.0)
|
package/README.md
CHANGED
|
@@ -4,354 +4,50 @@
|
|
|
4
4
|
|
|
5
5
|
A scaffolding CLI that generates [Obsidian](https://obsidian.md/) plugin projects from modern templates. Supports creating new plugins and updating existing ones.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| Package manager | Runner | Command |
|
|
10
|
-
|-----------------|--------|---------------------------------------------|
|
|
11
|
-
| npm | create | `npm create @mnaoumov/obsidian-plugin` |
|
|
12
|
-
| npm | npx | `npx @mnaoumov/create-obsidian-plugin` |
|
|
13
|
-
| pnpm | create | `pnpm create @mnaoumov/obsidian-plugin` |
|
|
14
|
-
| pnpm | dlx | `pnpm dlx @mnaoumov/create-obsidian-plugin` |
|
|
15
|
-
| yarn | create | `yarn create @mnaoumov/obsidian-plugin` |
|
|
16
|
-
| yarn | dlx | `yarn dlx @mnaoumov/create-obsidian-plugin` |
|
|
17
|
-
| bun | create | `bun create @mnaoumov/obsidian-plugin` |
|
|
18
|
-
| bun | bunx | `bunx @mnaoumov/create-obsidian-plugin` |
|
|
19
|
-
|
|
20
|
-
This walks you through an interactive wizard to scaffold a new Obsidian plugin project with optional post-scaffold actions (npm install, git init, GitHub repo creation).
|
|
21
|
-
|
|
22
|
-
The wizard first asks for the project's own details — plugin id, name and description, your name and GitHub username, the default branch (`main` unless you say otherwise; `git init` creates it and the CI workflow triggers on it), where people can support you (a funding platform and your username on it, or any URL), and the path to a test vault's config folder — then the feature options below.
|
|
23
|
-
|
|
24
|
-
The id, name and description are checked against what the Obsidian Community directory's automated review enforces, and a rejection says which rule you hit. That matters most for the id: the directory re-reads your `manifest.json` when you submit, and an id can never be changed once your plugin is published.
|
|
25
|
-
|
|
26
|
-
## Update an existing plugin
|
|
7
|
+
**Documentation: <https://mnaoumov.dev/create-obsidian-plugin/>** — every option and prompt, the generated project, updating, custom templates and more.
|
|
27
8
|
|
|
28
|
-
|
|
9
|
+
## Create a new plugin
|
|
29
10
|
|
|
30
11
|
```bash
|
|
31
12
|
npm create @mnaoumov/obsidian-plugin
|
|
32
13
|
```
|
|
33
14
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- Detect the existing project via `.create-obsidian-plugin.json`
|
|
37
|
-
- Compare file hashes to detect local modifications
|
|
38
|
-
- Update unmodified files to the latest template version
|
|
39
|
-
- Skip files you've customized (with a warning)
|
|
40
|
-
- Create any new files added to the template
|
|
41
|
-
|
|
42
|
-
## Non-interactive use
|
|
15
|
+
This walks you through an interactive wizard, then scaffolds the project into a new `obsidian-<plugin id>` folder. `pnpm`, `yarn` and `bun` work too: see [Getting started](https://mnaoumov.dev/create-obsidian-plugin/guides/getting-started/).
|
|
43
16
|
|
|
44
|
-
|
|
17
|
+
Run the same command inside a project it created to [update it](https://mnaoumov.dev/create-obsidian-plugin/guides/updating/) to the latest templates, keeping the files you changed.
|
|
45
18
|
|
|
46
|
-
|
|
47
|
-
|--------|--------------|
|
|
48
|
-
| `-y`, `--yes` | Take the default for every unanswered question, and skip the post-scaffold prompts, so nothing is installed, formatted or committed. The closing `Next steps` line lists what is left, including `format` on prettier and biome, which the project's own `format:check` fails until it has run once. It never asks anything: a question no flag answers stops the run with an error naming the flag |
|
|
49
|
-
| `--mode=create\|update` | Create a new plugin, or update the project in the current directory, without being asked which. Under `--yes` it is required when the current directory holds a `.create-obsidian-plugin.json` |
|
|
50
|
-
| `--force` | Scaffold into an `obsidian-<pluginId>` directory that already exists, instead of being asked. Under `--yes` it is required when that directory exists |
|
|
51
|
-
| `-h`, `--help` | List every option, including the accepted values for each answer |
|
|
52
|
-
| `--answersFile=<path>` | Read answers from a JSON file |
|
|
53
|
-
| `--customTemplate=<dir>` | Layer your own templates over the built-in ones — see [Your own templates](#your-own-templates) |
|
|
54
|
-
| `--latest` | With `--preset=official-sample`: copy the official sample plugin as it is upstream today, rather than the snapshot this release ships — see [Official sample plugin](#official-sample-plugin) |
|
|
55
|
-
| `--<answer>=<value>` | Set one answer, e.g. `--packageManager=yarn` |
|
|
19
|
+
## Example
|
|
56
20
|
|
|
57
|
-
|
|
21
|
+
Answer every question up front and skip the prompts:
|
|
58
22
|
|
|
59
23
|
```bash
|
|
60
|
-
npm create @mnaoumov/obsidian-plugin -- --yes --pluginId=my-tool --packageManager=
|
|
61
|
-
pnpm create @mnaoumov/obsidian-plugin --yes --pluginId=my-tool --packageManager=pnpm
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Answers are applied in order, so the most specific wins: built-in defaults, then the answers saved in an existing project, then `--answersFile`, then individual flags. An answer given this way is not asked about again. On an update, `--yes` reuses the project's saved answers with any flags applied over them, instead of asking whether to change them.
|
|
65
|
-
|
|
66
|
-
The answers file is a JSON object using the same names. It also accepts a whole `.create-obsidian-plugin.json`, so you can point it at an existing project to scaffold another one like it:
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
{
|
|
70
|
-
"preset": "all-in-one",
|
|
71
|
-
"bundler": "esbuild",
|
|
72
|
-
"packageManager": "npm",
|
|
73
|
-
"pluginId": "my-tool"
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Invalid input is refused rather than quietly ignored: an unknown answer, a value outside what a question accepts, a plugin id the wizard itself would reject, or `--currentYear` and `--pluginShortName`, which the generator computes rather than asks for.
|
|
78
|
-
|
|
79
|
-
### Saving the answers from an interactive run
|
|
80
|
-
|
|
81
|
-
At the end of the wizard, before scaffolding, you can save what you just answered as either form — a runnable script (`.cmd` on Windows, `.sh` elsewhere) or an answers file. You can save both and then generate; saving does not end the run. Every answer is written, not only the ones that differ from a default, so the recipe keeps producing the same project after a release changes a default.
|
|
82
|
-
|
|
83
|
-
## Your own templates
|
|
84
|
-
|
|
85
|
-
When the answers cannot express what every plugin of yours needs, keep it in a directory of your own and pass `--customTemplate=<dir>`. The directory mirrors the generator's `templates/default` and is searched ahead of it, so it uses the same EJS templates and the same partials:
|
|
86
|
-
|
|
87
|
-
```text
|
|
88
|
-
my-obsidian-template/
|
|
89
|
-
overlay.json
|
|
90
|
-
README.md@support_mine.ejs # adds a section at an existing render('support') seam
|
|
91
|
-
LICENSE.ejs # replaces the built-in LICENSE
|
|
92
|
-
docs/NOTES.md.ejs # a file the built-ins do not have
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
```json
|
|
96
|
-
{
|
|
97
|
-
"partials": ["mine"],
|
|
98
|
-
"files": ["docs/NOTES.md"],
|
|
99
|
-
"packages": ["type-fest"],
|
|
100
|
-
"badges": ["[](https://example.com)"]
|
|
101
|
-
}
|
|
24
|
+
npm create @mnaoumov/obsidian-plugin "--" --yes --pluginId=my-tool --packageManager=npm
|
|
102
25
|
```
|
|
103
26
|
|
|
104
|
-
|
|
105
|
-
- A folder whose name is in parentheses, such as the built-ins' `scripts/(build)/`, only groups templates and is not part of their path. So `scripts/build.ts.ejs` and `scripts/(build)/build.ts.ejs` are the same template, and you may group your own files the same way or not at all.
|
|
106
|
-
- `partials` names what your partial files contribute. The names share one namespace with the built-in partials, so a name the generator already uses is refused. Your partials render after the built-in ones at every seam.
|
|
107
|
-
- `files` lists the paths you add, without `.ejs`. A built-in path is refused there; to replace one, just place its template.
|
|
108
|
-
- `packages` are added to `devDependencies`, and their versions are resolved when you generate, as the built-in ones are.
|
|
109
|
-
- `badges` go after the built-in badges, on the same README line.
|
|
110
|
-
|
|
111
|
-
The directory is checked before any question is asked. A template that nothing would ever emit, a partial nobody declared, a section no template renders, or any other file (except `overlay.json` and a `README.md` of its own) stops the run with the reason.
|
|
112
|
-
|
|
113
|
-
The directory is recorded in `.create-obsidian-plugin.json`, relative to the project, and an update applies it again. That keeps the files it produced updating cleanly rather than looking hand-edited. Pass `--customTemplate=<dir>` on an update to point at a moved directory, or `--customTemplate=` to stop using one. A recorded directory that is missing stops the update instead of quietly reverting your files to the built-ins.
|
|
114
|
-
|
|
115
|
-
## Feature options
|
|
116
|
-
|
|
117
|
-
The wizard lets you pick and choose from the following categories:
|
|
118
|
-
|
|
119
|
-
### Preset
|
|
120
|
-
|
|
121
|
-
| Option | Description |
|
|
122
|
-
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
123
|
-
| All-in-one Framework | Recommended. The author's framework: [@mnaoumov/obsidian-lib](https://github.com/mnaoumov/obsidian-lib), a library of ready-made helpers for settings, commands, modals, notices, file and link handling, and more, plus the @mnaoumov toolkits for building, linting, testing and releases. `"preset": "enhanced"` still works as a deprecated alias |
|
|
124
|
-
| Demo | All features enabled for demonstration |
|
|
125
|
-
| Developer docs showcase | The All-in-one Framework project, plus every code block of the [Obsidian developer docs](https://docs.obsidian.md) as a command you can run. See [Developer docs showcase](#developer-docs-showcase) |
|
|
126
|
-
| Standalone | Standalone plugin without the @mnaoumov library dependencies |
|
|
127
|
-
| Official sample plugin | Exactly [obsidianmd/obsidian-sample-plugin](https://github.com/obsidianmd/obsidian-sample-plugin), with your plugin's id, name, author and description filled in. See [Official sample plugin](#official-sample-plugin) |
|
|
128
|
-
|
|
129
|
-
#### Official sample plugin
|
|
130
|
-
|
|
131
|
-
`"preset": "official-sample"` gives you Obsidian's own sample plugin as it is, rather than a project built from this tool's templates. No other question is asked: upstream has already chosen its tooling (esbuild, ESLint with `eslint-plugin-obsidianmd`, npm), and a tooling flag beside this preset is refused.
|
|
132
|
-
|
|
133
|
-
Only the plugin's identity changes:
|
|
134
|
-
|
|
135
|
-
- `manifest.json`: `id`, `name`, `description`, `author`, `authorUrl` (your GitHub profile) and `fundingUrl`, which is removed if you choose no funding platform
|
|
136
|
-
- `package.json` and `package-lock.json`: the package name, `obsidian-<id>`
|
|
137
|
-
- `README.md`: the title
|
|
138
|
-
|
|
139
|
-
Every other file is upstream's, byte for byte, including its 0BSD `LICENSE` and its `AGENTS.md`. The generator adds only its own `.create-obsidian-plugin.json`, which is what lets an update work the way it does for every preset.
|
|
140
|
-
|
|
141
|
-
The files come from a snapshot this release ships, pinned to an upstream commit in `upstreams.json`, so generating works offline and gives the same result every time. Pass `--latest` to copy upstream as it is today instead; if upstream cannot be reached, the shipped snapshot is used with a warning.
|
|
142
|
-
|
|
143
|
-
Upstream's `npm run lint` fails on a fresh copy, and on upstream itself, until you rename the sample classes and remove the sample code: `eslint-plugin-obsidianmd` checks for exactly that. `npm run build` passes.
|
|
144
|
-
|
|
145
|
-
#### Developer docs showcase
|
|
146
|
-
|
|
147
|
-
`"preset": "docs-showcase"` generates the All-in-one Framework project and adds `src/docs/`: every code block of [obsidianmd/obsidian-developer-docs](https://github.com/obsidianmd/obsidian-developer-docs), each one a command. Open the command palette, type `Docs:`, and run `Docs: Vault > Modify files` to see that block of the Vault page work against a scratch note.
|
|
148
|
-
|
|
149
|
-
Each block is kept as the docs show it. The generator adds only what the docs leave to the reader: the imports a block leaves out, and a host that gives the block a plugin to register things on, a note to work with, an editor, or an element to draw into. A block that declares a plugin class is loaded as a child plugin, and running its command again unloads it. A stylesheet is applied and removed the same way.
|
|
150
|
-
|
|
151
|
-
`src/docs/README.md` lists every block of the docs. A block that does not compile against the `obsidian` API, or that is not plugin code (shell commands, JSON samples, diagrams), is listed there with the reason rather than left out silently.
|
|
152
|
-
|
|
153
|
-
The blocks are type-checked by `src/docs/tsconfig.json`, which carries the official sample plugin's compiler options, so the project's own stricter `tsconfig.json`, linter, formatter and spell checker leave the folder alone. With the vitest answer, `npm run test:integration:desktop` runs every `Docs:` command in a real Obsidian and fails on any that throws.
|
|
154
|
-
|
|
155
|
-
The blocks come from a snapshot this release ships, pinned to an upstream commit in `upstreams.json`. Maintainers refresh it with `npm run sync:docs-showcase`.
|
|
156
|
-
|
|
157
|
-
### Bundler
|
|
158
|
-
|
|
159
|
-
| Option | Description |
|
|
160
|
-
|---------|------------------------------------------|
|
|
161
|
-
| esbuild | Fast and simple (recommended) |
|
|
162
|
-
| Parcel | Zero-config bundler |
|
|
163
|
-
| Rollup | Flexible with plugin ecosystem |
|
|
164
|
-
| Vite | Modern dev server with HMR |
|
|
165
|
-
| Webpack | Mature bundler with broad plugin support |
|
|
166
|
-
|
|
167
|
-
### UI framework
|
|
27
|
+
Keep the quotes around `"--"`: in PowerShell a bare `--` is swallowed before `npm` sees it. See [The -- separator in PowerShell](https://mnaoumov.dev/create-obsidian-plugin/guides/powershell-separator/). Every option is listed in [Command-line options](https://mnaoumov.dev/create-obsidian-plugin/guides/command-line/).
|
|
168
28
|
|
|
169
|
-
|
|
170
|
-
|--------|-------------------------------------------|
|
|
171
|
-
| (none) | Plain TypeScript, no UI framework |
|
|
172
|
-
| Lit | Web Components with declarative templates |
|
|
173
|
-
| Preact | Lightweight React alternative (3kB) |
|
|
174
|
-
| React | Component-based UI with JSX |
|
|
175
|
-
| Solid | Fine-grained reactivity, no virtual DOM |
|
|
176
|
-
| Svelte | Lightweight reactive components |
|
|
177
|
-
| Vue | Progressive framework with SFC |
|
|
29
|
+
[Sample Plugin Extended](https://github.com/mnaoumov/obsidian-sample-plugin-extended) is a plugin generated with this tool.
|
|
178
30
|
|
|
179
|
-
|
|
31
|
+
## See also
|
|
180
32
|
|
|
181
|
-
|
|
182
|
-
|--------|---------------------------------------------|
|
|
183
|
-
| (none) | No linting |
|
|
184
|
-
| Biome | Fast linter and formatter |
|
|
185
|
-
| ESLint | Industry standard for JavaScript/TypeScript |
|
|
186
|
-
|
|
187
|
-
### Formatter
|
|
188
|
-
|
|
189
|
-
| Option | Description |
|
|
190
|
-
|----------|------------------------------------------|
|
|
191
|
-
| (none) | No formatting |
|
|
192
|
-
| Biome | Fast formatter (pairs with Biome linter) |
|
|
193
|
-
| dprint | Fast, pluggable, written in Rust |
|
|
194
|
-
| Prettier | Opinionated, widely adopted |
|
|
195
|
-
|
|
196
|
-
### Spell checker
|
|
197
|
-
|
|
198
|
-
| Option | Description |
|
|
199
|
-
|--------|-------------------------------------|
|
|
200
|
-
| (none) | No spell checking |
|
|
201
|
-
| cspell | Configurable spell checker for code |
|
|
202
|
-
|
|
203
|
-
### Markdown linter
|
|
204
|
-
|
|
205
|
-
| Option | Description |
|
|
206
|
-
|--------------|-----------------------------------------------|
|
|
207
|
-
| (none) | No Markdown linting |
|
|
208
|
-
| markdownlint | Lint Markdown files for style and consistency |
|
|
209
|
-
|
|
210
|
-
### Unit testing
|
|
211
|
-
|
|
212
|
-
| Option | Description |
|
|
213
|
-
|--------|------------------------------|
|
|
214
|
-
| (none) | No unit testing |
|
|
215
|
-
| Jest | Feature-rich, widely adopted |
|
|
216
|
-
| Vitest | Fast, Vite-native, ESM-first |
|
|
217
|
-
|
|
218
|
-
### E2E testing
|
|
219
|
-
|
|
220
|
-
| Option | Description |
|
|
221
|
-
|---------------|-----------------------------------------------------------|
|
|
222
|
-
| (none) | No E2E testing |
|
|
223
|
-
| obsidian-test | Tests run inside Obsidian with real app APIs |
|
|
224
|
-
| wdio-obsidian | WebdriverIO service for Obsidian, multi-version and CI/CD |
|
|
225
|
-
|
|
226
|
-
### Editor extensions
|
|
227
|
-
|
|
228
|
-
| Option | Description |
|
|
229
|
-
|------------|------------------------------------------------------|
|
|
230
|
-
| (none) | No editor extensions |
|
|
231
|
-
| CodeMirror | CodeMirror 6 state fields, view plugins, decorations |
|
|
232
|
-
|
|
233
|
-
### Styling
|
|
234
|
-
|
|
235
|
-
| Option | Description |
|
|
236
|
-
|--------------|------------------------------------------|
|
|
237
|
-
| (none) | No custom styles |
|
|
238
|
-
| CSS | Plain CSS styles |
|
|
239
|
-
| CSS Modules | Scoped CSS with .module.css files |
|
|
240
|
-
| PostCSS | CSS with plugins (autoprefixer, nesting) |
|
|
241
|
-
| SCSS | Sass/SCSS preprocessor |
|
|
242
|
-
| Tailwind CSS | Utility-first CSS framework |
|
|
243
|
-
|
|
244
|
-
### WebAssembly support
|
|
245
|
-
|
|
246
|
-
| Option | Description |
|
|
247
|
-
|--------|------------------------------|
|
|
248
|
-
| (none) | No WASM |
|
|
249
|
-
| WASM | Import and use .wasm modules |
|
|
250
|
-
|
|
251
|
-
### Commit linting
|
|
252
|
-
|
|
253
|
-
| Option | Description |
|
|
254
|
-
|----------------------|----------------------------------|
|
|
255
|
-
| (none) | No commit linting |
|
|
256
|
-
| Conventional Commits | commitlint + husky + lint-staged |
|
|
257
|
-
|
|
258
|
-
### Hot reload
|
|
259
|
-
|
|
260
|
-
| Option | Description |
|
|
261
|
-
|-------------------|-----------------------------------------------------------------------------------------|
|
|
262
|
-
| (none) | No hot reload support |
|
|
263
|
-
| Hot Reload Plugin | Creates `.hotreload` marker for [Hot Reload](https://community.obsidian.md/plugins/hot-reload) |
|
|
264
|
-
| Obsidian CLI | Reloads via `obsidian plugin:reload` CLI command |
|
|
265
|
-
|
|
266
|
-
### Internationalization
|
|
267
|
-
|
|
268
|
-
| Option | Description |
|
|
269
|
-
|---------------|-----------------------------------------------|
|
|
270
|
-
| (none) | No internationalization |
|
|
271
|
-
| i18next | Popular i18n framework with JSON translations |
|
|
272
|
-
| typesafe-i18n | Type-safe i18n with auto-generated types |
|
|
273
|
-
|
|
274
|
-
### GitHub Actions
|
|
275
|
-
|
|
276
|
-
| Option | Description |
|
|
277
|
-
|--------------|--------------------------------------|
|
|
278
|
-
| (none) | No GitHub Actions workflows |
|
|
279
|
-
| CI + Release | CI + automated release on tag push |
|
|
280
|
-
| CI | CI workflow only (lint, test, build) |
|
|
281
|
-
|
|
282
|
-
### GitHub issue templates
|
|
283
|
-
|
|
284
|
-
| Option | Description |
|
|
285
|
-
|---------------|------------------------------------------|
|
|
286
|
-
| (none) | No issue templates |
|
|
287
|
-
| Bug & Feature | Bug report and feature request templates |
|
|
288
|
-
|
|
289
|
-
### Coverage badge
|
|
290
|
-
|
|
291
|
-
| Option | Description |
|
|
292
|
-
|----------------|----------------------------------------------------------------|
|
|
293
|
-
| (none) | No coverage badge |
|
|
294
|
-
| Coverage badge | A `coverage: 100%` badge at the end of the README's badge line |
|
|
295
|
-
|
|
296
|
-
### Funding
|
|
297
|
-
|
|
298
|
-
Asked with the project details, not under Customize. The platform and your username on it produce the `fundingUrl` in `manifest.json`, the README's badge and `## Support` link, and a `.github/FUNDING.yml` with that one platform's line. `(none)` emits none of them.
|
|
299
|
-
|
|
300
|
-
| Option | Description |
|
|
301
|
-
|-----------------|------------------------------------------------------------|
|
|
302
|
-
| (none) | No funding link, FUNDING.yml or badge |
|
|
303
|
-
| Buy Me a Coffee | buymeacoffee.com/<username> |
|
|
304
|
-
| GitHub Sponsors | github.com/sponsors/<username> |
|
|
305
|
-
| Ko-fi | ko-fi.com/<username> |
|
|
306
|
-
| Liberapay | liberapay.com/<username> |
|
|
307
|
-
| Open Collective | opencollective.com/<username> |
|
|
308
|
-
| Patreon | patreon.com/<username> |
|
|
309
|
-
| Polar | polar.sh/<username> |
|
|
310
|
-
| thanks.dev | thanks.dev/<username>, e.g. `u/gh/<GitHub username>` |
|
|
311
|
-
| Custom URL | Any other URL, asked for as a URL rather than a username |
|
|
312
|
-
|
|
313
|
-
### Obsidian API subset
|
|
314
|
-
|
|
315
|
-
| Option | Description |
|
|
316
|
-
|-----------------------|------------------------------------------------------------|
|
|
317
|
-
| Official API | Stable, documented API only |
|
|
318
|
-
| Official + Unofficial | Includes internal undocumented APIs (for experienced devs) |
|
|
319
|
-
|
|
320
|
-
### Package manager
|
|
321
|
-
|
|
322
|
-
| Option | Description |
|
|
323
|
-
|--------|-------------------------------------|
|
|
324
|
-
| Bun | Fast all-in-one JavaScript toolkit |
|
|
325
|
-
| npm | Default, comes with Node.js |
|
|
326
|
-
| pnpm | Fast, efficient disk space |
|
|
327
|
-
| Yarn | Classic alternative with workspaces |
|
|
328
|
-
|
|
329
|
-
### Platform support
|
|
330
|
-
|
|
331
|
-
| Option | Description |
|
|
332
|
-
|--------------------|------------------------------------|
|
|
333
|
-
| Desktop and mobile | Plugin works on desktop and mobile |
|
|
334
|
-
| Desktop only | Plugin works only on desktop |
|
|
335
|
-
|
|
336
|
-
## Configuration file
|
|
337
|
-
|
|
338
|
-
The `.create-obsidian-plugin.json` file stores:
|
|
339
|
-
|
|
340
|
-
- `generatorVersion` — version of the generator that created the project
|
|
341
|
-
- `fileHashes` — SHA-256 hashes of generated files for update detection
|
|
342
|
-
- `answers` — every answer, which an update reuses
|
|
343
|
-
- `customTemplate` — the [custom template](#your-own-templates) directory, if one was used
|
|
33
|
+
[My other Obsidian resources](https://github.com/mnaoumov/obsidian-resources)
|
|
344
34
|
|
|
345
|
-
|
|
35
|
+
| Tool | Features | How `create-obsidian-plugin` differs |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| [`@aoout/create-obsidian-plugin`](https://www.npmjs.com/package/@aoout/create-obsidian-plugin) | `npm create` wizard, forked from zephraph's `create-obsidian-plugin`; `i18next`; a tag-triggered release workflow; a desktop-only option; `git init` and an optional GitHub repository | Same goal; adds: presets; a choice of bundler, UI framework, linter, formatter and test runner; an update in place that keeps the files you edited; `--yes` and answers export |
|
|
38
|
+
| [`cookiecutter-obsidian-plugin`](https://github.com/serafinovsky/cookiecutter-obsidian-plugin) | A Python `cookiecutter` template; TypeScript, `esbuild`, `eslint`, `prettier`; optional `vitest` and i18n; a release workflow | Same goal; runs on Node.js through `npm create`, with no Python; adds: presets; five bundlers; UI frameworks; an update in place |
|
|
39
|
+
| [`create-obsidian-plugin`](https://www.npmjs.com/package/create-obsidian-plugin) | The `npm init obsidian-plugin` wizard by zephraph; asks for the id and name; a TypeScript plugin whose `dev` and `build` scripts run `obsidian-plugin-cli` | Same goal; adds: presets; a tooling choice in each category; an update in place; `--yes` and answers export |
|
|
40
|
+
| [`create-obsidian-plugin-pnpm`](https://www.npmjs.com/package/create-obsidian-plugin-pnpm) | An `npm create` scaffolder that copies one fixed template; `pnpm`, strict TypeScript, `esbuild`, `eslint`; CI and draft-release workflows; a declarative settings example | Same goal; adds: `npm`, `yarn` and `bun`; presets; a choice of bundler, UI framework, linter, formatter and test runner; an update in place |
|
|
41
|
+
| [`dsebastien/obsidian-plugin-template`](https://github.com/dsebastien/obsidian-plugin-template) | A GitHub template repository; `bun`, strict TypeScript, `tailwindcss` 4, `eslint`, `prettier`, `husky` with `lint-staged`, `commitizen`; CI and release workflows | A generator rather than a template you copy and rename by hand; fills in your plugin's identity from prompts or flags; a choice in each tooling category; an update in place |
|
|
42
|
+
| [`generator-obsidian-plugin`](https://github.com/mnaoumov/generator-obsidian-plugin) (my tool) | A Yeoman generator (`yo obsidian-plugin`); `obsidian-typings`, `eslint`, `cspell`, `dprint`, `obsidian-dev-utils` scripts; `svelte` and `react` examples; `sass` | Supersedes it; needs no global `yo`; adds: presets; a choice in each tooling category; an update in place that keeps the files you edited; `--yes` |
|
|
43
|
+
| [`nolebase/obsidian-plugin-template`](https://github.com/nolebase/obsidian-plugin-template) | A GitHub template repository; TypeScript, `unbuild`, `eslint`, `cspell`, `pnpm`; publishing on release | A generator rather than a template you copy and rename; five bundlers, `unbuild` not among them; UI frameworks; an update in place |
|
|
44
|
+
| [`obsidianmd/obsidian-sample-plugin`](https://github.com/obsidianmd/obsidian-sample-plugin) | Obsidian's official template repository; TypeScript, `esbuild`, `eslint` with `eslint-plugin-obsidianmd`, `npm`; sample code | Extends it: the `official-sample` preset copies it with your id, name, author, description and license holder filled in, and can update it later; the other presets add a choice of tooling |
|
|
346
45
|
|
|
347
|
-
|
|
348
|
-
- **Logicless templates** — no `<% if %>` conditionals; conditional content via partials
|
|
349
|
-
- **Two-tier scripts** — standalone has full inline scripts; All-in-one Framework wraps `@mnaoumov/obsidian-dev-toolkit` and `@mnaoumov/typescript-dev-toolkit`
|
|
350
|
-
- **Root configs are thin wrappers** — actual logic lives in `scripts/`
|
|
46
|
+
Last searched for similar tools on 2026-11-01.
|
|
351
47
|
|
|
352
|
-
##
|
|
48
|
+
## Changelog
|
|
353
49
|
|
|
354
|
-
|
|
50
|
+
All notable changes to this project will be documented in the [CHANGELOG](./CHANGELOG.md).
|
|
355
51
|
|
|
356
52
|
## Contributing
|
|
357
53
|
|
|
@@ -371,10 +67,6 @@ Open issues through the [issue forms](https://github.com/mnaoumov/create-obsidia
|
|
|
371
67
|
|
|
372
68
|
Filing from the CLI or the API (`gh issue create --body`, or an AI agent filing for you) skips the form, and `gh issue create --template` cannot load an issue form. In that case, open the matching form in [`.github/ISSUE_TEMPLATE/`](https://github.com/mnaoumov/create-obsidian-plugin/tree/HEAD/.github/ISSUE_TEMPLATE), copy every field as a `### <label>` heading in the form's order, and fill in each one. Always state the version you used and your operating system.
|
|
373
69
|
|
|
374
|
-
## My other Obsidian resources
|
|
375
|
-
|
|
376
|
-
[My other Obsidian resources](https://github.com/mnaoumov/obsidian-resources)
|
|
377
|
-
|
|
378
70
|
## License
|
|
379
71
|
|
|
380
72
|
© [Michael Naumov](https://github.com/mnaoumov/)
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
cancel as cancel3,
|
|
6
6
|
confirm,
|
|
7
7
|
intro,
|
|
8
|
-
log as
|
|
8
|
+
log as log4,
|
|
9
9
|
note,
|
|
10
10
|
outro,
|
|
11
11
|
select as select2,
|
|
@@ -179,7 +179,7 @@ var OFFICIAL_SAMPLE_ANSWER_KEYS = [
|
|
|
179
179
|
var OfficialSample = class extends FeatureOption {
|
|
180
180
|
constructor() {
|
|
181
181
|
super({
|
|
182
|
-
promptHint: "Exactly obsidianmd/obsidian-sample-plugin, with your plugin's id, name, author and
|
|
182
|
+
promptHint: "Exactly obsidianmd/obsidian-sample-plugin, with your plugin's id, name, author, description and license holder filled in",
|
|
183
183
|
promptLabel: "Official sample plugin",
|
|
184
184
|
settingValue: OFFICIAL_SAMPLE_PRESET
|
|
185
185
|
});
|
|
@@ -2555,26 +2555,8 @@ function getPluginShortName(pluginId) {
|
|
|
2555
2555
|
return pluginId.split("-").map((word) => (word[0] ?? "").toUpperCase() + word.slice(1)).join("");
|
|
2556
2556
|
}
|
|
2557
2557
|
|
|
2558
|
-
// src/answers/deprecated-answers.ts
|
|
2559
|
-
import { log as log2 } from "@clack/prompts";
|
|
2560
|
-
var DEPRECATED_ANSWER_VALUES = {
|
|
2561
|
-
preset: {
|
|
2562
|
-
enhanced: "all-in-one"
|
|
2563
|
-
}
|
|
2564
|
-
};
|
|
2565
|
-
function resolveDeprecatedAnswer(key, value, source) {
|
|
2566
|
-
const replacement = DEPRECATED_ANSWER_VALUES[key]?.[value];
|
|
2567
|
-
if (replacement === void 0) {
|
|
2568
|
-
return value;
|
|
2569
|
-
}
|
|
2570
|
-
log2.warn(`${key} "${value}" in ${source} is deprecated and was read as "${replacement}". Use "${replacement}" instead.`);
|
|
2571
|
-
return replacement;
|
|
2572
|
-
}
|
|
2573
|
-
|
|
2574
2558
|
// src/answers/cli-args.ts
|
|
2575
2559
|
var COMPUTED_ANSWER_KEYS = ["currentYear"];
|
|
2576
|
-
var DERIVED_TEMPLATE_KEYS = ["pluginShortName"];
|
|
2577
|
-
var REFUSED_KEYS = [...COMPUTED_ANSWER_KEYS, ...DERIVED_TEMPLATE_KEYS];
|
|
2578
2560
|
var ANSWERS_FILE_PREFIX = "--answersFile=";
|
|
2579
2561
|
var CUSTOM_TEMPLATE_PREFIX = "--customTemplate=";
|
|
2580
2562
|
var MODE_PREFIX = "--mode=";
|
|
@@ -2612,7 +2594,11 @@ function getAnswerableKeys() {
|
|
|
2612
2594
|
}
|
|
2613
2595
|
function getHelpText() {
|
|
2614
2596
|
const lines = [
|
|
2615
|
-
|
|
2597
|
+
'Usage: npm create @mnaoumov/obsidian-plugin ["--" <options>]',
|
|
2598
|
+
"",
|
|
2599
|
+
'npm needs the "--" before the options; pnpm, yarn and bun do not. Keep its quotes: PowerShell',
|
|
2600
|
+
"swallows a bare --. See https://mnaoumov.dev/create-obsidian-plugin/guides/powershell-separator/",
|
|
2601
|
+
"Full documentation: https://mnaoumov.dev/create-obsidian-plugin/",
|
|
2616
2602
|
"",
|
|
2617
2603
|
"Options:",
|
|
2618
2604
|
" -y, --yes Take every default and skip the post-scaffold prompts. Never asks:",
|
|
@@ -2677,7 +2663,7 @@ function parseCliArgs(argv) {
|
|
|
2677
2663
|
}
|
|
2678
2664
|
const [rawKey, rawValue] = splitFlag(argument);
|
|
2679
2665
|
const key = toAnswerKey(rawKey);
|
|
2680
|
-
flagAnswers[key] = resolveAnswer(key, rawValue
|
|
2666
|
+
flagAnswers[key] = resolveAnswer(key, rawValue);
|
|
2681
2667
|
}
|
|
2682
2668
|
const fileAnswers = answersFilePath === null ? {} : readAnswersFile(answersFilePath);
|
|
2683
2669
|
const isOfficialSample = isOfficialSamplePreset(flagAnswers.preset ?? fileAnswers.preset);
|
|
@@ -2696,13 +2682,12 @@ function parseCliArgs(argv) {
|
|
|
2696
2682
|
useDefaults
|
|
2697
2683
|
};
|
|
2698
2684
|
}
|
|
2699
|
-
function resolveAnswer(key, value
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
return resolved;
|
|
2685
|
+
function resolveAnswer(key, value) {
|
|
2686
|
+
assertValidAnswer(key, value);
|
|
2687
|
+
return value;
|
|
2703
2688
|
}
|
|
2704
2689
|
function toAnswerKey(key) {
|
|
2705
|
-
if (
|
|
2690
|
+
if (COMPUTED_ANSWER_KEYS.includes(key)) {
|
|
2706
2691
|
throw new Error(`${key} is computed by the generator, not answered, so it cannot be set.`);
|
|
2707
2692
|
}
|
|
2708
2693
|
const answerable = getAnswerableKeys();
|
|
@@ -2757,14 +2742,14 @@ function readAnswersFile(path) {
|
|
|
2757
2742
|
const source = typeof wrapped === "object" && wrapped !== null ? wrapped : record;
|
|
2758
2743
|
const answers = {};
|
|
2759
2744
|
for (const [rawKey, value] of Object.entries(source)) {
|
|
2760
|
-
if (
|
|
2745
|
+
if (COMPUTED_ANSWER_KEYS.includes(rawKey)) {
|
|
2761
2746
|
continue;
|
|
2762
2747
|
}
|
|
2763
2748
|
if (typeof value !== "string") {
|
|
2764
2749
|
throw new Error(`${rawKey} in ${path} should be a string, got ${typeof value}.`);
|
|
2765
2750
|
}
|
|
2766
2751
|
const key = toAnswerKey(rawKey);
|
|
2767
|
-
answers[key] = resolveAnswer(key, value
|
|
2752
|
+
answers[key] = resolveAnswer(key, value);
|
|
2768
2753
|
}
|
|
2769
2754
|
return answers;
|
|
2770
2755
|
}
|
|
@@ -2883,7 +2868,7 @@ function validatePluginIdIsUnlisted(pluginId, listedIds) {
|
|
|
2883
2868
|
import { join as join5 } from "node:path";
|
|
2884
2869
|
|
|
2885
2870
|
// src/templates.ts
|
|
2886
|
-
import { log as
|
|
2871
|
+
import { log as log3 } from "@clack/prompts";
|
|
2887
2872
|
import ejs from "ejs";
|
|
2888
2873
|
import { createHash } from "node:crypto";
|
|
2889
2874
|
import {
|
|
@@ -3165,7 +3150,7 @@ function walkFiles(root, relativeDir) {
|
|
|
3165
3150
|
}
|
|
3166
3151
|
|
|
3167
3152
|
// src/versions.ts
|
|
3168
|
-
import { log as
|
|
3153
|
+
import { log as log2 } from "@clack/prompts";
|
|
3169
3154
|
import {
|
|
3170
3155
|
maxSatisfying,
|
|
3171
3156
|
minVersion,
|
|
@@ -3468,7 +3453,7 @@ async function resolveVersions(dependencies) {
|
|
|
3468
3453
|
}
|
|
3469
3454
|
resolved.set(packageName, resolution.spec);
|
|
3470
3455
|
if (resolution.heldBelow !== null) {
|
|
3471
|
-
|
|
3456
|
+
log2.info(`Holding ${packageName} on ${resolution.spec}, the range ${resolution.heldBy ?? ""} declares, rather than the latest ${resolution.heldBelow}.`);
|
|
3472
3457
|
}
|
|
3473
3458
|
}
|
|
3474
3459
|
for (const constrainer of CONSTRAINING_PACKAGES) {
|
|
@@ -3493,7 +3478,7 @@ async function resolveVersions(dependencies) {
|
|
|
3493
3478
|
record(rest[index] ?? "", resolution);
|
|
3494
3479
|
}
|
|
3495
3480
|
if (failed.length > 0) {
|
|
3496
|
-
|
|
3481
|
+
log2.warn(`Could not look up the latest version of ${failed.join(", ")}. Falling back to "${FALLBACK_VERSION}".`);
|
|
3497
3482
|
}
|
|
3498
3483
|
return resolved;
|
|
3499
3484
|
}
|
|
@@ -3802,10 +3787,7 @@ function loadConfig(dir) {
|
|
|
3802
3787
|
if (!existsSync4(configPath)) {
|
|
3803
3788
|
return null;
|
|
3804
3789
|
}
|
|
3805
|
-
|
|
3806
|
-
const raw = parsed;
|
|
3807
|
-
migrateAnswers(raw);
|
|
3808
|
-
return parsed;
|
|
3790
|
+
return JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
3809
3791
|
}
|
|
3810
3792
|
function pinPresetAnswers(answers) {
|
|
3811
3793
|
return answers.preset === "demo" ? { ...answers, ...DEMO_PINNED_ANSWERS } : answers;
|
|
@@ -3947,25 +3929,25 @@ function importedModule(block) {
|
|
|
3947
3929
|
}
|
|
3948
3930
|
function logUpdateSummary(updated, created, skipped) {
|
|
3949
3931
|
if (updated.length > 0) {
|
|
3950
|
-
|
|
3932
|
+
log3.success(`Updated ${String(updated.length)} file(s):`);
|
|
3951
3933
|
for (const f of updated) {
|
|
3952
|
-
|
|
3934
|
+
log3.info(` ${f}`);
|
|
3953
3935
|
}
|
|
3954
3936
|
}
|
|
3955
3937
|
if (created.length > 0) {
|
|
3956
|
-
|
|
3938
|
+
log3.success(`Created ${String(created.length)} new file(s):`);
|
|
3957
3939
|
for (const f of created) {
|
|
3958
|
-
|
|
3940
|
+
log3.info(` ${f}`);
|
|
3959
3941
|
}
|
|
3960
3942
|
}
|
|
3961
3943
|
if (skipped.length > 0) {
|
|
3962
|
-
|
|
3944
|
+
log3.warn(`Skipped ${String(skipped.length)} file(s) with local modifications:`);
|
|
3963
3945
|
for (const f of skipped) {
|
|
3964
|
-
|
|
3946
|
+
log3.info(` ${f}`);
|
|
3965
3947
|
}
|
|
3966
3948
|
}
|
|
3967
3949
|
if (updated.length === 0 && created.length === 0) {
|
|
3968
|
-
|
|
3950
|
+
log3.info("Everything is already up to date.");
|
|
3969
3951
|
}
|
|
3970
3952
|
}
|
|
3971
3953
|
function matchesRender(currentHash, newHash, projectFormatter, destinationPath, rendered) {
|
|
@@ -3978,64 +3960,6 @@ function matchesRender(currentHash, newHash, projectFormatter, destinationPath,
|
|
|
3978
3960
|
const formatted = projectFormatter.formatContent(destinationPath, rendered);
|
|
3979
3961
|
return formatted !== null && sha256(formatted) === currentHash;
|
|
3980
3962
|
}
|
|
3981
|
-
function migrateAnswers(raw) {
|
|
3982
|
-
const answers = raw["answers"];
|
|
3983
|
-
if (!answers) {
|
|
3984
|
-
return;
|
|
3985
|
-
}
|
|
3986
|
-
if (answers["framework"] && !answers["uiFramework"]) {
|
|
3987
|
-
answers["uiFramework"] = answers["framework"];
|
|
3988
|
-
delete answers["framework"];
|
|
3989
|
-
}
|
|
3990
|
-
if (answers["cssMode"] && !answers["styling"]) {
|
|
3991
|
-
answers["styling"] = answers["cssMode"];
|
|
3992
|
-
delete answers["cssMode"];
|
|
3993
|
-
}
|
|
3994
|
-
if (answers["buildSystem"] && !answers["bundler"]) {
|
|
3995
|
-
answers["bundler"] = answers["buildSystem"];
|
|
3996
|
-
delete answers["buildSystem"];
|
|
3997
|
-
}
|
|
3998
|
-
answers["defaultBranch"] ??= "master";
|
|
3999
|
-
migrateFundingAnswers(answers);
|
|
4000
|
-
answers["fundingUsername"] ??= answers["authorGitHubName"];
|
|
4001
|
-
answers["coverageBadge"] ??= "none";
|
|
4002
|
-
delete answers["pluginShortName"];
|
|
4003
|
-
for (const [key, value] of Object.entries(answers)) {
|
|
4004
|
-
if (typeof value === "string") {
|
|
4005
|
-
answers[key] = resolveDeprecatedAnswer(key, value, CONFIG_FILE_NAME);
|
|
4006
|
-
}
|
|
4007
|
-
}
|
|
4008
|
-
}
|
|
4009
|
-
function migrateFundingAnswers(answers) {
|
|
4010
|
-
if (answers["fundingPlatform"] !== void 0) {
|
|
4011
|
-
return;
|
|
4012
|
-
}
|
|
4013
|
-
delete answers["gitHubFunding"];
|
|
4014
|
-
const url = typeof answers["fundingUrl"] === "string" ? answers["fundingUrl"] : "";
|
|
4015
|
-
if (!url) {
|
|
4016
|
-
answers["fundingPlatform"] = "none";
|
|
4017
|
-
answers["fundingUrl"] = "";
|
|
4018
|
-
return;
|
|
4019
|
-
}
|
|
4020
|
-
function stripWww(value) {
|
|
4021
|
-
return value.replace("://www.", "://");
|
|
4022
|
-
}
|
|
4023
|
-
for (const option of FUNDING_PLATFORM_OPTIONS) {
|
|
4024
|
-
const prefix = stripWww(option.getUrl({ fundingUrl: "", fundingUsername: "" }));
|
|
4025
|
-
const normalizedUrl = stripWww(url);
|
|
4026
|
-
if (!prefix || !normalizedUrl.startsWith(prefix)) {
|
|
4027
|
-
continue;
|
|
4028
|
-
}
|
|
4029
|
-
const handle = normalizedUrl.slice(prefix.length).replace(/\/$/, "");
|
|
4030
|
-
if (!validateFundingUsername(handle)) {
|
|
4031
|
-
answers["fundingPlatform"] = option.settingValue;
|
|
4032
|
-
answers["fundingUsername"] = handle;
|
|
4033
|
-
answers["fundingUrl"] = "";
|
|
4034
|
-
return;
|
|
4035
|
-
}
|
|
4036
|
-
}
|
|
4037
|
-
answers["fundingPlatform"] = "custom";
|
|
4038
|
-
}
|
|
4039
3963
|
function sha256(content) {
|
|
4040
3964
|
return createHash("sha256").update(content).digest("hex");
|
|
4041
3965
|
}
|
|
@@ -4163,6 +4087,10 @@ function applyOfficialSampleIdentity(files, answers) {
|
|
|
4163
4087
|
if (readme) {
|
|
4164
4088
|
result.set("README.md", Buffer.from(readme.toString("utf-8").replace(/^# .*$/m, `# ${answers.pluginName}`), "utf-8"));
|
|
4165
4089
|
}
|
|
4090
|
+
const license = result.get("LICENSE");
|
|
4091
|
+
if (license) {
|
|
4092
|
+
result.set("LICENSE", Buffer.from(license.toString("utf-8").replace(/^Copyright \(C\) [^\r\n]*/m, getOfficialSampleCopyrightLine(answers)), "utf-8"));
|
|
4093
|
+
}
|
|
4166
4094
|
return result;
|
|
4167
4095
|
}
|
|
4168
4096
|
function copyOfficialSample(answers, targetDir, currentVersion, existingConfig, snapshot) {
|
|
@@ -4175,6 +4103,9 @@ function copyOfficialSample(answers, targetDir, currentVersion, existingConfig,
|
|
|
4175
4103
|
);
|
|
4176
4104
|
return { ...config, upstreamCommit: snapshot.commit };
|
|
4177
4105
|
}
|
|
4106
|
+
function getOfficialSampleCopyrightLine(answers) {
|
|
4107
|
+
return `Copyright (C) ${String(answers.currentYear)} by ${answers.authorName}`;
|
|
4108
|
+
}
|
|
4178
4109
|
function getPackageRoot() {
|
|
4179
4110
|
return join5(getScriptDir(), "..");
|
|
4180
4111
|
}
|
|
@@ -4579,7 +4510,7 @@ function buildSavedConfig(newConfig, answers, overlay, targetDir) {
|
|
|
4579
4510
|
async function checkForUpdates(currentVersion) {
|
|
4580
4511
|
const latestVer = await fetchLatestVersion("@mnaoumov/create-obsidian-plugin");
|
|
4581
4512
|
if (latestVer !== null && compare(currentVersion, latestVer) < 0) {
|
|
4582
|
-
|
|
4513
|
+
log4.warn(`Your version is outdated. Latest: ${latestVer}. Update with:
|
|
4583
4514
|
npm install -g @mnaoumov/create-obsidian-plugin`);
|
|
4584
4515
|
}
|
|
4585
4516
|
}
|
|
@@ -4607,7 +4538,7 @@ async function ensurePluginIdIsUnlisted(answers, useDefaults) {
|
|
|
4607
4538
|
const listedIds = await fetchCommunityPluginIds();
|
|
4608
4539
|
if (listedIds === null) {
|
|
4609
4540
|
s.stop("Could not check the plugin id against the Community directory.");
|
|
4610
|
-
|
|
4541
|
+
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.`);
|
|
4611
4542
|
return answers;
|
|
4612
4543
|
}
|
|
4613
4544
|
const collision = validatePluginIdIsUnlisted(answers.pluginId, listedIds);
|
|
@@ -4620,7 +4551,7 @@ async function ensurePluginIdIsUnlisted(answers, useDefaults) {
|
|
|
4620
4551
|
exitWithUsageError(`${collision}.
|
|
4621
4552
|
Pass --pluginId=<value> to choose another.`);
|
|
4622
4553
|
}
|
|
4623
|
-
|
|
4554
|
+
log4.warn(collision);
|
|
4624
4555
|
const choice = await select2({
|
|
4625
4556
|
initialValue: "reenter",
|
|
4626
4557
|
message: `What should happen to "${answers.pluginId}"?`,
|
|
@@ -4751,13 +4682,13 @@ async function offerAnswersExport(answers, customTemplate) {
|
|
|
4751
4682
|
if (shell === "sh") {
|
|
4752
4683
|
chmodSync(scriptPath, SCRIPT_MODE);
|
|
4753
4684
|
}
|
|
4754
|
-
|
|
4685
|
+
log4.success(`Wrote ${scriptName}`);
|
|
4755
4686
|
note(formatCreateCommand(answers, shell, customTemplate), "Non-interactive command");
|
|
4756
4687
|
} else {
|
|
4757
4688
|
writeFileSync2(join9(process.cwd(), answersFileName), formatAnswersJson(answers));
|
|
4758
|
-
|
|
4689
|
+
log4.success(`Wrote ${answersFileName}`);
|
|
4759
4690
|
const overlayFlag = customTemplate ? ` --customTemplate=${customTemplate}` : "";
|
|
4760
|
-
note(`npm create @mnaoumov/obsidian-plugin -- --yes --mode=create --answersFile=${answersFileName}${overlayFlag}`, "Non-interactive command");
|
|
4691
|
+
note(`npm create @mnaoumov/obsidian-plugin "--" --yes --mode=create --answersFile=${answersFileName}${overlayFlag}`, "Non-interactive command");
|
|
4761
4692
|
}
|
|
4762
4693
|
choice = await ask();
|
|
4763
4694
|
}
|
|
@@ -4786,7 +4717,7 @@ async function resolveOfficialSampleSnapshot(latest) {
|
|
|
4786
4717
|
return snapshot;
|
|
4787
4718
|
} catch (error) {
|
|
4788
4719
|
s.stop(`Could not fetch ${upstream.repo}.`);
|
|
4789
|
-
|
|
4720
|
+
log4.warn(`${error instanceof Error ? error.message : String(error)}
|
|
4790
4721
|
Using the snapshot this release vendored, ${vendored.commit.slice(0, SHORT_SHA_LENGTH)}.`);
|
|
4791
4722
|
return vendored;
|
|
4792
4723
|
}
|
|
@@ -4890,7 +4821,7 @@ async function runPostScaffold(targetDir, answers) {
|
|
|
4890
4821
|
} catch (error) {
|
|
4891
4822
|
s.stop(`Failed to install dependencies. Run \`${installCmd}\` manually.`);
|
|
4892
4823
|
if (error instanceof Error && "stderr" in error) {
|
|
4893
|
-
|
|
4824
|
+
log4.error(String(error.stderr));
|
|
4894
4825
|
}
|
|
4895
4826
|
}
|
|
4896
4827
|
}
|
|
@@ -4914,7 +4845,7 @@ async function runPostScaffold(targetDir, answers) {
|
|
|
4914
4845
|
} catch (error) {
|
|
4915
4846
|
s.stop("Failed to initialize git. Run `git init` manually.");
|
|
4916
4847
|
if (error instanceof Error && "stderr" in error) {
|
|
4917
|
-
|
|
4848
|
+
log4.error(String(error.stderr));
|
|
4918
4849
|
}
|
|
4919
4850
|
}
|
|
4920
4851
|
}
|
|
@@ -4940,10 +4871,10 @@ async function runUpdate(currentVersion, cliArgs) {
|
|
|
4940
4871
|
const targetDir = process.cwd();
|
|
4941
4872
|
const existingConfig = loadConfig(targetDir);
|
|
4942
4873
|
if (!existingConfig) {
|
|
4943
|
-
|
|
4874
|
+
log4.error("No config file found. Cannot update.");
|
|
4944
4875
|
process.exit(1);
|
|
4945
4876
|
}
|
|
4946
|
-
|
|
4877
|
+
log4.info(`Current project was generated with v${existingConfig.generatorVersion}`);
|
|
4947
4878
|
let overlay = null;
|
|
4948
4879
|
if (customTemplate !== void 0) {
|
|
4949
4880
|
overlay = customTemplate ? loadOverlayOrExit(resolvePath(customTemplate)) : null;
|
|
@@ -4953,14 +4884,14 @@ async function runUpdate(currentVersion, cliArgs) {
|
|
|
4953
4884
|
`
|
|
4954
4885
|
This project records it in ${CONFIG_FILE_NAME}. Pass --customTemplate=<dir> to point at where it is now, or --customTemplate= to stop using it.`
|
|
4955
4886
|
);
|
|
4956
|
-
|
|
4887
|
+
log4.info(`Applying the custom template recorded for this project: ${existingConfig.customTemplate}`);
|
|
4957
4888
|
}
|
|
4958
4889
|
const configPath = join9(targetDir, CONFIG_FILE_NAME);
|
|
4959
4890
|
const savedConfig = JSON.parse(readFileSync8(configPath, "utf-8"));
|
|
4960
4891
|
let answers;
|
|
4961
4892
|
if (savedConfig.answers) {
|
|
4962
4893
|
const saved = { ...savedConfig.answers, ...suppliedAnswers };
|
|
4963
|
-
|
|
4894
|
+
log4.info("Using saved answers from previous generation.");
|
|
4964
4895
|
const shouldRePrompt = useDefaults ? false : await confirm({
|
|
4965
4896
|
initialValue: false,
|
|
4966
4897
|
message: "Would you like to change any settings?"
|
|
@@ -4976,7 +4907,7 @@ This project records it in ${CONFIG_FILE_NAME}. Pass --customTemplate=<dir> to p
|
|
|
4976
4907
|
exitWithUsageError(`${CONFIG_FILE_NAME} records no answers, so --yes has none to reuse.
|
|
4977
4908
|
Run without --yes to answer them again.`);
|
|
4978
4909
|
}
|
|
4979
|
-
|
|
4910
|
+
log4.warn("No saved answers found. Please provide the settings again.");
|
|
4980
4911
|
answers = await promptAnswers(suppliedAnswers);
|
|
4981
4912
|
}
|
|
4982
4913
|
const savedPluginId = savedConfig.answers?.pluginId;
|
|
@@ -4998,7 +4929,7 @@ Run without --yes to answer them again.`);
|
|
|
4998
4929
|
const { minAppVersion, resolvedVersions } = await resolveExternalVersions(answers, overlay);
|
|
4999
4930
|
const projectFormatter = createProjectFormatter(targetDir, answers.formatter);
|
|
5000
4931
|
if (projectFormatter === null && needsInitialFormat(answers.formatter)) {
|
|
5001
|
-
|
|
4932
|
+
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.`);
|
|
5002
4933
|
}
|
|
5003
4934
|
const s = spinner();
|
|
5004
4935
|
s.start("Updating project files...");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mnaoumov/create-obsidian-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Create Obsidian plugin from modern templates.",
|
|
5
|
-
"homepage": "https://
|
|
5
|
+
"homepage": "https://mnaoumov.dev/create-obsidian-plugin/",
|
|
6
6
|
"bugs": "https://github.com/mnaoumov/create-obsidian-plugin/issues",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "jiti scripts/build.ts",
|
|
29
29
|
"commit": "jiti scripts/commit.ts",
|
|
30
|
+
"docs:build": "jiti scripts/docs-build.ts",
|
|
31
|
+
"docs:dev": "jiti scripts/docs-dev.ts",
|
|
32
|
+
"docs:preview": "jiti scripts/docs-preview.ts",
|
|
30
33
|
"format": "jiti scripts/format.ts",
|
|
31
34
|
"format:check": "jiti scripts/format-check.ts",
|
|
32
35
|
"gate:case": "jiti scripts/gate-one-case.ts",
|
|
@@ -57,24 +60,28 @@
|
|
|
57
60
|
"typescript": "$typescript"
|
|
58
61
|
},
|
|
59
62
|
"dependencies": {
|
|
60
|
-
"@clack/core": "^1.
|
|
61
|
-
"@clack/prompts": "^1.
|
|
63
|
+
"@clack/core": "^1.5.1",
|
|
64
|
+
"@clack/prompts": "^1.8.1",
|
|
62
65
|
"ejs": "^6.0.1",
|
|
63
66
|
"semver": "^7.8.5"
|
|
64
67
|
},
|
|
65
68
|
"devDependencies": {
|
|
69
|
+
"@astrojs/markdown-satteri": "^0.4.6",
|
|
70
|
+
"@astrojs/starlight": "^0.42.8",
|
|
66
71
|
"@commitlint/cli": "^21.2.2",
|
|
67
72
|
"@commitlint/config-conventional": "^21.2.2",
|
|
68
73
|
"@commitlint/types": "^21.2.0",
|
|
69
74
|
"@eslint-community/eslint-plugin-eslint-comments": "^4.7.2",
|
|
70
75
|
"@eslint/js": "^10.0.1",
|
|
71
76
|
"@mnaoumov/typescript-dev-toolkit": "^5.2.0",
|
|
77
|
+
"@resvg/resvg-js": "^2.6.2",
|
|
72
78
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
73
79
|
"@total-typescript/ts-reset": "^0.6.1",
|
|
74
80
|
"@tsconfig/strictest": "^2.0.8",
|
|
75
81
|
"@types/ejs": "^3.1.5",
|
|
76
82
|
"@types/semver": "^7.8.0",
|
|
77
83
|
"@vitest/coverage-v8": "^5.0.5",
|
|
84
|
+
"astro": "^7.4.4",
|
|
78
85
|
"better-typescript-lib": "^2.12.0",
|
|
79
86
|
"cspell": "^10.1.1",
|
|
80
87
|
"czg": "^1.14.0",
|
|
@@ -88,6 +95,8 @@
|
|
|
88
95
|
"husky": "^9.1.7",
|
|
89
96
|
"ignore": "^7.0.12",
|
|
90
97
|
"jiti": "^2.7.0",
|
|
98
|
+
"satori": "^0.33.5",
|
|
99
|
+
"satteri": "^0.10.5",
|
|
91
100
|
"typescript": "6.0.3",
|
|
92
101
|
"typescript-eslint": "^8.68.0",
|
|
93
102
|
"vitest": "^5.0.5",
|
package/src/main.ts
CHANGED
|
@@ -417,7 +417,8 @@ async function offerAnswersExport(answers: Answers, customTemplate: string | und
|
|
|
417
417
|
writeFileSync(join(process.cwd(), answersFileName), formatAnswersJson(answers));
|
|
418
418
|
log.success(`Wrote ${answersFileName}`);
|
|
419
419
|
const overlayFlag = customTemplate ? ` --customTemplate=${customTemplate}` : '';
|
|
420
|
-
|
|
420
|
+
// `"--"` is quoted so the command works pasted into PowerShell, which swallows a bare `--`, as well as cmd and sh.
|
|
421
|
+
note(`npm create @mnaoumov/obsidian-plugin "--" --yes --mode=create --answersFile=${answersFileName}${overlayFlag}`, 'Non-interactive command');
|
|
421
422
|
}
|
|
422
423
|
|
|
423
424
|
choice = await ask();
|