pochade-obsidian 0.1.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 ADDED
@@ -0,0 +1,88 @@
1
+ ![Pochade Obsidian Splash Image](./pochade-js-logo.jpg)
2
+
3
+ # Pochade Obsidian Plugin Generator
4
+
5
+ ## Write Obsidian Plugins with Passion
6
+
7
+ **Pochade Obsidian** is a modern boilerplate generator for [Obsidian](https://obsidian.md/) plugins. It sets you up with a robust, modular TypeScript environment designed for Agentic AI workflows and clean architecture.
8
+
9
+ It includes **Context Engineering** files (`LLM.md`, `AGENTS.md`) specifically designed to help Large Language Models (like Gemini, ChatGPT, Claude) understand and generate high-quality Obsidian plugin code.
10
+
11
+ ## Quick Start
12
+
13
+ Create a new plugin with a single command:
14
+
15
+ ```bash
16
+ npx pochade-obsidian-plugin my-plugin-name
17
+ ```
18
+
19
+ The interactive CLI will ask for:
20
+ 1. **Plugin Name & Description**
21
+ 2. **Author Name**
22
+ 3. **Commands** (comma-separated list of commands to scaffold)
23
+
24
+ It will then:
25
+ * 🚀 Scaffold a new directory with a modular `src/` structure.
26
+ * 📦 Install dependencies automatically.
27
+ * ✨ Generate boilerplate code for your specific commands.
28
+ * 🔧 Configure `esbuild` and `TypeScript`.
29
+ * 🔥 Set up Hot Reload support.
30
+
31
+ ## Project Structure
32
+
33
+ Unlike the default sample plugin, Pochade Obsidian enforces a scalable `src/` structure:
34
+
35
+ ```
36
+ my-plugin-name/
37
+ ├── .hotreload # Trigger file for Obsidian Hot Reload
38
+ ├── AGENTS.md # Guide for AI Agents
39
+ ├── LLM.md # Detailed plugin dev guide for LLMs
40
+ ├── manifest.json # Plugin metadata
41
+ ├── esbuild.config.mjs # Build config
42
+ ├── src/
43
+ │ ├── main.ts # Entry point (Lifecycle & Registration)
44
+ │ ├── settings.ts # Settings Tab & Interface
45
+ │ └── commands/ # Modular command files
46
+ │ ├── insert-date.ts
47
+ │ └── format-table.ts
48
+ └── styles.css
49
+ ```
50
+
51
+ ## AI-Assisted Development
52
+
53
+ This template is built to work seamlessly with AI coding assistants.
54
+
55
+ * **`LLM.md`**: A comprehensive guide on Obsidian API constraints (`requestUrl` vs `fetch`), file structure, and best practices. Feed this to your AI context.
56
+ * **`AGENTS.md`**: High-level architectural rules.
57
+
58
+ ## Development Workflow
59
+
60
+ 1. **Go to your project:**
61
+ ```bash
62
+ cd my-plugin-name
63
+ ```
64
+
65
+ 2. **Start development build (Watch mode):**
66
+ ```bash
67
+ npm run dev
68
+ ```
69
+
70
+ 3. **Install in Obsidian:**
71
+ * Copy your `my-plugin-name` folder to `<Vault>/.obsidian/plugins/`.
72
+ * **Pro Tip**: Use a symlink to avoid copying manually.
73
+
74
+ 4. **Enable Hot Reload:**
75
+ * Install the [Hot Reload](https://github.com/pjeby/hot-reload) plugin in Obsidian.
76
+ * Enable your plugin in Community Plugins.
77
+ * Changes will automatically reload the plugin!
78
+
79
+ ## Features
80
+
81
+ * **Interactive CLI**: Define your commands upfront.
82
+ * **Modular Architecture**: Separates concerns by default (commands, settings, main).
83
+ * **Strict TypeScript**: configured for safety.
84
+ * **Agent Ready**: Documentation included to bootstrap AI understanding.
85
+
86
+ ## License
87
+
88
+ Unlicense (Public Domain). Write code freely.
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-pochade-js (Obsidian Plugin Edition)
5
+ *
6
+ * Creates a new Obsidian plugin project from the template.
7
+ */
8
+
9
+ const spawn = require('cross-spawn');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const readline = require('readline');
13
+
14
+ // Utilities
15
+ const rl = readline.createInterface({
16
+ input: process.stdin,
17
+ output: process.stdout
18
+ });
19
+
20
+ const ask = (question, defaultVal = '') => {
21
+ return new Promise((resolve) => {
22
+ const prompt = defaultVal ? `${question} (${defaultVal}): ` : `${question}: `;
23
+ rl.question(prompt, (answer) => {
24
+ resolve(answer.trim() || defaultVal);
25
+ });
26
+ });
27
+ };
28
+
29
+ // CamelCase to kebab-case
30
+ const toKebabCase = (str) => {
31
+ return str
32
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
33
+ .replace(/[\s_]+/g, '-')
34
+ .toLowerCase();
35
+ };
36
+
37
+ // CamelCase helper for class names
38
+ const toCamelCase = (str) => {
39
+ return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
40
+ return index === 0 ? word.toLowerCase() : word.toUpperCase();
41
+ }).replace(/\s+/g, '');
42
+ };
43
+
44
+ const toPascalCase = (str) => {
45
+ const camel = toCamelCase(str);
46
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
47
+ }
48
+
49
+ async function createPlugin() {
50
+ const logo = ".-. .-. .-. . . .-. .-. .-. . .-.\r\n|-\' | | | |-| |-| | )|- | `-.\r\n\' `-\' `-\' \' ` ` \' `-\' `-\' `-\' `-\'\r\n Obsidian Plugin Gen\r\n By LNSY\r\n"
51
+ console.log(logo);
52
+
53
+ // 1. Collect Info
54
+ let projectName = process.argv[2];
55
+ if (!projectName) {
56
+ projectName = await ask('Plugin ID (kebab-case folder name)', 'my-obsidian-plugin');
57
+ }
58
+
59
+ // Ensure kebab case
60
+ projectName = toKebabCase(projectName);
61
+
62
+ const pluginName = await ask('Plugin Name (Human Readable)', toPascalCase(projectName));
63
+ const pluginDesc = await ask('Description', 'A super cool Obsidian plugin');
64
+ const author = await ask('Author', 'Mister Anderson');
65
+
66
+ // Ask for commands
67
+ console.log('\nDefine your commands. Enter them as a comma-separated list.');
68
+ console.log('e.g. "Insert Date, Format Table, Toggle Mode"');
69
+ const commandsInput = await ask('Commands', '');
70
+ const commands = commandsInput.split(',').map(c => c.trim()).filter(c => c.length > 0);
71
+
72
+ const projectDir = path.resolve(process.cwd(), projectName);
73
+
74
+ if (fs.existsSync(projectDir)) {
75
+ console.error(`\n❌ Error: Directory "${projectName}" already exists.`);
76
+ process.exit(1);
77
+ }
78
+
79
+ console.log(`\n🚀 Scaffolding ${pluginName} in ${projectDir}...`);
80
+
81
+ // 2. Create Directory & Copy Template
82
+ fs.mkdirSync(projectDir, { recursive: true });
83
+
84
+ const templateDir = path.resolve(__dirname, '..', 'template');
85
+ fs.cpSync(templateDir, projectDir, { recursive: true });
86
+
87
+ // 3. Structure Restructuring (Move to src/)
88
+ const srcDir = path.join(projectDir, 'src');
89
+ fs.mkdirSync(srcDir);
90
+ const cmdDir = path.join(srcDir, 'commands');
91
+ fs.mkdirSync(cmdDir);
92
+
93
+ // Move main.ts and delete old one
94
+ // We will actually GENERATE a completely new main.ts, so we can delete the template one.
95
+ if (fs.existsSync(path.join(projectDir, 'main.ts'))) {
96
+ fs.unlinkSync(path.join(projectDir, 'main.ts'));
97
+ }
98
+
99
+ // 4. Generate Command Files
100
+ const commandImports = [];
101
+ const commandRegistrations = [];
102
+
103
+ commands.forEach(cmd => {
104
+ const cmdId = toKebabCase(cmd);
105
+ const cmdFnName = toCamelCase(cmd);
106
+ const cmdFileName = `${cmdId}.ts`;
107
+ const cmdPath = path.join(cmdDir, cmdFileName);
108
+
109
+ const fileContent = `import { Editor, MarkdownView, Notice } from 'obsidian';
110
+ import { MyPluginSettings } from '../settings';
111
+
112
+ export function ${cmdFnName}(editor: Editor, settings: MyPluginSettings) {
113
+ new Notice('Running ${cmd}!');
114
+ console.log('${cmd} requested');
115
+ // Implementation here
116
+ const cursor = editor.getCursor();
117
+ editor.replaceRange(' [${cmd}] ', cursor);
118
+ }
119
+ `;
120
+ fs.writeFileSync(cmdPath, fileContent);
121
+
122
+ commandImports.push(`import { ${cmdFnName} } from './commands/${cmdId}';`);
123
+ commandRegistrations.push(`
124
+ this.addCommand({
125
+ id: '${cmdId}',
126
+ name: '${cmd}',
127
+ editorCallback: (editor: Editor, view: MarkdownView) => {
128
+ ${cmdFnName}(editor, this.settings);
129
+ }
130
+ });`);
131
+ });
132
+
133
+ // 5. Generate Settings.ts
134
+ const settingsContent = `import { App, PluginSettingTab, Setting } from 'obsidian';
135
+ import MyPlugin from './main';
136
+
137
+ export interface MyPluginSettings {
138
+ mySetting: string;
139
+ }
140
+
141
+ export const DEFAULT_SETTINGS: MyPluginSettings = {
142
+ mySetting: 'default'
143
+ }
144
+
145
+ export class SampleSettingTab extends PluginSettingTab {
146
+ plugin: MyPlugin;
147
+
148
+ constructor(app: App, plugin: MyPlugin) {
149
+ super(app, plugin);
150
+ this.plugin = plugin;
151
+ }
152
+
153
+ display(): void {
154
+ const {containerEl} = this;
155
+ containerEl.empty();
156
+
157
+ new Setting(containerEl)
158
+ .setName('Setting #1')
159
+ .setDesc('It\\'s a secret')
160
+ .addText(text => text
161
+ .setPlaceholder('Enter your secret')
162
+ .setValue(this.plugin.settings.mySetting)
163
+ .onChange(async (value) => {
164
+ this.plugin.settings.mySetting = value;
165
+ await this.plugin.saveSettings();
166
+ }));
167
+ }
168
+ }
169
+ `;
170
+ fs.writeFileSync(path.join(srcDir, 'settings.ts'), settingsContent);
171
+
172
+ // 6. Generate main.ts
173
+ const mainContent = `import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
174
+ import { MyPluginSettings, DEFAULT_SETTINGS, SampleSettingTab } from './settings';
175
+ ${commandImports.join('\n')}
176
+
177
+ export default class MyPlugin extends Plugin {
178
+ settings: MyPluginSettings;
179
+
180
+ async onload() {
181
+ await this.loadSettings();
182
+
183
+ // Settings Tab
184
+ this.addSettingTab(new SampleSettingTab(this.app, this));
185
+
186
+ // Commands
187
+ ${commandRegistrations.join('')}
188
+ }
189
+
190
+ onunload() {
191
+
192
+ }
193
+
194
+ async loadSettings() {
195
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
196
+ }
197
+
198
+ async saveSettings() {
199
+ await this.saveData(this.settings);
200
+ }
201
+ }
202
+ `;
203
+ fs.writeFileSync(path.join(srcDir, 'main.ts'), mainContent);
204
+
205
+ // 7. Update esbuild.config.mjs to point to src/main.ts
206
+ const esbuildPath = path.join(projectDir, 'esbuild.config.mjs');
207
+ let esbuildContent = fs.readFileSync(esbuildPath, 'utf8');
208
+ esbuildContent = esbuildContent.replace('entryPoints: ["main.ts"]', 'entryPoints: ["src/main.ts"]');
209
+ fs.writeFileSync(esbuildPath, esbuildContent);
210
+
211
+ // 8. Update manifest.json
212
+ const manifestPath = path.join(projectDir, 'manifest.json');
213
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
214
+ manifest.id = projectName;
215
+ manifest.name = pluginName;
216
+ manifest.description = pluginDesc;
217
+ manifest.author = author;
218
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, '\t'));
219
+
220
+ // 9. Update package.json
221
+ const pkgPath = path.join(projectDir, 'package.json');
222
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
223
+ pkg.name = projectName;
224
+ pkg.description = pluginDesc;
225
+ pkg.author = author;
226
+ delete pkg.bin; // remove bin from generated project
227
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
228
+
229
+ // 10. Create .hotreload file
230
+ fs.writeFileSync(path.join(projectDir, '.hotreload'), '');
231
+
232
+ // 11. Rename dotfiles
233
+ const dotfiles = [
234
+ { from: 'gitignore', to: '.gitignore' },
235
+ { from: 'npmignore', to: '.npmignore' },
236
+ { from: 'envexample', to: '.env.example' }
237
+ ];
238
+ dotfiles.forEach(({ from, to }) => {
239
+ const fromPath = path.join(projectDir, from);
240
+ if (fs.existsSync(fromPath)) fs.renameSync(fromPath, path.join(projectDir, to));
241
+ });
242
+
243
+ console.log('\n📦 Installing dependencies...');
244
+ spawn.sync('npm', ['install'], { cwd: projectDir, stdio: 'inherit' });
245
+
246
+ console.log(`\n✨ Success! Created ${pluginName} at ./${projectName}`);
247
+ console.log('\nNext steps:');
248
+ console.log(`1. cd ${projectName}`);
249
+ console.log('2. npm run dev');
250
+ console.log('3. Open Obsidian settings -> Community Plugins -> Enable Your Plugin');
251
+ console.log('4. We recommend installing the Obsidian Hot Reload plugin for live reloading');
252
+
253
+ rl.close();
254
+ }
255
+
256
+ createPlugin();
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "pochade-obsidian",
3
+ "version": "0.1.0",
4
+ "description": "npx starter template for Obsidian Plugins",
5
+ "bin": {
6
+ "create-pochade-js": "./bin/create-pochade-js.js"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/lnsy-dev/pochade-obsidian.git"
11
+ },
12
+ "keywords": [
13
+ "obsidian",
14
+ "plugin",
15
+ "notes"
16
+ ],
17
+ "author": "LNSY",
18
+ "license": "Unlicense",
19
+ "bugs": {
20
+ "url": "https://github.com/lnsy-dev/pochade-obsidian/issues"
21
+ },
22
+ "homepage": "https://github.com/lnsy-dev/pochade-obsidian#readme",
23
+ "dependencies": {
24
+ "cross-spawn": "^7.0.6"
25
+ },
26
+ "files": [
27
+ "bin/",
28
+ "template/"
29
+ ]
30
+ }
@@ -0,0 +1,10 @@
1
+ # top-most EditorConfig file
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+ indent_style = tab
9
+ indent_size = 4
10
+ tab_width = 4
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+
3
+ main.js
@@ -0,0 +1,23 @@
1
+ {
2
+ "root": true,
3
+ "parser": "@typescript-eslint/parser",
4
+ "env": { "node": true },
5
+ "plugins": [
6
+ "@typescript-eslint"
7
+ ],
8
+ "extends": [
9
+ "eslint:recommended",
10
+ "plugin:@typescript-eslint/eslint-recommended",
11
+ "plugin:@typescript-eslint/recommended"
12
+ ],
13
+ "parserOptions": {
14
+ "sourceType": "module"
15
+ },
16
+ "rules": {
17
+ "no-unused-vars": "off",
18
+ "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
19
+ "@typescript-eslint/ban-ts-comment": "off",
20
+ "no-prototype-builtins": "off",
21
+ "@typescript-eslint/no-empty-function": "off"
22
+ }
23
+ }
@@ -0,0 +1,252 @@
1
+ # Obsidian community plugin
2
+
3
+ ## Project overview
4
+
5
+ - Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
6
+ - Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian.
7
+ - Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`.
8
+
9
+ ## Environment & tooling
10
+
11
+ - Node.js: use current LTS (Node 18+ recommended).
12
+ - **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies).
13
+ - **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`.
14
+ - Types: `obsidian` type definitions.
15
+ - **Network Calls**: usage of `requestUrl` from the `obsidian` module is **MANDATORY** for all network requests. Do not use `fetch` or node's `http` module to ensure compatibility and avoid CORS issues.
16
+
17
+ **Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly.
18
+
19
+ ### Install
20
+
21
+ ```bash
22
+ npm install
23
+ ```
24
+
25
+ ### Dev (watch)
26
+
27
+ ```bash
28
+ npm run dev
29
+ ```
30
+
31
+ ### Production build
32
+
33
+ ```bash
34
+ npm run build
35
+ ```
36
+
37
+ ## Linting
38
+
39
+ - To use eslint install eslint from terminal: `npm install -g eslint`
40
+ - To use eslint to analyze this project use this command: `eslint main.ts`
41
+ - eslint will then create a report with suggestions for code improvement by file and line number.
42
+ - If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/`
43
+
44
+ ## File & folder conventions
45
+
46
+ - **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
47
+ - Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
48
+ - **Example file structure**:
49
+ ```
50
+ src/
51
+ main.ts # Plugin entry point, lifecycle management
52
+ settings.ts # Settings interface and defaults
53
+ commands/ # Command implementations
54
+ command1.ts
55
+ command2.ts
56
+ ui/ # UI components, modals, views
57
+ modal.ts
58
+ view.ts
59
+ utils/ # Utility functions, helpers
60
+ helpers.ts
61
+ constants.ts
62
+ types.ts # TypeScript interfaces and types
63
+ ```
64
+ - **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control.
65
+ - Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages.
66
+ - Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`).
67
+
68
+ ## Manifest rules (`manifest.json`)
69
+
70
+ - Must include (non-exhaustive):
71
+ - `id` (plugin ID; for local dev it should match the folder name)
72
+ - `name`
73
+ - `version` (Semantic Versioning `x.y.z`)
74
+ - `minAppVersion`
75
+ - `description`
76
+ - `isDesktopOnly` (boolean)
77
+ - Optional: `author`, `authorUrl`, `fundingUrl` (string or map)
78
+ - Never change `id` after release. Treat it as stable API.
79
+ - Keep `minAppVersion` accurate when using newer APIs.
80
+ - Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml
81
+
82
+ ## Testing
83
+
84
+ - Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to:
85
+ ```
86
+ <Vault>/.obsidian/plugins/<plugin-id>/
87
+ ```
88
+ - Reload Obsidian and enable the plugin in **Settings → Community plugins**.
89
+
90
+ ## Commands & settings
91
+
92
+ - Any user-facing commands should be added via `this.addCommand(...)`.
93
+ - If the plugin has configuration, provide a settings tab and sensible defaults.
94
+ - Persist settings using `this.loadData()` / `this.saveData()`.
95
+ - Use stable command IDs; avoid renaming once released.
96
+
97
+ ## Versioning & releases
98
+
99
+ - Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version.
100
+ - Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`.
101
+ - Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets.
102
+ - After the initial release, follow the process to add/update your plugin in the community catalog as required.
103
+
104
+ ## Security, privacy, and compliance
105
+
106
+ Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular:
107
+
108
+ - Default to local/offline operation. Only make network requests when essential to the feature.
109
+ - No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings.
110
+ - Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases.
111
+ - Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault.
112
+ - Clearly disclose any external services used, data sent, and risks.
113
+ - Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented.
114
+ - Avoid deceptive patterns, ads, or spammy notifications.
115
+ - Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely.
116
+
117
+ ## UX & copy guidelines (for UI text, commands, settings)
118
+
119
+ - Prefer sentence case for headings, buttons, and titles.
120
+ - Use clear, action-oriented imperatives in step-by-step copy.
121
+ - Use **bold** to indicate literal UI labels. Prefer "select" for interactions.
122
+ - Use arrow notation for navigation: **Settings → Community plugins**.
123
+ - Keep in-app strings short, consistent, and free of jargon.
124
+
125
+ ## Performance
126
+
127
+ - Keep startup light. Defer heavy work until needed.
128
+ - Avoid long-running tasks during `onload`; use lazy initialization.
129
+ - Batch disk access and avoid excessive vault scans.
130
+ - Debounce/throttle expensive operations in response to file system events.
131
+
132
+ ## Coding conventions
133
+
134
+ - TypeScript with `"strict": true` preferred.
135
+ - **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
136
+ - **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
137
+ - **Use clear module boundaries**: Each file should have a single, well-defined responsibility.
138
+ - Bundle everything into `main.js` (no unbundled runtime deps).
139
+ - Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly.
140
+ - Prefer `async/await` over promise chains; handle errors gracefully.
141
+
142
+ ## Mobile
143
+
144
+ - Where feasible, test on iOS and Android.
145
+ - Don't assume desktop-only behavior unless `isDesktopOnly` is `true`.
146
+ - Avoid large in-memory structures; be mindful of memory and storage constraints.
147
+
148
+ ## Agent do/don't
149
+
150
+ **Do**
151
+ - Add commands with stable IDs (don't rename once released).
152
+ - Provide defaults and validation in settings.
153
+ - Write idempotent code paths so reload/unload doesn't leak listeners or intervals.
154
+ - Use `this.register*` helpers for everything that needs cleanup.
155
+
156
+ **Don't**
157
+ - Introduce network calls without an obvious user-facing reason and documentation.
158
+ - Ship features that require cloud services without clear disclosure and explicit opt-in.
159
+ - Store or transmit vault contents unless essential and consented.
160
+
161
+ ## Common tasks
162
+
163
+ ### Organize code across multiple files
164
+
165
+ **main.ts** (minimal, lifecycle only):
166
+ ```ts
167
+ import { Plugin } from "obsidian";
168
+ import { MySettings, DEFAULT_SETTINGS } from "./settings";
169
+ import { registerCommands } from "./commands";
170
+
171
+ export default class MyPlugin extends Plugin {
172
+ settings: MySettings;
173
+
174
+ async onload() {
175
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
176
+ registerCommands(this);
177
+ }
178
+ }
179
+ ```
180
+
181
+ **settings.ts**:
182
+ ```ts
183
+ export interface MySettings {
184
+ enabled: boolean;
185
+ apiKey: string;
186
+ }
187
+
188
+ export const DEFAULT_SETTINGS: MySettings = {
189
+ enabled: true,
190
+ apiKey: "",
191
+ };
192
+ ```
193
+
194
+ **commands/index.ts**:
195
+ ```ts
196
+ import { Plugin } from "obsidian";
197
+ import { doSomething } from "./my-command";
198
+
199
+ export function registerCommands(plugin: Plugin) {
200
+ plugin.addCommand({
201
+ id: "do-something",
202
+ name: "Do something",
203
+ callback: () => doSomething(plugin),
204
+ });
205
+ }
206
+ ```
207
+
208
+ ### Add a command
209
+
210
+ ```ts
211
+ this.addCommand({
212
+ id: "your-command-id",
213
+ name: "Do the thing",
214
+ callback: () => this.doTheThing(),
215
+ });
216
+ ```
217
+
218
+ ### Persist settings
219
+
220
+ ```ts
221
+ interface MySettings { enabled: boolean }
222
+ const DEFAULT_SETTINGS: MySettings = { enabled: true };
223
+
224
+ async onload() {
225
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
226
+ await this.saveData(this.settings);
227
+ }
228
+ ```
229
+
230
+ ### Register listeners safely
231
+
232
+ ```ts
233
+ this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
234
+ this.registerDomEvent(window, "resize", () => { /* ... */ });
235
+ this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
236
+ ```
237
+
238
+ ## Troubleshooting
239
+
240
+ - Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`.
241
+ - Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code.
242
+ - Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique.
243
+ - Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes.
244
+ - Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust.
245
+
246
+ ## References
247
+
248
+ - Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
249
+ - API documentation: https://docs.obsidian.md
250
+ - Developer policies: https://docs.obsidian.md/Developer+policies
251
+ - Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
252
+ - Style guide: https://help.obsidian.md/style-guide
@@ -0,0 +1,5 @@
1
+ Copyright (C) 2020-2025 by Dynalist Inc.
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,185 @@
1
+ # Obsidian Plugin Development Guide for LLMs
2
+
3
+ This document is a comprehensive guide for Large Language Models (LLMs) on how to generate high-quality, maintainable, and compliant Obsidian plugins.
4
+
5
+ ## Core Principles
6
+
7
+ 1. **Safety & Security**:
8
+ * **NO** arbitrary network calls without user consent.
9
+ * **NO** remote code execution.
10
+ * **NO** accessing files outside the vault.
11
+ * **ALWAYS** use `requestUrl` (from `obsidian` module) for HTTP requests, not `fetch`.
12
+
13
+ 2. **Performance**:
14
+ * Minimize work in `onload`.
15
+ * Debounce file system listeners.
16
+ * Avoid reading the entire vault.
17
+
18
+ 3. **Modularity**:
19
+ * Split code into small, focused files (Single Responsibility Principle).
20
+ * Use a `src/` directory structure.
21
+ * `src/main.ts`: Entry point, lifecycle management (onload/onunload), registering commands/settings.
22
+ * `src/settings.ts`: Settings interface and Tab.
23
+ * `src/commands/`: Individual command implementations.
24
+ * `src/ui/`: Modals, Views, and other UI components.
25
+
26
+ ## TypeScript & API Usage
27
+
28
+ * **Strict Typing**: Always use TypeScript with `strict: true`. Avoid `any`.
29
+ * **Obsidian API**: Import types and classes from `obsidian`.
30
+ * `Plugin`, `App`, `Editor`, `MarkdownView`, `Modal`, `Notice`, `Setting`, `PluginSettingTab`.
31
+ * **Asynchronous Operations**: Use `async/await` for all file I/O and network operations.
32
+
33
+ ## File Structure Standard
34
+
35
+ Generate plugins using this directory structure:
36
+
37
+ ```
38
+ src/
39
+ ├── main.ts # Plugin entry point
40
+ ├── settings.ts # Settings definition & UI
41
+ ├── types.ts # Shared interfaces
42
+ ├── utils/ # Helper functions
43
+ └── commands/ # Separate file for each command logic (optional but recommended for complex commands)
44
+ ├── insert-date.ts
45
+ └── format-table.ts
46
+ styles.css # CSS styles
47
+ manifest.json # Plugin metadata
48
+ esbuild.config.mjs # Build configuration
49
+ ```
50
+
51
+ ## Implementation Patterns
52
+
53
+ ### 1. `src/main.ts` (Entry Point)
54
+
55
+ The `main.ts` should be minimal. It orchestrates the plugin's components.
56
+
57
+ ```typescript
58
+ import { Plugin } from 'obsidian';
59
+ import { MyPluginSettings, DEFAULT_SETTINGS, SampleSettingTab } from './settings';
60
+ import { insertDateCommand } from './commands/insert-date';
61
+
62
+ export default class MyPlugin extends Plugin {
63
+ settings: MyPluginSettings;
64
+
65
+ async onload() {
66
+ await this.loadSettings();
67
+
68
+ // Register Commands
69
+ this.addCommand({
70
+ id: 'insert-date',
71
+ name: 'Insert Date',
72
+ editorCallback: (editor) => insertDateCommand(editor, this.settings)
73
+ });
74
+
75
+ // Add Settings Tab
76
+ this.addSettingTab(new SampleSettingTab(this.app, this));
77
+ }
78
+
79
+ async loadSettings() {
80
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
81
+ }
82
+
83
+ async saveSettings() {
84
+ await this.saveData(this.settings);
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### 2. Network Requests (`requestUrl`)
90
+
91
+ **CRITICAL**: Do NOT use `fetch`. Use `requestUrl` to avoid CORS issues and adhere to Obsidian's API.
92
+
93
+ ```typescript
94
+ import { requestUrl, RequestUrlParam } from 'obsidian';
95
+
96
+ async function fetchData(url: string) {
97
+ try {
98
+ const response = await requestUrl({
99
+ url: url,
100
+ method: 'GET',
101
+ headers: {
102
+ 'Content-Type': 'application/json'
103
+ }
104
+ });
105
+
106
+ if (response.status !== 200) {
107
+ throw new Error(`Failed to fetch: ${response.status}`);
108
+ }
109
+
110
+ return response.json;
111
+ } catch (error) {
112
+ console.error('API Error:', error);
113
+ throw error;
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### 3. Modifing Content (Editor Transactions)
119
+
120
+ Use the `Editor` interface for text manipulation.
121
+
122
+ ```typescript
123
+ import { Editor } from 'obsidian';
124
+
125
+ export function insertText(editor: Editor, text: string) {
126
+ const cursor = editor.getCursor();
127
+ editor.replaceRange(text, cursor);
128
+ // Move cursor to end of inserted text
129
+ editor.setCursor({
130
+ line: cursor.line,
131
+ ch: cursor.ch + text.length
132
+ });
133
+ }
134
+ ```
135
+
136
+ ### 4. Settings (`src/settings.ts`)
137
+
138
+ ```typescript
139
+ import { App, PluginSettingTab, Setting } from 'obsidian';
140
+ import MyPlugin from './main';
141
+
142
+ export interface MyPluginSettings {
143
+ dateFormat: string;
144
+ }
145
+
146
+ export const DEFAULT_SETTINGS: MyPluginSettings = {
147
+ dateFormat: 'YYYY-MM-DD'
148
+ };
149
+
150
+ export class SampleSettingTab extends PluginSettingTab {
151
+ plugin: MyPlugin;
152
+
153
+ constructor(app: App, plugin: MyPlugin) {
154
+ super(app, plugin);
155
+ this.plugin = plugin;
156
+ }
157
+
158
+ display(): void {
159
+ const { containerEl } = this;
160
+ containerEl.empty();
161
+
162
+ new Setting(containerEl)
163
+ .setName('Date Format')
164
+ .setDesc('Moment.js format string')
165
+ .addText(text => text
166
+ .setPlaceholder('YYYY-MM-DD')
167
+ .setValue(this.plugin.settings.dateFormat)
168
+ .onChange(async (value) => {
169
+ this.plugin.settings.dateFormat = value;
170
+ await this.plugin.saveSettings();
171
+ }));
172
+ }
173
+ }
174
+ ```
175
+
176
+ ## Hot Reloading
177
+
178
+ This project is set up for hot reloading. A `.hotreload` file is present in the root. Ensure you have the "Hot Reload" plugin installed in Obsidian for this to work during development.
179
+
180
+ ## Build System
181
+
182
+ * **esbuild** is the bundler.
183
+ * **src/main.ts** is the entry point.
184
+ * **main.js** is the output (bundled) file.
185
+ * Do NOT modify `main.js` manually.
@@ -0,0 +1,94 @@
1
+ # Obsidian Sample Plugin
2
+
3
+ This is a sample plugin for Obsidian (https://obsidian.md).
4
+
5
+ This project uses TypeScript to provide type checking and documentation.
6
+ The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does.
7
+
8
+ This sample plugin demonstrates some of the basic functionality the plugin API can do.
9
+ - Adds a ribbon icon, which shows a Notice when clicked.
10
+ - Adds a command "Open Sample Modal" which opens a Modal.
11
+ - Adds a plugin setting tab to the settings page.
12
+ - Registers a global click event and output 'click' to the console.
13
+ - Registers a global interval which logs 'setInterval' to the console.
14
+
15
+ ## First time developing plugins?
16
+
17
+ Quick starting guide for new plugin devs:
18
+
19
+ - Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with.
20
+ - Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it).
21
+ - Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder.
22
+ - Install NodeJS, then run `npm i` in the command line under your repo folder.
23
+ - Run `npm run dev` to compile your plugin from `main.ts` to `main.js`.
24
+ - Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`.
25
+ - Reload Obsidian to load the new version of your plugin.
26
+ - Enable plugin in settings window.
27
+ - For updates to the Obsidian API run `npm update` in the command line under your repo folder.
28
+
29
+ ## Releasing new releases
30
+
31
+ - Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release.
32
+ - Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible.
33
+ - Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases
34
+ - Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release.
35
+ - Publish the release.
36
+
37
+ > You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`.
38
+ > The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json`
39
+
40
+ ## Adding your plugin to the community plugin list
41
+
42
+ - Check the [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines).
43
+ - Publish an initial version.
44
+ - Make sure you have a `README.md` file in the root of your repo.
45
+ - Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin.
46
+
47
+ ## How to use
48
+
49
+ - Clone this repo.
50
+ - Make sure your NodeJS is at least v16 (`node --version`).
51
+ - `npm i` or `yarn` to install dependencies.
52
+ - `npm run dev` to start compilation in watch mode.
53
+
54
+ ## Manually installing the plugin
55
+
56
+ - Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
57
+
58
+ ## Improve code quality with eslint (optional)
59
+ - [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code.
60
+ - To use eslint with this project, make sure to install eslint from terminal:
61
+ - `npm install -g eslint`
62
+ - To use eslint to analyze this project use this command:
63
+ - `eslint main.ts`
64
+ - eslint will then create a report with suggestions for code improvement by file and line number.
65
+ - If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder:
66
+ - `eslint ./src/`
67
+
68
+ ## Funding URL
69
+
70
+ You can include funding URLs where people who use your plugin can financially support it.
71
+
72
+ The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
73
+
74
+ ```json
75
+ {
76
+ "fundingUrl": "https://buymeacoffee.com"
77
+ }
78
+ ```
79
+
80
+ If you have multiple URLs, you can also do:
81
+
82
+ ```json
83
+ {
84
+ "fundingUrl": {
85
+ "Buy Me a Coffee": "https://buymeacoffee.com",
86
+ "GitHub Sponsor": "https://github.com/sponsors",
87
+ "Patreon": "https://www.patreon.com/"
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## API Documentation
93
+
94
+ See https://github.com/obsidianmd/obsidian-api
@@ -0,0 +1,49 @@
1
+ import esbuild from "esbuild";
2
+ import process from "process";
3
+ import builtins from "builtin-modules";
4
+
5
+ const banner =
6
+ `/*
7
+ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
8
+ if you want to view the source, please visit the github repository of this plugin
9
+ */
10
+ `;
11
+
12
+ const prod = (process.argv[2] === "production");
13
+
14
+ const context = await esbuild.context({
15
+ banner: {
16
+ js: banner,
17
+ },
18
+ entryPoints: ["main.ts"],
19
+ bundle: true,
20
+ external: [
21
+ "obsidian",
22
+ "electron",
23
+ "@codemirror/autocomplete",
24
+ "@codemirror/collab",
25
+ "@codemirror/commands",
26
+ "@codemirror/language",
27
+ "@codemirror/lint",
28
+ "@codemirror/search",
29
+ "@codemirror/state",
30
+ "@codemirror/view",
31
+ "@lezer/common",
32
+ "@lezer/highlight",
33
+ "@lezer/lr",
34
+ ...builtins],
35
+ format: "cjs",
36
+ target: "es2018",
37
+ logLevel: "info",
38
+ sourcemap: prod ? false : "inline",
39
+ treeShaking: true,
40
+ outfile: "main.js",
41
+ minify: prod,
42
+ });
43
+
44
+ if (prod) {
45
+ await context.rebuild();
46
+ process.exit(0);
47
+ } else {
48
+ await context.watch();
49
+ }
@@ -0,0 +1,134 @@
1
+ import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
2
+
3
+ // Remember to rename these classes and interfaces!
4
+
5
+ interface MyPluginSettings {
6
+ mySetting: string;
7
+ }
8
+
9
+ const DEFAULT_SETTINGS: MyPluginSettings = {
10
+ mySetting: 'default'
11
+ }
12
+
13
+ export default class MyPlugin extends Plugin {
14
+ settings: MyPluginSettings;
15
+
16
+ async onload() {
17
+ await this.loadSettings();
18
+
19
+ // This creates an icon in the left ribbon.
20
+ const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (_evt: MouseEvent) => {
21
+ // Called when the user clicks the icon.
22
+ new Notice('This is a notice!');
23
+ });
24
+ // Perform additional things with the ribbon
25
+ ribbonIconEl.addClass('my-plugin-ribbon-class');
26
+
27
+ // This adds a status bar item to the bottom of the app. Does not work on mobile apps.
28
+ const statusBarItemEl = this.addStatusBarItem();
29
+ statusBarItemEl.setText('Status Bar Text');
30
+
31
+ // This adds a simple command that can be triggered anywhere
32
+ this.addCommand({
33
+ id: 'open-sample-modal-simple',
34
+ name: 'Open sample modal (simple)',
35
+ callback: () => {
36
+ new SampleModal(this.app).open();
37
+ }
38
+ });
39
+ // This adds an editor command that can perform some operation on the current editor instance
40
+ this.addCommand({
41
+ id: 'sample-editor-command',
42
+ name: 'Sample editor command',
43
+ editorCallback: (editor: Editor, _view: MarkdownView) => {
44
+ console.log(editor.getSelection());
45
+ editor.replaceSelection('Sample Editor Command');
46
+ }
47
+ });
48
+ // This adds a complex command that can check whether the current state of the app allows execution of the command
49
+ this.addCommand({
50
+ id: 'open-sample-modal-complex',
51
+ name: 'Open sample modal (complex)',
52
+ checkCallback: (checking: boolean) => {
53
+ // Conditions to check
54
+ const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
55
+ if (markdownView) {
56
+ // If checking is true, we're simply "checking" if the command can be run.
57
+ // If checking is false, then we want to actually perform the operation.
58
+ if (!checking) {
59
+ new SampleModal(this.app).open();
60
+ }
61
+
62
+ // This command will only show up in Command Palette when the check function returns true
63
+ return true;
64
+ }
65
+ }
66
+ });
67
+
68
+ // This adds a settings tab so the user can configure various aspects of the plugin
69
+ this.addSettingTab(new SampleSettingTab(this.app, this));
70
+
71
+ // If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
72
+ // Using this function will automatically remove the event listener when this plugin is disabled.
73
+ this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
74
+ console.log('click', evt);
75
+ });
76
+
77
+ // When registering intervals, this function will automatically clear the interval when the plugin is disabled.
78
+ this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
79
+ }
80
+
81
+ onunload() {
82
+
83
+ }
84
+
85
+ async loadSettings() {
86
+ this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
87
+ }
88
+
89
+ async saveSettings() {
90
+ await this.saveData(this.settings);
91
+ }
92
+ }
93
+
94
+ class SampleModal extends Modal {
95
+ constructor(app: App) {
96
+ super(app);
97
+ }
98
+
99
+ onOpen() {
100
+ const {contentEl} = this;
101
+ contentEl.setText('Woah!');
102
+ }
103
+
104
+ onClose() {
105
+ const {contentEl} = this;
106
+ contentEl.empty();
107
+ }
108
+ }
109
+
110
+ class SampleSettingTab extends PluginSettingTab {
111
+ plugin: MyPlugin;
112
+
113
+ constructor(app: App, plugin: MyPlugin) {
114
+ super(app, plugin);
115
+ this.plugin = plugin;
116
+ }
117
+
118
+ display(): void {
119
+ const {containerEl} = this;
120
+
121
+ containerEl.empty();
122
+
123
+ new Setting(containerEl)
124
+ .setName('Setting #1')
125
+ .setDesc('It\'s a secret')
126
+ .addText(text => text
127
+ .setPlaceholder('Enter your secret')
128
+ .setValue(this.plugin.settings.mySetting)
129
+ .onChange(async (value) => {
130
+ this.plugin.settings.mySetting = value;
131
+ await this.plugin.saveSettings();
132
+ }));
133
+ }
134
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "sample-plugin",
3
+ "name": "Sample Plugin",
4
+ "version": "1.0.0",
5
+ "minAppVersion": "0.15.0",
6
+ "description": "Demonstrates some of the capabilities of the Obsidian API.",
7
+ "author": "Obsidian",
8
+ "authorUrl": "https://obsidian.md",
9
+ "fundingUrl": "https://obsidian.md/pricing",
10
+ "isDesktopOnly": false
11
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "obsidian-sample-plugin",
3
+ "version": "1.0.0",
4
+ "description": "This is a sample plugin for Obsidian (https://obsidian.md)",
5
+ "main": "main.js",
6
+ "scripts": {
7
+ "dev": "node esbuild.config.mjs",
8
+ "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
9
+ "version": "node version-bump.mjs && git add manifest.json versions.json"
10
+ },
11
+ "keywords": [],
12
+ "author": "",
13
+ "license": "MIT",
14
+ "devDependencies": {
15
+ "@types/node": "^16.11.6",
16
+ "@typescript-eslint/eslint-plugin": "5.29.0",
17
+ "@typescript-eslint/parser": "5.29.0",
18
+ "builtin-modules": "3.3.0",
19
+ "esbuild": "0.17.3",
20
+ "obsidian": "latest",
21
+ "tslib": "2.4.0",
22
+ "typescript": "4.7.4"
23
+ }
24
+ }
@@ -0,0 +1,8 @@
1
+ /*
2
+
3
+ This CSS file will be included with your plugin, and
4
+ available in the app when your plugin is enabled.
5
+
6
+ If your plugin does not need CSS, delete this file.
7
+
8
+ */
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "inlineSourceMap": true,
5
+ "inlineSources": true,
6
+ "module": "ESNext",
7
+ "target": "ES6",
8
+ "allowJs": true,
9
+ "noImplicitAny": true,
10
+ "moduleResolution": "node",
11
+ "importHelpers": true,
12
+ "isolatedModules": true,
13
+ "strictNullChecks": true,
14
+ "lib": [
15
+ "DOM",
16
+ "ES5",
17
+ "ES6",
18
+ "ES7"
19
+ ]
20
+ },
21
+ "include": [
22
+ "**/*.ts"
23
+ ]
24
+ }
@@ -0,0 +1,17 @@
1
+ import { readFileSync, writeFileSync } from "fs";
2
+
3
+ const targetVersion = process.env.npm_package_version;
4
+
5
+ // read minAppVersion from manifest.json and bump version to target version
6
+ const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
7
+ const { minAppVersion } = manifest;
8
+ manifest.version = targetVersion;
9
+ writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
10
+
11
+ // update versions.json with target version and minAppVersion from manifest.json
12
+ // but only if the target version is not already in versions.json
13
+ const versions = JSON.parse(readFileSync('versions.json', 'utf8'));
14
+ if (!Object.values(versions).includes(minAppVersion)) {
15
+ versions[targetVersion] = minAppVersion;
16
+ writeFileSync('versions.json', JSON.stringify(versions, null, '\t'));
17
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "1.0.0": "0.15.0"
3
+ }