obsidian-dev-skills 1.1.3 ā 1.2.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/README.md +8 -1
- package/obsidian-dev/references/code-patterns.md +8 -192
- package/obsidian-dev/references/commands-settings.md +1 -4
- package/obsidian-dev/references/common-tasks.md +1 -1
- package/obsidian-ops/SKILL.md +4 -1
- package/obsidian-ops/references/contributing-template.md +96 -0
- package/obsidian-ops/references/scorecard-compliance.md +308 -0
- package/obsidian-ops/references/security-privacy.md +34 -0
- package/obsidian-ops/references/versioning-releases.md +51 -0
- package/package.json +9 -5
- package/scripts/setup-local.ps1 +6 -3
- package/scripts/bulk-update.mjs +0 -40
- package/scripts/check-status.mjs +0 -45
package/README.md
CHANGED
|
@@ -49,6 +49,7 @@ node node_modules/obsidian-dev-skills/scripts/init.mjs
|
|
|
49
49
|
- Obsidian API usage
|
|
50
50
|
- Plugin lifecycle management
|
|
51
51
|
- Command and settings implementation
|
|
52
|
+
- `obsidianmd` ESLint rule patterns (`createDiv`/`createSpan`, `activeWindow`, `activeDocument`, sentence case)
|
|
52
53
|
|
|
53
54
|
### obsidian-theme-dev
|
|
54
55
|
- CSS/SCSS development patterns
|
|
@@ -61,6 +62,9 @@ node node_modules/obsidian-dev-skills/scripts/init.mjs
|
|
|
61
62
|
- Version management
|
|
62
63
|
- Sync procedures
|
|
63
64
|
- Testing and quality assurance
|
|
65
|
+
- Obsidian community **scorecard compliance** mapping (vulnerable transitive dep handling via `pnpm.overrides`, duplicate CSS selector detection, ESLint rule violations)
|
|
66
|
+
- Release workflow with **build provenance attestation** (`actions/attest-build-provenance@v2`)
|
|
67
|
+
- Portable **`CONTRIBUTING.md` template** for the scorecard hygiene check
|
|
64
68
|
|
|
65
69
|
### obsidian-ref
|
|
66
70
|
- API documentation
|
|
@@ -80,7 +84,10 @@ Each project should also have a `project/` skill in `.agent/skills/project/` tha
|
|
|
80
84
|
|
|
81
85
|
### Updating Skills
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
The update flow depends on how a project consumes this package:
|
|
88
|
+
|
|
89
|
+
- **npm install (default)**: When this package publishes a new version, downstream projects pick up updates the next time they run `pnpm obsidian-dev-skills` (or the equivalent npx/manual command). The init script re-copies skill files into `.agent/skills/`.
|
|
90
|
+
- **Sibling clone + symlinks (advanced)**: If a project symlinks `.agent/skills/<name>` to a sibling clone of this repository (e.g. via `scripts/setup-local.ps1`), updates appear instantly the moment files change here, with no per-project sync step.
|
|
84
91
|
|
|
85
92
|
### Tracking Sync Status
|
|
86
93
|
|
|
@@ -130,119 +130,16 @@ class MySettingTab extends PluginSettingTab {
|
|
|
130
130
|
this.addSettingTab(new MySettingTab(this.app, this));
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
## Settings with Groups
|
|
133
|
+
## Settings with Groups
|
|
134
134
|
|
|
135
135
|
**Source**: Based on `.ref/obsidian-api/obsidian.d.ts` (API is authoritative) - `SettingGroup` requires API 1.11.0+
|
|
136
136
|
|
|
137
|
-
**Use this when**: You want to
|
|
137
|
+
**Use this when**: You want to visually group related settings together.
|
|
138
138
|
|
|
139
|
-
**
|
|
140
|
-
- Continue using the compatibility utility (supports all versions)
|
|
141
|
-
- Force `minAppVersion: "1.11.0"` in `manifest.json` and use `SettingGroup` directly (simpler, but excludes older versions)
|
|
142
|
-
|
|
143
|
-
### Step 1: Create the Compatibility Utility
|
|
144
|
-
|
|
145
|
-
Create `src/utils/settings-compat.ts` (or wherever you keep utilities):
|
|
146
|
-
|
|
147
|
-
```ts
|
|
148
|
-
/**
|
|
149
|
-
* Compatibility utilities for settings
|
|
150
|
-
* Provides backward compatibility for SettingGroup (requires API 1.11.0+)
|
|
151
|
-
*/
|
|
152
|
-
import { Setting, requireApiVersion } from 'obsidian';
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Type definition for SettingGroup constructor
|
|
156
|
-
* Note: SettingGroup may exist at runtime in 1.11.0+ but may not be in TypeScript definitions
|
|
157
|
-
*
|
|
158
|
-
* IMPORTANT: This type signature is inferred from usage patterns. When .ref/obsidian-api/obsidian.d.ts
|
|
159
|
-
* is available, verify the actual signature there. The signature shown here matches the expected
|
|
160
|
-
* behavior based on Obsidian's API design patterns.
|
|
161
|
-
*/
|
|
162
|
-
type SettingGroupConstructor = new (containerEl: HTMLElement) => {
|
|
163
|
-
setHeading(heading: string): {
|
|
164
|
-
addSetting(cb: (setting: Setting) => void): void;
|
|
165
|
-
};
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Interface that works with both SettingGroup and fallback container
|
|
170
|
-
*/
|
|
171
|
-
export interface SettingsContainer {
|
|
172
|
-
addSetting(cb: (setting: Setting) => void): void;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Creates a settings container that uses SettingGroup if available (API 1.11.0+),
|
|
177
|
-
* otherwise falls back to creating a heading and using the container directly.
|
|
178
|
-
*
|
|
179
|
-
* Uses requireApiVersion('1.11.0') to check if SettingGroup is available.
|
|
180
|
-
* This is the official Obsidian API method for version checking.
|
|
181
|
-
*
|
|
182
|
-
* IMPORTANT: We use dynamic require() instead of direct import because SettingGroup
|
|
183
|
-
* may not be in TypeScript type definitions even if it exists at runtime in 1.11.0+.
|
|
184
|
-
* This avoids compile-time TypeScript errors while still working at runtime.
|
|
185
|
-
*
|
|
186
|
-
* @param containerEl - The container element for settings
|
|
187
|
-
* @param heading - The heading text for the settings group (optional)
|
|
188
|
-
* @param manifestId - The plugin's manifest ID for CSS scoping (required for fallback mode)
|
|
189
|
-
* @returns A container that can be used to add settings
|
|
190
|
-
*/
|
|
191
|
-
export function createSettingsGroup(
|
|
192
|
-
containerEl: HTMLElement,
|
|
193
|
-
heading?: string,
|
|
194
|
-
manifestId?: string
|
|
195
|
-
): SettingsContainer {
|
|
196
|
-
// Check if SettingGroup is available (API 1.11.0+)
|
|
197
|
-
// requireApiVersion is the official Obsidian API method for version checking
|
|
198
|
-
if (requireApiVersion('1.11.0')) {
|
|
199
|
-
// Use dynamic require() to access SettingGroup at runtime
|
|
200
|
-
// This avoids TypeScript errors when SettingGroup isn't in type definitions
|
|
201
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
202
|
-
const obsidian = require('obsidian');
|
|
203
|
-
const SettingGroup = obsidian.SettingGroup as SettingGroupConstructor;
|
|
204
|
-
|
|
205
|
-
// Use SettingGroup - it's guaranteed to exist if requireApiVersion returns true
|
|
206
|
-
const group = heading
|
|
207
|
-
? new SettingGroup(containerEl).setHeading(heading)
|
|
208
|
-
: new SettingGroup(containerEl);
|
|
209
|
-
return {
|
|
210
|
-
addSetting(cb: (setting: Setting) => void) {
|
|
211
|
-
group.addSetting(cb);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
} else {
|
|
215
|
-
// Fallback path (either API < 1.11.0 or SettingGroup not found)
|
|
216
|
-
// Add scoping class to containerEl to scope CSS to only this plugin's settings
|
|
217
|
-
if (manifestId) {
|
|
218
|
-
containerEl.addClass(`${manifestId}-settings-compat`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Fallback: Create a heading manually and use container directly
|
|
222
|
-
if (heading) {
|
|
223
|
-
const headingEl = containerEl.createDiv('setting-group-heading');
|
|
224
|
-
headingEl.createEl('h3', { text: heading });
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return {
|
|
228
|
-
addSetting(cb: (setting: Setting) => void) {
|
|
229
|
-
const setting = new Setting(containerEl);
|
|
230
|
-
cb(setting);
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
**Note**: The dynamic `require()` approach is necessary because `SettingGroup` may not be in TypeScript type definitions even if it exists at runtime in Obsidian 1.11.0+. This avoids compile-time TypeScript errors while maintaining runtime compatibility.
|
|
238
|
-
|
|
239
|
-
### Step 2: Use in Settings Tab
|
|
240
|
-
|
|
241
|
-
Update your settings tab to use the compatibility utility:
|
|
139
|
+
**Important**: You must set `minAppVersion: "1.11.0"` in your `manifest.json` to use `SettingGroup`.
|
|
242
140
|
|
|
243
141
|
```ts
|
|
244
|
-
import { App, PluginSettingTab, Setting } from "obsidian";
|
|
245
|
-
import { createSettingsGroup } from "./utils/settings-compat";
|
|
142
|
+
import { App, PluginSettingTab, Setting, SettingGroup } from "obsidian";
|
|
246
143
|
|
|
247
144
|
interface MyPluginSettings {
|
|
248
145
|
generalEnabled: boolean;
|
|
@@ -271,7 +168,7 @@ class MySettingTab extends PluginSettingTab {
|
|
|
271
168
|
containerEl.empty();
|
|
272
169
|
|
|
273
170
|
// General Settings Group
|
|
274
|
-
const generalGroup =
|
|
171
|
+
const generalGroup = new SettingGroup(containerEl).setHeading("General Settings");
|
|
275
172
|
|
|
276
173
|
generalGroup.addSetting((setting) => {
|
|
277
174
|
setting
|
|
@@ -304,7 +201,7 @@ class MySettingTab extends PluginSettingTab {
|
|
|
304
201
|
});
|
|
305
202
|
|
|
306
203
|
// Advanced Settings Group
|
|
307
|
-
const advancedGroup =
|
|
204
|
+
const advancedGroup = new SettingGroup(containerEl).setHeading("Advanced Settings");
|
|
308
205
|
|
|
309
206
|
advancedGroup.addSetting((setting) => {
|
|
310
207
|
setting
|
|
@@ -343,69 +240,9 @@ class MySettingTab extends PluginSettingTab {
|
|
|
343
240
|
this.addSettingTab(new MySettingTab(this.app, this));
|
|
344
241
|
```
|
|
345
242
|
|
|
346
|
-
### Step 3: Add CSS Styling (Required for Older Obsidian Builds)
|
|
347
|
-
|
|
348
|
-
**Important**: When using the compatibility utility for older Obsidian builds (< 1.11.0), you must add CSS to prevent double divider lines. The fallback creates a heading with class `setting-group-heading`, and without proper CSS, you'll see a double divider (one from the heading's border-bottom and one from the first setting-item's border-top).
|
|
349
|
-
|
|
350
|
-
**CRITICAL**: The CSS **MUST** be scoped to your plugin's settings container using a manifest-ID-based class to avoid affecting other plugins' settings. Global CSS selectors will impact all settings in Obsidian, not just your plugin's settings.
|
|
351
|
-
|
|
352
|
-
Add this CSS to your `styles.css` file, replacing `{manifest-id}` with your plugin's manifest ID:
|
|
353
|
-
|
|
354
|
-
```css
|
|
355
|
-
/* Group settings compatibility styling for older Obsidian builds (< 1.11.0) */
|
|
356
|
-
/* Scoped to only this plugin's settings container to avoid affecting other plugins */
|
|
357
|
-
.{manifest-id}-settings-compat .setting-group-heading h3 {
|
|
358
|
-
margin: 0 0 0.75rem;
|
|
359
|
-
padding-bottom: 0.5rem;
|
|
360
|
-
padding-top: 0.5rem;
|
|
361
|
-
font-size: 1rem;
|
|
362
|
-
font-weight: 600;
|
|
363
|
-
border-bottom: none !important;
|
|
364
|
-
}
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
**Example**: If your manifest ID is `sample-plugin`, use `.sample-plugin-settings-compat` as the scoping class.
|
|
368
|
-
|
|
369
|
-
**How it works**:
|
|
370
|
-
- The CSS uses the `:has()` selector to detect if a `.setting-item` immediately follows the heading
|
|
371
|
-
- If settings exist below the heading, no border-bottom is applied (avoiding double divider)
|
|
372
|
-
- If no settings follow, border-bottom is applied for visual separation
|
|
373
|
-
- The scoping class (`{manifest-id}-settings-compat`) ensures CSS only affects headings within this plugin's settings container
|
|
374
|
-
- This only affects older builds (< 1.11.0) where the compatibility fallback is used
|
|
375
|
-
- On Obsidian 1.11.0+, `SettingGroup` handles styling automatically, so this CSS has no effect
|
|
376
|
-
|
|
377
|
-
**Note**: The `:has()` selector is well-supported in modern Obsidian (Chromium-based). If you need to support very old browsers, see the alternative TypeScript-based approach in the Common Pitfalls section below.
|
|
378
|
-
|
|
379
|
-
### How It Works
|
|
380
|
-
|
|
381
|
-
- **On Obsidian 1.11.0+**: Uses `SettingGroup` with proper styling and grouping
|
|
382
|
-
- **On older versions**: Creates a manual heading (`<h3>`) and uses regular `Setting` objects
|
|
383
|
-
- **Same API**: Your code using `addSetting()` works identically in both cases
|
|
384
|
-
|
|
385
243
|
### Common Pitfalls
|
|
386
244
|
|
|
387
|
-
#### Pitfall 1:
|
|
388
|
-
|
|
389
|
-
**Problem**: You may see this TypeScript error:
|
|
390
|
-
```ts
|
|
391
|
-
Module '"obsidian"' has no exported member 'SettingGroup'
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
**Cause**: `SettingGroup` may exist at runtime in Obsidian 1.11.0+ but may not be in the TypeScript type definitions, causing compile-time errors.
|
|
395
|
-
|
|
396
|
-
**Solution**: Use dynamic `require()` instead of direct import, as shown in the compatibility utility above. Do not import `SettingGroup` directly:
|
|
397
|
-
|
|
398
|
-
```ts
|
|
399
|
-
// ā WRONG - Causes TypeScript errors
|
|
400
|
-
import { SettingGroup } from 'obsidian';
|
|
401
|
-
|
|
402
|
-
// ā
CORRECT - Use dynamic require()
|
|
403
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
404
|
-
const obsidian = require('obsidian');
|
|
405
|
-
const SettingGroup = obsidian.SettingGroup as SettingGroupConstructor;
|
|
406
|
-
```
|
|
407
|
-
|
|
408
|
-
#### Pitfall 2: Missing Closing Parentheses
|
|
245
|
+
#### Pitfall 1: Missing Closing Parentheses
|
|
409
246
|
|
|
410
247
|
**Problem**: Arrow functions with method chaining need proper closing parentheses and semicolons.
|
|
411
248
|
|
|
@@ -431,7 +268,7 @@ generalGroup.addSetting((setting) =>
|
|
|
431
268
|
); // Closing parenthesis and semicolon required
|
|
432
269
|
```
|
|
433
270
|
|
|
434
|
-
#### Pitfall
|
|
271
|
+
#### Pitfall 2: Storing Setting References
|
|
435
272
|
|
|
436
273
|
**Problem**: If you need to reference a `Setting` object later (e.g., for visibility toggling), you must use block syntax `{ }` instead of expression syntax.
|
|
437
274
|
|
|
@@ -460,27 +297,6 @@ generalGroup.addSetting((setting) => {
|
|
|
460
297
|
mySetting.settingEl.style.display = this.plugin.settings.enabled ? "" : "none";
|
|
461
298
|
```
|
|
462
299
|
|
|
463
|
-
### Alternative: Force Minimum Version
|
|
464
|
-
|
|
465
|
-
If you don't need to support versions before 1.11.0, you can skip the compatibility utility:
|
|
466
|
-
|
|
467
|
-
1. Set `minAppVersion: "1.11.0"` in your `manifest.json`
|
|
468
|
-
2. Use `SettingGroup` directly:
|
|
469
|
-
|
|
470
|
-
```ts
|
|
471
|
-
import { Setting, SettingGroup } from "obsidian";
|
|
472
|
-
|
|
473
|
-
// In settings tab:
|
|
474
|
-
const group = new SettingGroup(containerEl).setHeading("My Settings");
|
|
475
|
-
group.addSetting((setting) => {
|
|
476
|
-
// ... configure setting
|
|
477
|
-
});
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
**Note**: Even with `minAppVersion: "1.11.0"`, you may still encounter TypeScript errors if `SettingGroup` isn't in the type definitions. In that case, you can still use the compatibility utility approach (it will always use `SettingGroup` when `requireApiVersion('1.11.0')` returns true), or use dynamic `require()` as shown in the compatibility utility.
|
|
481
|
-
|
|
482
|
-
This approach is simpler but excludes users on older Obsidian versions. The compatibility utility still works and is recommended for maximum flexibility.
|
|
483
|
-
|
|
484
300
|
## Modal with Form Input
|
|
485
301
|
|
|
486
302
|
**Source**: Based on `.ref/obsidian-plugin-docs/docs/guides/modals.md`
|
|
@@ -16,9 +16,6 @@ Applicability: Plugin
|
|
|
16
16
|
|
|
17
17
|
## Version Considerations
|
|
18
18
|
|
|
19
|
-
When using newer API features (e.g., `SettingGroup` since API 1.11.0),
|
|
20
|
-
- **For new plugins**: You can set `minAppVersion: "1.11.0"` in `manifest.json` and use the feature directly
|
|
21
|
-
- **For existing plugins**: Use version checking with `requireApiVersion()` to support both newer and older Obsidian versions
|
|
22
|
-
- See [code-patterns.md](code-patterns.md) for backward compatibility patterns, including a complete example for `SettingGroup`
|
|
19
|
+
When using newer API features (e.g., `SettingGroup` since API 1.11.0), you must set `minAppVersion: "1.11.0"` in your `manifest.json`.
|
|
23
20
|
|
|
24
21
|
|
|
@@ -137,7 +137,7 @@ this.addSettingTab(new MySettingTab(this.app, this));
|
|
|
137
137
|
- `addSearch(cb: (component: SearchComponent) => any)` - Add a search input at the beginning of the group (useful for filtering)
|
|
138
138
|
- `addExtraButton(cb: (component: ExtraButtonComponent) => any)` - Add an extra button to the group
|
|
139
139
|
|
|
140
|
-
**
|
|
140
|
+
**Important**: You must set `minAppVersion: "1.11.0"` in your `manifest.json` to use these methods.
|
|
141
141
|
|
|
142
142
|
## Secret Storage
|
|
143
143
|
|
package/obsidian-ops/SKILL.md
CHANGED
|
@@ -29,7 +29,10 @@ This skill covers:
|
|
|
29
29
|
|
|
30
30
|
- `references/build-workflow.md`: Standard build and development commands.
|
|
31
31
|
- `references/release-readiness.md`: Checklist for ensuring a project is ready for release.
|
|
32
|
+
- `references/scorecard-compliance.md`: Map of Obsidian community plugin scorecard signals to concrete fixes (vulnerability overrides, attestation workflow, CSS dedup, obsidianmd rules).
|
|
33
|
+
- `references/contributing-template.md`: Portable CONTRIBUTING.md template for the scorecard hygiene check.
|
|
34
|
+
- `references/security-privacy.md`: Developer policies, mandatory disclosures, and dependency vulnerability hygiene via `pnpm.overrides`.
|
|
32
35
|
- `references/sync-procedure.md`: How to pull updates from reference repositories.
|
|
33
|
-
- `references/versioning-releases.md`: Workflow for versioning and GitHub releases.
|
|
36
|
+
- `references/versioning-releases.md`: Workflow for versioning and GitHub releases, including the build-provenance attestation pattern.
|
|
34
37
|
- `references/troubleshooting.md`: Common issues and their resolutions.
|
|
35
38
|
- `references/quick-reference.md`: One-page cheat sheet for common operations.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Source: Authored for Obsidian plugin scorecard compliance
|
|
3
|
+
Last synced: See sync-status.json for authoritative sync dates
|
|
4
|
+
Update frequency: Update when the standard development setup changes
|
|
5
|
+
Applicability: Plugin
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# CONTRIBUTING.md Template
|
|
9
|
+
|
|
10
|
+
The Obsidian community scorecard's **Hygiene** check looks for a `CONTRIBUTING.md` at the repo root. The template below is portable across plugin repositories without modification, because it uses relative GitHub links (`../../issues`) and references generic plugin concepts.
|
|
11
|
+
|
|
12
|
+
## When to drop in unmodified
|
|
13
|
+
|
|
14
|
+
Use the template as-is when the repo:
|
|
15
|
+
|
|
16
|
+
- Uses pnpm with the standard `pnpm dev`, `pnpm build`, `pnpm lint`, `pnpm lint:fix` scripts.
|
|
17
|
+
- Builds `main.js`, `styles.css`, and `manifest.json` to the repo root.
|
|
18
|
+
- Is licensed MIT.
|
|
19
|
+
- Uses the house terminology rules (properties, Markdown capitalized).
|
|
20
|
+
|
|
21
|
+
## When to customize
|
|
22
|
+
|
|
23
|
+
Change the template only when:
|
|
24
|
+
|
|
25
|
+
- The plugin uses npm/yarn/bun instead of pnpm (update the install/build commands).
|
|
26
|
+
- The plugin has additional scripts the contributor needs to run (e.g. a separate test command).
|
|
27
|
+
- The license is not MIT (update the License section).
|
|
28
|
+
|
|
29
|
+
Do not customize the issue link ā the relative `../../issues` form resolves correctly on any GitHub repo.
|
|
30
|
+
|
|
31
|
+
## Template
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
# Contributing
|
|
35
|
+
|
|
36
|
+
Thanks for your interest in contributing. This document describes how to report issues, propose changes, and get a local development environment running.
|
|
37
|
+
|
|
38
|
+
## Reporting issues
|
|
39
|
+
|
|
40
|
+
- Search [existing issues](../../issues) before opening a new one.
|
|
41
|
+
- For bugs, include: Obsidian version, plugin version, operating system, reproduction steps, and what you expected vs. what happened. Screenshots or a short screen recording help.
|
|
42
|
+
- For feature requests, describe the use case and the problem the feature would solve, not just the proposed solution.
|
|
43
|
+
|
|
44
|
+
## Development setup
|
|
45
|
+
|
|
46
|
+
Requirements:
|
|
47
|
+
|
|
48
|
+
- [Node.js](https://nodejs.org/) (LTS recommended)
|
|
49
|
+
- [pnpm](https://pnpm.io/). This repo pins a specific version via `packageManager` in `package.json`.
|
|
50
|
+
|
|
51
|
+
`ā`ā`bash
|
|
52
|
+
pnpm install
|
|
53
|
+
pnpm dev # watch mode build
|
|
54
|
+
pnpm build # production build
|
|
55
|
+
pnpm lint # check code style
|
|
56
|
+
pnpm lint:fix # auto-fix where possible
|
|
57
|
+
`ā`ā`
|
|
58
|
+
|
|
59
|
+
Build artifacts (`main.js`, `manifest.json`, `styles.css`) are produced at the repo root.
|
|
60
|
+
|
|
61
|
+
To test against a real vault, point the build output at `<vault>/.obsidian/plugins/<plugin-id>/` (symlink the three files, or use a tool like [hot-reload](https://github.com/pjeby/hot-reload)).
|
|
62
|
+
|
|
63
|
+
## Pull requests
|
|
64
|
+
|
|
65
|
+
- Open an issue first for anything non-trivial so we can agree on direction before you spend time on it.
|
|
66
|
+
- Keep PRs focused. One concern per PR is easier to review.
|
|
67
|
+
- Run `pnpm lint` and `pnpm build` before submitting; CI will run these too.
|
|
68
|
+
- Follow the existing code style. Use **"properties"** (not "frontmatter") when referring to YAML metadata. **"Markdown"** is always capitalized.
|
|
69
|
+
- Update the README if you change user-facing behavior.
|
|
70
|
+
|
|
71
|
+
## Code style
|
|
72
|
+
|
|
73
|
+
- TypeScript strict mode, no `any` unless unavoidable.
|
|
74
|
+
- Prefer Obsidian's API (`Vault`, `MetadataCache`, `Workspace`) over Node `fs` for vault access.
|
|
75
|
+
- No network requests from the plugin runtime.
|
|
76
|
+
|
|
77
|
+
## Releases
|
|
78
|
+
|
|
79
|
+
Maintainers cut releases by bumping the version in `manifest.json`, `package.json`, and `versions.json`, committing, then tagging and pushing the tag. The release workflow builds and attaches `main.js`, `manifest.json`, and `styles.css` with build provenance attestation.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Style notes
|
|
87
|
+
|
|
88
|
+
- The template avoids em dashes (`ā`); prefer periods, commas, or parentheses instead.
|
|
89
|
+
- The template avoids the word "Claude" and any reference to AI assistance, since the file is user-facing documentation.
|
|
90
|
+
- Headings use sentence case to match Obsidian UI conventions.
|
|
91
|
+
|
|
92
|
+
## Related Documentation
|
|
93
|
+
|
|
94
|
+
- [scorecard-compliance.md](scorecard-compliance.md) ā covers every scorecard signal, not just the contributing guide.
|
|
95
|
+
- [release-readiness.md](release-readiness.md) ā broader pre-release checklist.
|
|
96
|
+
- [versioning-releases.md](versioning-releases.md) ā release workflow setup.
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Source: Obsidian community plugin scorecard (community.obsidian.md/plugins/<id>)
|
|
3
|
+
Last synced: See sync-status.json for authoritative sync dates
|
|
4
|
+
Update frequency: Check the Obsidian community portal scorecard format for changes
|
|
5
|
+
Applicability: Plugin
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Community Plugin Scorecard Compliance
|
|
9
|
+
|
|
10
|
+
The Obsidian community portal publishes an automated scorecard for every plugin. It scans the latest GitHub release plus the source repository and reports across four health categories (**Hygiene**, **Maintenance**, **Responsiveness**, **Adoption**) and a **Review** section that flags concrete issues from automated scans.
|
|
11
|
+
|
|
12
|
+
This document maps the scorecard signals to concrete fixes you can apply. Use it when a user asks "why is my score low?" or "help me fix my plugin's scorecard."
|
|
13
|
+
|
|
14
|
+
## Health Category Fixes
|
|
15
|
+
|
|
16
|
+
### Hygiene
|
|
17
|
+
|
|
18
|
+
The portal checks for four files at the repo root.
|
|
19
|
+
|
|
20
|
+
| Signal | Fix |
|
|
21
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
22
|
+
| Missing readme | Add `README.md` describing the plugin's purpose, installation, usage, and screenshots. |
|
|
23
|
+
| Missing license | Add `LICENSE` (MIT is typical). |
|
|
24
|
+
| Missing description | Ensure `manifest.json` has a clear `description` field. |
|
|
25
|
+
| Missing contributing | Add `CONTRIBUTING.md`. See [contributing-template.md](contributing-template.md) for a portable template. |
|
|
26
|
+
|
|
27
|
+
### Maintenance, Responsiveness, Adoption
|
|
28
|
+
|
|
29
|
+
These reflect commit cadence, issue close rate, install count, and stars. They are not directly fixable by editing files. Steer the user toward consistent maintenance habits rather than a one-shot fix.
|
|
30
|
+
|
|
31
|
+
## Review Section Fixes
|
|
32
|
+
|
|
33
|
+
The Review section is where most score improvement happens. Issues are grouped into **Risks**, **Warnings**, **Disclosures**, **Other**, and **Passed**.
|
|
34
|
+
|
|
35
|
+
### Disclosures (informational, not fixable)
|
|
36
|
+
|
|
37
|
+
Disclosures are not deductions, they are informational. Common ones for plugins:
|
|
38
|
+
|
|
39
|
+
- **Vault Enumeration** ā uses `vault.getFiles()`, `getMarkdownFiles()`, etc.
|
|
40
|
+
- **Vault Read** ā uses `vault.read()` or `vault.cachedRead()`.
|
|
41
|
+
- **Vault Write** ā uses `vault.modify()`, `vault.create()`, etc.
|
|
42
|
+
- **Clipboard Access** ā reads or writes the system clipboard.
|
|
43
|
+
- **Malware scan not available** / **Vulnerable dependencies scan not available** ā scanner-side limitations.
|
|
44
|
+
|
|
45
|
+
Do not try to remove these. They reflect the plugin's actual API surface and the user should understand them when installing.
|
|
46
|
+
|
|
47
|
+
### Vulnerable transitive dev dependencies
|
|
48
|
+
|
|
49
|
+
The scanner reports advisories like:
|
|
50
|
+
|
|
51
|
+
> Potentially vulnerable dependency `<pkg>` (via `<parent>`). Upgrade to version X or later.
|
|
52
|
+
|
|
53
|
+
These are almost always **dev-only transitives** pulled in through `eslint`, `eslint-plugin-obsidianmd`, and `@typescript-eslint/*`. They never ship in `main.js`, but the scorecard still counts them.
|
|
54
|
+
|
|
55
|
+
**Fix**: add a `pnpm.overrides` block to `package.json` that forces patched versions. Major-version-scoped ranges keep API compatibility while bumping the patch:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"pnpm": {
|
|
60
|
+
"overrides": {
|
|
61
|
+
"minimatch@<3.1.3": "^3.1.3",
|
|
62
|
+
"minimatch@^4 || ^5 || ^6 || ^7 || ^8": "^8.0.5",
|
|
63
|
+
"minimatch@^9": "^9.0.6",
|
|
64
|
+
"picomatch@<2.3.2": "^2.3.2",
|
|
65
|
+
"picomatch@^3 || ^4": "^4.0.4",
|
|
66
|
+
"brace-expansion@<1.1.13": "^1.1.13",
|
|
67
|
+
"brace-expansion@^2": "^2.0.3",
|
|
68
|
+
"ajv@<6.14.0": "^6.14.0",
|
|
69
|
+
"ajv@^7 || ^8": "^8.18.0",
|
|
70
|
+
"flatted@<3.4.0": "^3.4.0",
|
|
71
|
+
"fast-uri@<3.1.1": "^3.1.1",
|
|
72
|
+
"yaml@<2.8.3": "^2.8.3"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then run `pnpm install` and `pnpm why <pkg>` to verify the patched version is resolved.
|
|
79
|
+
|
|
80
|
+
**Why this format**: pnpm override selectors match against resolved versions. The `package@<range>` key form lets you target one major at a time. A bare `package: "version"` would force every consumer to the same major and break anything depending on a different one.
|
|
81
|
+
|
|
82
|
+
### Missing GitHub artifact attestation
|
|
83
|
+
|
|
84
|
+
> X release assets are missing a GitHub artifact attestation.
|
|
85
|
+
|
|
86
|
+
**Fix**: add a release workflow that runs `actions/attest-build-provenance@v2` against the release files. Drop this at `.github/workflows/release.yml`:
|
|
87
|
+
|
|
88
|
+
```yaml
|
|
89
|
+
name: Release
|
|
90
|
+
|
|
91
|
+
on:
|
|
92
|
+
push:
|
|
93
|
+
tags:
|
|
94
|
+
- "*"
|
|
95
|
+
|
|
96
|
+
permissions:
|
|
97
|
+
contents: write
|
|
98
|
+
id-token: write
|
|
99
|
+
attestations: write
|
|
100
|
+
|
|
101
|
+
jobs:
|
|
102
|
+
release:
|
|
103
|
+
runs-on: ubuntu-latest
|
|
104
|
+
steps:
|
|
105
|
+
- uses: actions/checkout@v4
|
|
106
|
+
- uses: pnpm/action-setup@v4
|
|
107
|
+
- uses: actions/setup-node@v4
|
|
108
|
+
with:
|
|
109
|
+
node-version: "20"
|
|
110
|
+
cache: "pnpm"
|
|
111
|
+
- run: pnpm install --frozen-lockfile
|
|
112
|
+
- run: pnpm build
|
|
113
|
+
- uses: actions/attest-build-provenance@v2
|
|
114
|
+
with:
|
|
115
|
+
subject-path: |
|
|
116
|
+
main.js
|
|
117
|
+
styles.css
|
|
118
|
+
manifest.json
|
|
119
|
+
- env:
|
|
120
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
121
|
+
TAG: ${{ github.ref_name }}
|
|
122
|
+
run: |
|
|
123
|
+
gh release create "$TAG" \
|
|
124
|
+
--title="$TAG" \
|
|
125
|
+
--generate-notes \
|
|
126
|
+
main.js styles.css manifest.json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Then cut releases by pushing a tag (e.g. `git tag 0.1.0 && git push origin 0.1.0`) instead of uploading files manually. The workflow attaches the assets and registers the attestation, which the scorecard checks against the source repo for byte-for-byte reproducibility.
|
|
130
|
+
|
|
131
|
+
### Duplicate CSS selectors
|
|
132
|
+
|
|
133
|
+
> Unexpected duplicate selector ".foo .bar", first used at line N.
|
|
134
|
+
|
|
135
|
+
**Fix**: merge the two rule blocks. If they target the same selector and have non-overlapping properties, combine into one block. If they target the same selector and one is meant to override the other (e.g. inside a media query), wrap the override in a more specific selector or use a CSS variable.
|
|
136
|
+
|
|
137
|
+
### `obsidianmd` ESLint rules (Warnings and Risks)
|
|
138
|
+
|
|
139
|
+
`eslint-plugin-obsidianmd` enforces Obsidian-idiomatic patterns. Common signals and fixes:
|
|
140
|
+
|
|
141
|
+
#### `obsidianmd/ui/sentence-case` (Risk if disabled, Warning if violated)
|
|
142
|
+
|
|
143
|
+
User-facing strings must follow sentence case. The rule auto-detects most static strings; template literals are sometimes missed but still violate the spirit of the rule.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// Wrong
|
|
147
|
+
new Notice('Click Here To Continue');
|
|
148
|
+
this.countEl.setText('0 selected');
|
|
149
|
+
|
|
150
|
+
// Right
|
|
151
|
+
new Notice('Click here to continue');
|
|
152
|
+
this.countEl.setText('0 Selected');
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Never disable this rule.** The scorecard explicitly flags `eslint-disable-next-line obsidianmd/ui/sentence-case` as a Risk and counts each occurrence. If a string genuinely cannot be sentence case (e.g. it embeds a proper noun or product name), fix the casing instead of disabling.
|
|
156
|
+
|
|
157
|
+
#### `obsidianmd/prefer-create-el-shorthand` (Warning)
|
|
158
|
+
|
|
159
|
+
Use `createDiv()` and `createSpan()` instead of `createEl('div')` and `createEl('span')`.
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// Wrong
|
|
163
|
+
const wrap = containerEl.createEl('div', { cls: 'my-wrap' });
|
|
164
|
+
const label = wrap.createEl('span', { text: 'Hello' });
|
|
165
|
+
|
|
166
|
+
// Right
|
|
167
|
+
const wrap = containerEl.createDiv({ cls: 'my-wrap' });
|
|
168
|
+
const label = wrap.createSpan({ text: 'Hello' });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### `obsidianmd/prefer-active-doc` (Warning)
|
|
172
|
+
|
|
173
|
+
Use `activeDocument` instead of `document` and `activeWindow.setTimeout/setInterval/clearTimeout/clearInterval` instead of the globals. This ensures behavior in Obsidian popout windows.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// Wrong
|
|
177
|
+
this.registerDomEvent(document, 'click', handler);
|
|
178
|
+
setTimeout(refresh, 500);
|
|
179
|
+
clearInterval(this.intervalId);
|
|
180
|
+
|
|
181
|
+
// Right
|
|
182
|
+
this.registerDomEvent(activeDocument, 'click', handler);
|
|
183
|
+
activeWindow.setTimeout(refresh, 500);
|
|
184
|
+
activeWindow.clearInterval(this.intervalId);
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### `obsidianmd/no-eslint-disable-without-description` (Risk)
|
|
188
|
+
|
|
189
|
+
> Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary.
|
|
190
|
+
|
|
191
|
+
Every `eslint-disable*` comment must end with `--` and a reason.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
// Wrong
|
|
195
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
|
+
|
|
197
|
+
// Right
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- third-party type is loose
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
This is the most common Risk-tier finding for plugins that accumulated `eslint-disable` comments before adopting the rule. Audit every existing disable comment and either add a reason or refactor the code to avoid the disable.
|
|
202
|
+
|
|
203
|
+
#### `@typescript-eslint/no-unused-vars` (Warning)
|
|
204
|
+
|
|
205
|
+
> 'foo' is defined but never used. Allowed unused args must match /^_/u.
|
|
206
|
+
|
|
207
|
+
**Fix**: prefix unused parameters with `_` (e.g. `_file`, `_index`, `_settings`). For destructured properties you don't need, remove them.
|
|
208
|
+
|
|
209
|
+
#### `@typescript-eslint/no-explicit-any` (Warning)
|
|
210
|
+
|
|
211
|
+
> Unexpected `any`. Specify a different type.
|
|
212
|
+
|
|
213
|
+
**Fix**: replace `any` with `unknown` and narrow at the use site, or with a proper type. If the API is genuinely typed loosely (e.g. `JSON.parse`), use `unknown` and validate. Reserve `any` only for cases where even `unknown` is infeasible and document with an inline disable that has a description.
|
|
214
|
+
|
|
215
|
+
#### `@typescript-eslint/no-unnecessary-type-assertion` (Error after upgrading typescript-eslint)
|
|
216
|
+
|
|
217
|
+
> This assertion is unnecessary since it does not change the type of the expression.
|
|
218
|
+
|
|
219
|
+
**Fix**: run `pnpm lint:fix`. The autofix removes the redundant assertion.
|
|
220
|
+
|
|
221
|
+
#### `@typescript-eslint/no-misused-promises` (Warning)
|
|
222
|
+
|
|
223
|
+
> Promise returned in function argument where a void return was expected.
|
|
224
|
+
|
|
225
|
+
**Fix**: explicitly `void` or `.catch()` the promise:
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
// Wrong
|
|
229
|
+
button.onclick = async () => { await doThing(); };
|
|
230
|
+
|
|
231
|
+
// Right (fire-and-forget)
|
|
232
|
+
button.onclick = () => { void doThing(); };
|
|
233
|
+
|
|
234
|
+
// Better (handle errors)
|
|
235
|
+
button.onclick = () => { doThing().catch(console.error); };
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### `@typescript-eslint/no-floating-promises` (Warning)
|
|
239
|
+
|
|
240
|
+
> Promises must be awaited, end with a call to .catch, or be explicitly marked as ignored with the `void` operator.
|
|
241
|
+
|
|
242
|
+
Same fix as above.
|
|
243
|
+
|
|
244
|
+
### `Plugin name should not be in all caps` (manifest.json)
|
|
245
|
+
|
|
246
|
+
Lowercase the `name` field in `manifest.json`. Title case is fine; all caps is not.
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
// Wrong
|
|
250
|
+
"name": "SEO"
|
|
251
|
+
|
|
252
|
+
// Right
|
|
253
|
+
"name": "SEO Checker" // proper noun is OK
|
|
254
|
+
"name": "Search engine optimization"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## ESLint Config Pattern for the Type-Aware Rules
|
|
258
|
+
|
|
259
|
+
When you bump `eslint-plugin-obsidianmd` to a version that adds type-aware rules (0.3.0+), the rules will fail on non-TypeScript files because there is no `parserOptions.project` for them. Scope the recommended config to TS only:
|
|
260
|
+
|
|
261
|
+
```js
|
|
262
|
+
import obsidianmd from "eslint-plugin-obsidianmd";
|
|
263
|
+
|
|
264
|
+
export default defineConfig([
|
|
265
|
+
{ ignores: ["main.js", "node_modules/**", "*.js", "scripts/**"] },
|
|
266
|
+
// Only run obsidianmd recommended rules on TypeScript files
|
|
267
|
+
...obsidianmd.configs.recommended.map((config) => ({
|
|
268
|
+
...config,
|
|
269
|
+
files: config.files ?? ["**/*.ts"],
|
|
270
|
+
})),
|
|
271
|
+
{
|
|
272
|
+
files: ["**/*.ts"],
|
|
273
|
+
languageOptions: {
|
|
274
|
+
parser: tsparser,
|
|
275
|
+
parserOptions: { project: "./tsconfig.json", sourceType: "module" },
|
|
276
|
+
// ...
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
]);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Verification Workflow
|
|
283
|
+
|
|
284
|
+
After applying fixes:
|
|
285
|
+
|
|
286
|
+
1. `pnpm install` to refresh the lockfile.
|
|
287
|
+
2. `pnpm why <vulnerable-pkg>` for each advisory to confirm the patched version is resolved everywhere.
|
|
288
|
+
3. `pnpm build` to confirm the production build still works.
|
|
289
|
+
4. `pnpm lint` to confirm no new errors.
|
|
290
|
+
5. Bump `version` in `manifest.json`, `package.json`, and `versions.json`.
|
|
291
|
+
6. Commit, then tag and push the tag. The release workflow handles the rest.
|
|
292
|
+
7. Wait for the scorecard to refresh (usually within an hour of the release).
|
|
293
|
+
|
|
294
|
+
## What the Scorecard Doesn't Penalize
|
|
295
|
+
|
|
296
|
+
- Plugin runtime API surface (vault read/write, clipboard, network) ā these become **Disclosures** and inform the user, not deductions.
|
|
297
|
+
- Obsidian API breadth ā using lots of API is fine and expected.
|
|
298
|
+
- Plugin file count or repo size.
|
|
299
|
+
- The plugin's actual functionality or popularity (Maintenance and Adoption are reported separately and do not require code changes).
|
|
300
|
+
|
|
301
|
+
## Related Documentation
|
|
302
|
+
|
|
303
|
+
- [contributing-template.md](contributing-template.md) ā canonical CONTRIBUTING.md text.
|
|
304
|
+
- [versioning-releases.md](versioning-releases.md) ā release workflow setup.
|
|
305
|
+
- [security-privacy.md](security-privacy.md) ā dependency vulnerability handling.
|
|
306
|
+
- [release-readiness.md](release-readiness.md) ā broader pre-release checklist.
|
|
307
|
+
- [code-patterns.md](../../obsidian-dev/references/code-patterns.md) ā idiomatic API usage that satisfies obsidianmd rules.
|
|
308
|
+
- [ux-copy.md](../../obsidian-ref/references/ux-copy.md) ā sentence case and other UI text rules.
|
|
@@ -51,9 +51,43 @@ If your theme requires any of the following, you **must** disclose it clearly in
|
|
|
51
51
|
|
|
52
52
|
Themes are CSS-only and have minimal security surface area, but still follow privacy guidelines for any optional features.
|
|
53
53
|
|
|
54
|
+
## Dependency Vulnerability Hygiene (Plugins)
|
|
55
|
+
|
|
56
|
+
The community scorecard scans the lockfile and flags any package with a known advisory, even if the package is dev-only and never ships in `main.js`. Common offenders are transitives pulled in by `eslint`, `eslint-plugin-obsidianmd`, and `@typescript-eslint/*`: `minimatch`, `picomatch`, `brace-expansion`, `ajv`, `flatted`, `fast-uri`, `yaml`.
|
|
57
|
+
|
|
58
|
+
Use `pnpm.overrides` in `package.json` to force patched versions of vulnerable transitives without changing direct dependencies. Major-version-scoped ranges avoid breaking consumers that depend on a specific major:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"pnpm": {
|
|
63
|
+
"overrides": {
|
|
64
|
+
"minimatch@<3.1.3": "^3.1.3",
|
|
65
|
+
"minimatch@^4 || ^5 || ^6 || ^7 || ^8": "^8.0.5",
|
|
66
|
+
"minimatch@^9": "^9.0.6",
|
|
67
|
+
"picomatch@<2.3.2": "^2.3.2",
|
|
68
|
+
"picomatch@^3 || ^4": "^4.0.4",
|
|
69
|
+
"brace-expansion@<1.1.13": "^1.1.13",
|
|
70
|
+
"brace-expansion@^2": "^2.0.3",
|
|
71
|
+
"ajv@<6.14.0": "^6.14.0",
|
|
72
|
+
"ajv@^7 || ^8": "^8.18.0",
|
|
73
|
+
"flatted@<3.4.0": "^3.4.0",
|
|
74
|
+
"fast-uri@<3.1.1": "^3.1.1",
|
|
75
|
+
"yaml@<2.8.3": "^2.8.3"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Verify with `pnpm install` then `pnpm why <pkg>` for each advisory. Every resolved version should be at or above the advisory's fix version.
|
|
82
|
+
|
|
83
|
+
If a direct dependency (e.g. `eslint-plugin-obsidianmd`) has a newer release that already pulls in patched transitives, prefer bumping the direct dep before adding an override. Overrides are appropriate when no upstream release covers your case.
|
|
84
|
+
|
|
85
|
+
See [scorecard-compliance.md](scorecard-compliance.md) for the full set of scorecard signals and their fixes.
|
|
86
|
+
|
|
54
87
|
## Related Documentation
|
|
55
88
|
|
|
56
89
|
- [release-readiness.md](release-readiness.md) - Comprehensive release checklist including policy adherence
|
|
90
|
+
- [scorecard-compliance.md](scorecard-compliance.md) - Mapping of scorecard signals to concrete fixes
|
|
57
91
|
- [manifest.md](../../obsidian-ref/references/manifest.md) - Manifest requirements (includes security-related fields)
|
|
58
92
|
- [Developer Policies](https://docs.obsidian.md/Developer+policies) - Official Obsidian Developer Policies
|
|
59
93
|
- [Theme Guidelines](https://docs.obsidian.md/Themes/Releasing/Theme+guidelines) - Official Theme Guidelines
|
|
@@ -18,6 +18,57 @@ Update frequency: Check Obsidian Sample Theme repo for updates
|
|
|
18
18
|
- Attach `main.js`, `manifest.json`, and `styles.css` to the release as individual assets.
|
|
19
19
|
- Follow the plugin submission process to add/update your plugin in the community catalog.
|
|
20
20
|
|
|
21
|
+
### Automated plugin releases with build provenance attestation
|
|
22
|
+
|
|
23
|
+
The Obsidian community scorecard penalizes plugin releases whose assets are missing a GitHub artifact attestation. The recommended way to satisfy this signal is a tag-triggered release workflow that runs `actions/attest-build-provenance@v2` against the release files.
|
|
24
|
+
|
|
25
|
+
Drop the following at `.github/workflows/release.yml`:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
name: Release
|
|
29
|
+
|
|
30
|
+
on:
|
|
31
|
+
push:
|
|
32
|
+
tags:
|
|
33
|
+
- "*"
|
|
34
|
+
|
|
35
|
+
permissions:
|
|
36
|
+
contents: write
|
|
37
|
+
id-token: write
|
|
38
|
+
attestations: write
|
|
39
|
+
|
|
40
|
+
jobs:
|
|
41
|
+
release:
|
|
42
|
+
runs-on: ubuntu-latest
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v4
|
|
45
|
+
- uses: pnpm/action-setup@v4
|
|
46
|
+
- uses: actions/setup-node@v4
|
|
47
|
+
with:
|
|
48
|
+
node-version: "20"
|
|
49
|
+
cache: "pnpm"
|
|
50
|
+
- run: pnpm install --frozen-lockfile
|
|
51
|
+
- run: pnpm build
|
|
52
|
+
- uses: actions/attest-build-provenance@v2
|
|
53
|
+
with:
|
|
54
|
+
subject-path: |
|
|
55
|
+
main.js
|
|
56
|
+
styles.css
|
|
57
|
+
manifest.json
|
|
58
|
+
- env:
|
|
59
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
60
|
+
TAG: ${{ github.ref_name }}
|
|
61
|
+
run: |
|
|
62
|
+
gh release create "$TAG" \
|
|
63
|
+
--title="$TAG" \
|
|
64
|
+
--generate-notes \
|
|
65
|
+
main.js styles.css manifest.json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Cut releases by pushing a tag (`git tag 0.1.0 && git push origin 0.1.0`). The workflow attaches the three required assets and registers the attestation. The scorecard's "Build verified" signal also fires once the workflow has run, because the attested artifacts can be reproduced byte-for-byte from source.
|
|
69
|
+
|
|
70
|
+
For the full set of scorecard signals and their fixes, see [scorecard-compliance.md](scorecard-compliance.md).
|
|
71
|
+
|
|
21
72
|
> [!NOTE]
|
|
22
73
|
> Themes and plugins have different asset requirements and submission paths. Ensure you follow the correct flow for your project type.
|
|
23
74
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obsidian-dev-skills",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Agent skills for Obsidian plugin and theme development",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Agent skills for Obsidian plugin and theme development, including community scorecard compliance, release workflow attestation, and dependency vulnerability hygiene.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"obsidian",
|
|
7
7
|
"plugin",
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
"development",
|
|
10
10
|
"skills",
|
|
11
11
|
"ai",
|
|
12
|
-
"agent"
|
|
12
|
+
"agent",
|
|
13
|
+
"scorecard",
|
|
14
|
+
"compliance"
|
|
13
15
|
],
|
|
14
16
|
"homepage": "https://github.com/davidvkimball/obsidian-dev-skills",
|
|
15
17
|
"repository": {
|
|
@@ -18,12 +20,14 @@
|
|
|
18
20
|
},
|
|
19
21
|
"license": "MIT",
|
|
20
22
|
"author": "David V. Kimball",
|
|
23
|
+
"funding": "https://patreon.com/davidvkimball",
|
|
21
24
|
"files": [
|
|
22
25
|
"obsidian-dev/",
|
|
23
26
|
"obsidian-theme-dev/",
|
|
24
27
|
"obsidian-ops/",
|
|
25
28
|
"obsidian-ref/",
|
|
26
|
-
"scripts/"
|
|
29
|
+
"scripts/init.mjs",
|
|
30
|
+
"scripts/setup-local.ps1"
|
|
27
31
|
],
|
|
28
32
|
"bin": {
|
|
29
33
|
"obsidian-dev-skills": "./scripts/init.mjs"
|
|
@@ -32,6 +36,6 @@
|
|
|
32
36
|
"postinstall": "node ./scripts/init.mjs"
|
|
33
37
|
},
|
|
34
38
|
"engines": {
|
|
35
|
-
"node": ">=
|
|
39
|
+
"node": ">=18.0.0"
|
|
36
40
|
}
|
|
37
41
|
}
|
package/scripts/setup-local.ps1
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
# Setup skills symlinks for
|
|
2
|
-
# This script creates symlinks to the obsidian-dev-skills repository
|
|
1
|
+
# Setup skills symlinks for a downstream project
|
|
2
|
+
# This script creates symlinks to the obsidian-dev-skills repository.
|
|
3
|
+
# Intended to be copied into a downstream project's scripts/ directory.
|
|
4
|
+
# The default $SkillsRepoPath assumes obsidian-dev-skills is a sibling
|
|
5
|
+
# of the downstream project's repository root.
|
|
3
6
|
|
|
4
7
|
param(
|
|
5
|
-
[string]$SkillsRepoPath = "$PSScriptRoot
|
|
8
|
+
[string]$SkillsRepoPath = "$PSScriptRoot\..\..\obsidian-dev-skills"
|
|
6
9
|
)
|
|
7
10
|
|
|
8
11
|
$ErrorActionPreference = "Stop"
|
package/scripts/bulk-update.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = path.dirname(__filename);
|
|
7
|
-
const initScript = path.join(__dirname, 'init.mjs');
|
|
8
|
-
|
|
9
|
-
const projects = [
|
|
10
|
-
"C:\\Users\\david\\Development\\obsidian-alias-filename-history",
|
|
11
|
-
"C:\\Users\\david\\Development\\obsidian-astro-composer",
|
|
12
|
-
"C:\\Users\\david\\Development\\obsidian-astro-modular-settings",
|
|
13
|
-
"C:\\Users\\david\\Development\\obsidian-bases-cms",
|
|
14
|
-
"C:\\Users\\david\\Development\\obsidian-custom-slides",
|
|
15
|
-
"C:\\Users\\david\\Development\\obsidian-disable-tabs",
|
|
16
|
-
"C:\\Users\\david\\Development\\obsidian-explorer-focus",
|
|
17
|
-
"C:\\Users\\david\\Development\\obsidian-home-base",
|
|
18
|
-
"C:\\Users\\david\\Development\\obsidian-image-manager",
|
|
19
|
-
"C:\\Users\\david\\Development\\obsidian-oxygen-settings",
|
|
20
|
-
"C:\\Users\\david\\Development\\obsidian-periodic-links",
|
|
21
|
-
"C:\\Users\\david\\Development\\obsidian-property-over-file-name",
|
|
22
|
-
"C:\\Users\\david\\Development\\obsidian-seo",
|
|
23
|
-
"C:\\Users\\david\\Development\\obsidian-ui-tweaker",
|
|
24
|
-
"C:\\Users\\david\\Development\\obsidian-vault-cms",
|
|
25
|
-
"C:\\Users\\david\\Development\\obsidian-zenmode"
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
for (const project of projects) {
|
|
29
|
-
console.log(`\nš Updating skills for: ${path.basename(project)}`);
|
|
30
|
-
try {
|
|
31
|
-
execSync(`node "${initScript}"`, {
|
|
32
|
-
cwd: project,
|
|
33
|
-
stdio: 'inherit',
|
|
34
|
-
env: { ...process.env, INIT_CWD: project }
|
|
35
|
-
});
|
|
36
|
-
console.log(`ā
Success: ${path.basename(project)}`);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error(`ā Failed: ${path.basename(project)} - ${error.message}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
package/scripts/check-status.mjs
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
|
|
4
|
-
const projects = [
|
|
5
|
-
"C:\\Users\\david\\Development\\obsidian-alias-filename-history",
|
|
6
|
-
"C:\\Users\\david\\Development\\obsidian-astro-composer",
|
|
7
|
-
"C:\\Users\\david\\Development\\obsidian-astro-modular-settings",
|
|
8
|
-
"C:\\Users\\david\\Development\\obsidian-bases-cms",
|
|
9
|
-
"C:\\Users\\david\\Development\\obsidian-custom-slides",
|
|
10
|
-
"C:\\Users\\david\\Development\\obsidian-disable-tabs",
|
|
11
|
-
"C:\\Users\\david\\Development\\obsidian-explorer-focus",
|
|
12
|
-
"C:\\Users\\david\\Development\\obsidian-home-base",
|
|
13
|
-
"C:\\Users\\david\\Development\\obsidian-image-manager",
|
|
14
|
-
"C:\\Users\\david\\Development\\obsidian-oxygen-settings",
|
|
15
|
-
"C:\\Users\\david\\Development\\obsidian-periodic-links",
|
|
16
|
-
"C:\\Users\\david\\Development\\obsidian-property-over-file-name",
|
|
17
|
-
"C:\\Users\\david\\Development\\obsidian-seo",
|
|
18
|
-
"C:\\Users\\david\\Development\\obsidian-ui-tweaker",
|
|
19
|
-
"C:\\Users\\david\\Development\\obsidian-vault-cms",
|
|
20
|
-
"C:\\Users\\david\\Development\\obsidian-zenmode"
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
console.log('| Project | Status | Last Sync | Source |');
|
|
24
|
-
console.log('| --- | --- | --- | --- |');
|
|
25
|
-
|
|
26
|
-
for (const project of projects) {
|
|
27
|
-
const name = path.basename(project);
|
|
28
|
-
let agentDir = path.join(project, '.agent');
|
|
29
|
-
if (!fs.existsSync(agentDir) && fs.existsSync(path.join(project, '.agents'))) {
|
|
30
|
-
agentDir = path.join(project, '.agents');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const syncStatusPath = path.join(agentDir, 'sync-status.json');
|
|
34
|
-
|
|
35
|
-
if (fs.existsSync(syncStatusPath)) {
|
|
36
|
-
try {
|
|
37
|
-
const status = JSON.parse(fs.readFileSync(syncStatusPath, 'utf8'));
|
|
38
|
-
console.log(`| ${name} | ā
Found | ${status.lastFullSync || 'Unknown'} | ${status.lastSyncSource || 'Unknown'} |`);
|
|
39
|
-
} catch (e) {
|
|
40
|
-
console.log(`| ${name} | ā ļø Parse Error | - | - |`);
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
console.log(`| ${name} | ā Missing | - | - |`);
|
|
44
|
-
}
|
|
45
|
-
}
|