obsidian-plugin-config 1.7.2 → 1.7.5

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 CHANGED
@@ -28,9 +28,9 @@ obsidian-inject
28
28
  # Prompts for confirmation before replacing each existing file
29
29
  obsidian-inject ../my-plugin
30
30
 
31
- # Inject without confirmation
32
- # Auto-confirms all file replacements (no prompts)
33
- obsidian-inject ../my-plugin --no
31
+ # Inject without confirmation
32
+ # Auto-confirms all file replacements (no prompts)
33
+ obsidian-inject ../my-plugin --yes
34
34
 
35
35
  # Verification only (dry-run)
36
36
  # Shows what would be injected without making any changes
@@ -40,10 +40,10 @@ obsidian-inject ../my-plugin --dry-run
40
40
  obsidian-inject --help
41
41
  ```
42
42
 
43
- ## CLI Options
44
-
45
- - `--no`, `-n` - Skip confirmation prompts (auto-confirm)
46
- - `--dry-run` - Verification only (no changes)
43
+ ## CLI Options
44
+
45
+ - `--yes`, `-y` - Skip confirmation prompts (auto-confirm)
46
+ - `--dry-run` - Verification only (no changes)
47
47
 
48
48
  ## What is injected
49
49
 
@@ -3,13 +3,13 @@
3
3
  /**
4
4
  * Obsidian Plugin Config - CLI Entry Point
5
5
  * Global command: obsidian-inject
6
- * Version: 1.7.2
6
+ * Version: 1.7.5
7
7
  */
8
8
 
9
9
  import { execSync } from 'child_process';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { dirname, join, isAbsolute, resolve } from 'path';
12
- import fs from 'fs';
12
+ import { readFile, access, unlink, rm } from 'fs/promises';
13
13
 
14
14
  // Get the directory of this script
15
15
  const __filename = fileURLToPath(import.meta.url);
@@ -19,6 +19,15 @@ const packageRoot = dirname(__dirname);
19
19
  // Path to the injection script
20
20
  const injectScriptPath = join(packageRoot, 'scripts', 'inject-path.ts');
21
21
 
22
+ async function pathExists(p) {
23
+ try {
24
+ await access(p);
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
22
31
  function showHelp() {
23
32
  console.log(`
24
33
  Obsidian Plugin Config - Global CLI
@@ -56,7 +65,7 @@ More info: https://github.com/3C0D/obsidian-plugin-config
56
65
  `);
57
66
  }
58
67
 
59
- function main() {
68
+ async function main() {
60
69
  const args = process.argv.slice(2);
61
70
 
62
71
  // Handle help flags
@@ -66,7 +75,7 @@ function main() {
66
75
  }
67
76
 
68
77
  // Check if injection script exists
69
- if (!fs.existsSync(injectScriptPath)) {
78
+ if (!(await pathExists(injectScriptPath))) {
70
79
  console.error(`❌ Error: Injection script not found at ${injectScriptPath}`);
71
80
  console.error(` Make sure obsidian-plugin-config is properly installed.`);
72
81
  process.exit(1);
@@ -94,14 +103,14 @@ function main() {
94
103
  try {
95
104
  // Check if target directory has package.json
96
105
  const targetPackageJson = join(targetPath, 'package.json');
97
- if (!fs.existsSync(targetPackageJson)) {
106
+ if (!(await pathExists(targetPackageJson))) {
98
107
  console.error(`❌ Error: package.json not found in ${targetPath}`);
99
108
  console.error(` Make sure this is a valid Node.js project.`);
100
109
  process.exit(1);
101
110
  }
102
111
 
103
112
  // Prevent injecting into obsidian-plugin-config itself
104
- const pkg = JSON.parse(fs.readFileSync(targetPackageJson, 'utf8'));
113
+ const pkg = JSON.parse(await readFile(targetPackageJson, 'utf8'));
105
114
  if (pkg.name === 'obsidian-plugin-config') {
106
115
  console.error(`❌ Cannot inject into obsidian-plugin-config itself.`);
107
116
  process.exit(1);
@@ -109,25 +118,25 @@ function main() {
109
118
 
110
119
  // Clean NPM artifacts if package-lock.json exists
111
120
  const packageLockPath = join(targetPath, 'package-lock.json');
112
- if (fs.existsSync(packageLockPath)) {
121
+ if (await pathExists(packageLockPath)) {
113
122
  console.log(`🧹 NPM installation detected, cleaning...`);
114
123
 
115
124
  try {
116
125
  // Remove package-lock.json
117
- fs.unlinkSync(packageLockPath);
126
+ await unlink(packageLockPath);
118
127
  console.log(` 🗑️ package-lock.json removed`);
119
128
 
120
129
  // Remove node_modules if it exists
121
130
  const nodeModulesPath = join(targetPath, 'node_modules');
122
- if (fs.existsSync(nodeModulesPath)) {
123
- fs.rmSync(nodeModulesPath, { recursive: true, force: true });
131
+ if (await pathExists(nodeModulesPath)) {
132
+ await rm(nodeModulesPath, { recursive: true, force: true });
124
133
  console.log(` 🗑️ node_modules removed (will be reinstalled with Yarn)`);
125
134
  }
126
135
 
127
136
  console.log(` ✅ NPM artifacts cleaned to avoid Yarn conflicts`);
128
137
 
129
138
  } catch (cleanError) {
130
- console.error(` ❌ Cleanup failed:`, cleanError.message);
139
+ console.error(` ❌ Cleanup failed:`, cleanError instanceof Error ? cleanError.message : String(cleanError));
131
140
  console.log(` 💡 Manually remove package-lock.json and node_modules`);
132
141
  }
133
142
  }
@@ -174,5 +183,4 @@ function main() {
174
183
  }
175
184
  }
176
185
 
177
- // Run the CLI
178
- main();
186
+ main().catch(console.error);
@@ -1,76 +1,67 @@
1
- # Injection interactive
1
+ # Interactive Injection
2
2
 
3
- L'injection est **interactive par défaut** : elle compare chaque fichier du template
4
- avec le fichier existant de la cible et ne demande confirmation que lorsque le contenu
5
- diffère.
3
+ Injection is **interactive by default**: it compares each template file with the target's existing file and only prompts for confirmation when the content differs.
6
4
 
7
- ## Points d'entrée
5
+ ## Entry Points
8
6
 
9
7
  ```bash
10
- # CLI globale
11
- obsidian-inject # Injection dans le dossier courant
12
- obsidian-inject ../my-plugin # Injection par chemin
13
-
14
- # Scripts locaux (développement de ce repo)
15
- yarn inject-prompt # Demande le chemin du plugin cible, puis injecte
16
- yarn inject-path ../my-plugin # Injection directe par chemin
17
- yarn check-plugin ../my-plugin # Dry-run (vérification seule, aucune modification)
8
+ # Global CLI
9
+ obsidian-inject # Inject in current directory
10
+ obsidian-inject ../my-plugin # Inject by path
11
+
12
+ # Local scripts (development of this repo)
13
+ yarn inject-prompt # Prompts for target plugin path, then injects
14
+ yarn inject-path ../my-plugin # Direct injection by path
15
+ yarn check-plugin ../my-plugin # Dry-run (verification only, no modifications)
18
16
  ```
19
17
 
20
- ## Comportement fichier par fichier
18
+ ## File-by-File Behavior
21
19
 
22
- Pendant l'injection, chaque fichier est traité ainsi :
20
+ During injection, each file is handled as follows:
23
21
 
24
- - **La cible n'existe pas encore** → le fichier est injecté sans demander.
25
- - **Contenu identique** → ignoré silencieusement (`✅ ... (unchanged)`).
26
- - **Contenu différent** → l'outil demande `Update <fichier>? (content differs)`.
27
- - `y` → le fichier est remplacé.
28
- - `n` → le fichier existant est conservé (`⏭️ Kept existing ...`).
22
+ - **Target does not exist yet** → the file is injected without asking.
23
+ - **Identical content** → silently ignored (`✅ ... (unchanged)`).
24
+ - **Different content** → the tool asks `Update <file>? (content differs)`.
25
+ - `y` → the file is replaced.
26
+ - `n` → the existing file is kept (`⏭️ Kept existing ...`).
29
27
 
30
- Cas particuliers :
28
+ Special cases:
31
29
 
32
- - `.env` est toujours **fusionné** : le template est réécrit en préservant les
33
- valeurs déjà renseignées (chemins de vault, etc.).
34
- - `.npmrc` est toujours injecté (protection Yarn).
35
- - `eslint.config.mts` est approuvé automatiquement si un ancien `.eslintrc*`
36
- est détecté (migration depuis l'ancien format).
30
+ - `.env` is always **merged**: the template is rewritten while preserving existing values (vault paths, etc.).
31
+ - `.npmrc` is always injected (Yarn protection).
32
+ - `eslint.config.mts` is automatically approved if an old `.eslintrc*` is detected (migration from the old format).
37
33
 
38
34
  ## Options
39
35
 
40
36
  ```bash
41
- # Auto-confirmer tous les remplacements (aucune question)
42
- obsidian-inject ../my-plugin --no # CLI globale : --no / -n
43
- yarn inject-path ../my-plugin --yes # Scripts locaux : --yes / -y
37
+ # Auto-confirm all replacements (no questions)
38
+ obsidian-inject ../my-plugin --yes # Global CLI: --yes / -y
39
+ yarn inject-path ../my-plugin --yes # Local scripts: --yes / -y
44
40
 
45
- # Vérification seule (n'écrit rien)
41
+ # Verification only (writes nothing)
46
42
  obsidian-inject ../my-plugin --dry-run
47
43
  ```
48
44
 
49
- | Option | Effet |
50
- | ---------------- | -------------------------------------------------- |
51
- | `--no`, `-n` | (CLI globale) auto-confirme tous les remplacements |
52
- | `--yes`, `-y` | (scripts locaux) auto-confirme tous les remplacements |
53
- | `--dry-run` | vérification seule, aucune modification |
45
+ | Option | Effect |
46
+ | ------------- | ---------------------------------------------- |
47
+ | `--yes`, `-y` | (Global CLI) auto-confirms all replacements |
48
+ | `--yes`, `-y` | (Local scripts) auto-confirms all replacements |
49
+ | `--dry-run` | Verification only, no modifications |
54
50
 
55
- ## Ce qui est injecté
51
+ ## What is Injected
56
52
 
57
- Tous les fichiers du template sont pris en compte à chaque injection (pas de
58
- sélection par composant) :
53
+ All template files are considered during each injection (no component selection):
59
54
 
60
- - `templates/scripts/*` → `<cible>/scripts/`
61
- - `templates/tsconfig.json`, `eslint.config.mts`, `.editorconfig`,
62
- `.prettierrc`, `.prettierignore`, `.npmrc`, `.env`
55
+ - `templates/scripts/*` → `<target>/scripts/`
56
+ - `templates/tsconfig.json.template`, `eslint.config.mts`, `.editorconfig`, `.prettierrc`, `.prettierignore`, `.npmrc`, `.env`
63
57
  - `templates/.vscode/*`
64
58
  - `templates/.github/workflows/*`
65
- - `templates/gitignore.template` → `<cible>/.gitignore`
59
+ - `templates/gitignore.template` → `<target>/.gitignore`
66
60
 
67
- La confirmation fichier par fichier permet de conserver un fichier existant
68
- (par exemple un `esbuild.config.ts` personnalisé) en répondant `n` lorsque la
69
- question apparaît.
61
+ File-by-file confirmation allows keeping an existing file (for example, a custom `esbuild.config.ts`) by answering `n` when the prompt appears.
70
62
 
71
- ## Fichiers concernés
63
+ ## Related Files
72
64
 
73
- 1. **scripts/inject-core.ts** — logique d'injection (`diffAndPromptFiles`,
74
- `injectScripts`, `updatePackageJson`, `performInjection`).
75
- 2. **scripts/inject-prompt.ts** — entrée interactive (demande le chemin).
76
- 3. **scripts/inject-path.ts** — entrée CLI (parse `--yes`, `--dry-run`).
65
+ 1. **scripts/inject-core.ts** — injection logic (`diffAndPromptFiles`, `injectScripts`, `updatePackageJson`, `performInjection`).
66
+ 2. **scripts/inject-prompt.ts** — interactive entry (prompts for path).
67
+ 3. **scripts/inject-path.ts** — CLI entry (parses `--yes`, `--dry-run`).
package/docs/LLM-GUIDE.md CHANGED
@@ -13,8 +13,8 @@
13
13
  `templates/` contains everything that gets injected into target plugins:
14
14
 
15
15
  - `templates/scripts/` — scripts copied into `<target>/scripts/`
16
- - `templates/package.json` — base deps/scripts merged into `<target>/package.json`
17
- - `templates/tsconfig.json` — TypeScript config injected as `<target>/tsconfig.json`
16
+ - `templates/package.json.template` — base deps/scripts merged into `<target>/package.json`
17
+ - `templates/tsconfig.json.template` — TypeScript config injected as `<target>/tsconfig.json`
18
18
  - `templates/eslint.config.mts` — ESLint config injected into target
19
19
  - `templates/.editorconfig`, `templates/.prettierrc`, etc. — config files injected into target
20
20
  - `templates/.github/workflows/` — GitHub Actions workflows
@@ -37,7 +37,7 @@
37
37
 
38
38
  ### 1. Package.json merge
39
39
 
40
- `inject-core.ts → updatePackageJson()` reads `templates/package.json` and merges into the target plugin's `package.json`:
40
+ `inject-core.ts → updatePackageJson()` reads `templates/package.json.template` and merges into the target plugin's `package.json`:
41
41
 
42
42
  - All `scripts` are overwritten with template values
43
43
  - All `devDependencies` from template are added/updated
@@ -51,7 +51,8 @@
51
51
  - `templates/scripts/*` → `<target>/scripts/`
52
52
  - `templates/tsconfig.json` → `<target>/tsconfig.json`
53
53
  - `templates/eslint.config.mts` → `<target>/eslint.config.mts`
54
- - `templates/.editorconfig`, `.prettierrc`, `.npmrc`, `.env` → `<target>/`
54
+ - `templates/.editorconfig`, `.prettierrc`, `.npmrc`, `.env`, `.gitattributes` → `<target>/`
55
+ - `templates/.vscode/*` → `<target>/.vscode/`
55
56
  - `templates/.github/workflows/*` → `<target>/.github/workflows/`
56
57
  - `templates/gitignore.template` → `<target>/.gitignore`
57
58
 
@@ -95,9 +96,9 @@ The `esbuild-sass-plugin` dependency is **not** injected automatically. If a plu
95
96
 
96
97
  ## What NOT to do
97
98
 
98
- - ❌ Do not hardcode deps/scripts in `inject-core.ts` — they must come from `templates/package.json`
99
+ - ❌ Do not hardcode deps/scripts in `inject-core.ts` — they must come from `templates/package.json.template`
99
100
  - ❌ Do not modify root config files thinking it will affect injected plugins — always modify `templates/`
100
- - ❌ Do not add `obsidian-plugin-config` as a dependency in `templates/package.json` — injected plugins are standalone
101
+ - ❌ Do not add `obsidian-plugin-config` as a dependency in `templates/package.json.template` — injected plugins are standalone
101
102
  - ❌ Do not inject `esbuild.config.ts` without its dependencies (`constants.ts`, `env.ts`, `reload.ts`, `typingsPlugin.ts`, `utils.ts`) — they are all required
102
103
 
103
104
  ---
@@ -115,7 +116,7 @@ To change what gets injected:
115
116
 
116
117
  ## obsidian-typings paths
117
118
 
118
- The correct paths for `obsidian-typings` in `templates/tsconfig.json`:
119
+ The correct paths for `obsidian-typings` in `templates/tsconfig.json.template`:
119
120
 
120
121
  ```json
121
122
  "types": ["obsidian-typings"],
@@ -0,0 +1,277 @@
1
+ # How SCSS Works in this Config
2
+
3
+ This document explains end-to-end how a `.scss` file is detected, compiled, and then copied into the Obsidian `plugins` folder, based on the repository's code.
4
+
5
+ > Note: SCSS configuration is not enabled in `obsidian-plugin-config` itself. It is defined in the **templates** that are copied into each Obsidian plugin during injection (`yarn inject` / `obsidian-inject`). All the code cited below resides in `templates/scripts/`.
6
+
7
+ ---
8
+
9
+ ## 1. Flow Overview
10
+
11
+ ```
12
+ src/styles.scss
13
+
14
+ │ detection (esbuild.config.ts → main())
15
+
16
+ entryPoints = [src/main.ts, src/styles.scss]
17
+
18
+ │ esbuild.context() + sassPlugin() (esbuild.config.ts → createBuildContext)
19
+ │ outbase: src/ → styles.scss → styles.css (name derived from entry, not main.ts)
20
+
21
+ buildPath/
22
+ ├── main.js
23
+ └── styles.css ← produced directly by esbuild+sassPlugin
24
+
25
+ │ plugin "rename-main-css" → utils.renameMainCss (no-op safety net: main.css does not exist)
26
+ │ plugin "copy-to-plugins-folder" → utils.copyFilesToTargetDir (manifest only if SCSS)
27
+
28
+ buildPath/
29
+ ├── manifest.json (copied)
30
+ ├── main.js
31
+ └── styles.css ← already in place, no renaming necessary
32
+ ```
33
+
34
+ The two key moments are:
35
+
36
+ 1. **Detection and compilation** in `templates/scripts/esbuild.config.ts`.
37
+ 2. **Cleanup and copying to the plugins folder** via utility functions in `templates/scripts/utils.ts`.
38
+
39
+ ---
40
+
41
+ ## 2. SCSS File Path
42
+
43
+ ### 2.1 SCSS File Detection
44
+
45
+ In `templates/scripts/esbuild.config.ts`, `main()` function:
46
+
47
+ ```ts
48
+ // Check for SCSS first, then CSS in src, then in root
49
+ const srcStylesScssPath = path.join(pluginDir, 'src/styles.scss');
50
+ const srcStylesPath = path.join(pluginDir, 'src/styles.css');
51
+ const rootStylesPath = path.join(pluginDir, 'styles.css');
52
+
53
+ const scssExists = await isValidPath(srcStylesScssPath);
54
+ const stylePath = scssExists
55
+ ? srcStylesScssPath
56
+ : (await isValidPath(srcStylesPath))
57
+ ? srcStylesPath
58
+ : (await isValidPath(rootStylesPath))
59
+ ? rootStylesPath
60
+ : '';
61
+
62
+ const mainTsPath = path.join(pluginDir, 'src/main.ts');
63
+ const entryPoints = stylePath ? [mainTsPath, stylePath] : [mainTsPath];
64
+ const context = await createBuildContext(buildPath, isProd, entryPoints, scssExists);
65
+ ```
66
+
67
+ - The SCSS file is expected at `src/styles.scss` (highest priority).
68
+ - Otherwise, it falls back to `src/styles.css` then `styles.css` at the root.
69
+ - `scssExists` (boolean) is then used to enable or disable the SASS plugin in esbuild.
70
+
71
+ ### 2.2 Compilation via esbuild + sassPlugin
72
+
73
+ Also in `templates/scripts/esbuild.config.ts`, `createBuildContext()` function:
74
+
75
+ ```ts
76
+ const plugins = [
77
+ ...(hasSass
78
+ ? [
79
+ await (async () => {
80
+ // @ts-expect-error - esbuild-sass-plugin is installed during injection
81
+ const { sassPlugin } = await import('esbuild-sass-plugin');
82
+ return sassPlugin({ syntax: 'scss', style: 'expanded' });
83
+ })(),
84
+ {
85
+ name: 'rename-main-css',
86
+ setup(build: esbuild.PluginBuild): void {
87
+ build.onEnd(async (result) => {
88
+ if (result.errors.length === 0) {
89
+ await renameMainCss(buildPath);
90
+ }
91
+ });
92
+ }
93
+ }
94
+ ]
95
+ : []),
96
+ ```
97
+
98
+ - The `sassPlugin` is only imported if `hasSass` is `true`.
99
+ - It is configured with `scss` syntax and `expanded` style (not minified on the Sass side; esbuild applies its own minification if `isProd = true`).
100
+ - **Output filename**: esbuild derives the name from the entry point. With `outbase: src/` and entry `src/styles.scss`, the relative path is `styles.scss` → output `buildPath/styles.css`. The name `main.ts` does not interfere.
101
+ - The `rename-main-css` plugin is a **safety net**: in the current configuration, `buildPath/main.css` is never created, so `renameMainCss` is a no-op. It protects against future changes in sassPlugin behavior.
102
+
103
+ ### 2.3 Dependency: `esbuild-sass-plugin`
104
+
105
+ Dynamic import: installation is not mandatory for plugins without SCSS. If SCSS is detected but the plugin is missing:
106
+
107
+ > `⚠️ esbuild-sass-plugin not found. Install it with: yarn add -D esbuild-sass-plugin`
108
+
109
+ > **Note**: the `.sass` extension (indented syntax) is not supported. Only `.scss` is detected and compiled.
110
+
111
+ ---
112
+
113
+ ## 3. How it is Copied to the Plugins Folder
114
+
115
+ ### 3.1 `renameMainCss` — Safety Net (no-op in current SCSS flow)
116
+
117
+ In `templates/scripts/utils.ts`, `renameMainCss()` function:
118
+
119
+ ```ts
120
+ export async function renameMainCss(outdir: string): Promise<void> {
121
+ const mainCssPath = path.join(outdir, 'main.css');
122
+ const stylesCssPath = path.join(outdir, 'styles.css');
123
+ try {
124
+ if (await isValidPath(mainCssPath)) {
125
+ await rename(mainCssPath, stylesCssPath);
126
+ }
127
+ } catch (error: unknown) {
128
+ console.warn(`Warning: Could not rename main.css to styles.css: ...`);
129
+ }
130
+ }
131
+ ```
132
+
133
+ With `outbase: src/` and entry `src/styles.scss`, esbuild directly produces `buildPath/styles.css`. `buildPath/main.css` is therefore never created, and the `if (await isValidPath(mainCssPath))` check is always false. This function remains as a safeguard if sassPlugin behavior evolves.
134
+
135
+ > **Context of `_.._`**: for the root CSS case (`styles.css` outside `outbase: src/`), esbuild encodes `../styles.css` as `_.._/styles.css`. This is not related to SCSS. `copyFilesToTargetDir` handles this case separately (manual copy + removal of `_.._/`).
136
+
137
+ ### 3.2 Copying to the Plugins Folder
138
+
139
+ In `templates/scripts/utils.ts`, `copyFilesToTargetDir()` function:
140
+
141
+ ```ts
142
+ export async function copyFilesToTargetDir(buildPath: string): Promise<void> {
143
+ const pluginDir = process.cwd();
144
+ const manifestSrc = path.join(pluginDir, 'manifest.json');
145
+ const manifestDest = path.join(buildPath, 'manifest.json');
146
+ const cssDest = path.join(buildPath, 'styles.css');
147
+ const folderToRemove = path.join(buildPath, '_.._');
148
+ ...
149
+ // Copy CSS
150
+ try {
151
+ const srcStylesPath = path.join(pluginDir, 'src/styles.css');
152
+ const rootStylesPath = path.join(pluginDir, 'styles.css');
153
+
154
+ if (await isValidPath(srcStylesPath)) {
155
+ await copyFile(srcStylesPath, cssDest);
156
+ }
157
+ else if (await isValidPath(rootStylesPath)) {
158
+ await copyFile(rootStylesPath, cssDest);
159
+ if (await isValidPath(folderToRemove)) {
160
+ await rm(folderToRemove, { recursive: true });
161
+ }
162
+ } else {
163
+ return;
164
+ }
165
+ } ...
166
+ }
167
+ ```
168
+
169
+ > Important Note: `copyFilesToTargetDir` only copies `manifest.json` and any potential `styles.css` at the root. The `main.css` generated by esbuild-sass-plugin is renamed to `styles.css` in the previous step (3.1).
170
+
171
+ ### 3.3 Integration into the esbuild Lifecycle
172
+
173
+ Also in `templates/scripts/esbuild.config.ts`, the `copy-to-plugins-folder` plugin triggers copying after each build:
174
+
175
+ ```ts
176
+ {
177
+ name: 'copy-to-plugins-folder',
178
+ setup: (build: esbuild.PluginBuild): void => {
179
+ build.onEnd(async () => {
180
+ if (isProd) {
181
+ if (process.argv.includes('-r') || process.argv.includes('real')) {
182
+ await copyFilesToTargetDir(buildPath);
183
+ console.log(`Successfully installed in ${buildPath}`);
184
+ await reloadObsidian();
185
+ } else {
186
+ const folderToRemove = path.join(buildPath, '_.._');
187
+ if (await isValidPath(folderToRemove)) {
188
+ await rm(folderToRemove, { recursive: true });
189
+ }
190
+ console.log('Build done in initial folder');
191
+ }
192
+ } else {
193
+ // watch (dev)
194
+ await copyFilesToTargetDir(buildPath);
195
+ }
196
+ });
197
+ }
198
+ }
199
+ ```
200
+
201
+ - **Dev mode (`yarn dev`)**: after each rebuild (watch), `copyFilesToTargetDir` recopies the manifest + CSS to the test vault.
202
+ - **Prod build mode (`yarn build`)**: nothing is copied by default, unless `-r` / `real` is passed (=> copy to production vault + reload Obsidian).
203
+
204
+ ### 3.4 Target: `buildPath`
205
+
206
+ `buildPath` is calculated by `env.ts` (`getBuildPath`) from `.env` (`TEST_VAULT` / `REAL_VAULT`) or the current directory (in-place development). It points to:
207
+
208
+ ```
209
+ <vault>/.obsidian/plugins/<pluginId>/
210
+ ```
211
+
212
+ So concretely, the final result for SCSS is:
213
+
214
+ 1. `src/styles.scss` → compiled by esbuild-sass-plugin → `buildPath/main.css` (transient)
215
+ 2. `main.css` renamed to `styles.css` by `renameMainCss`
216
+ 3. The CSS is directly available and referenced via `manifest.json` (`"css": "styles.css"`).
217
+
218
+ > 💡 The final `styles.css` is automatically produced by this pipeline by renaming the `main.css` file from the SCSS compilation. No root `styles.css` source file is necessary if you use SCSS exclusively.
219
+
220
+ ---
221
+
222
+ ## 4. Where the Injector Installs Everything in the Target Plugin
223
+
224
+ Template injection happens in `scripts/inject-core.ts`, `injectScripts()` function. The `scriptFiles` array explicitly lists files copied into the target plugin's `scripts/` folder:
225
+
226
+ ```ts
227
+ const scriptFiles = [
228
+ 'templates/scripts/utils.ts',
229
+ 'templates/scripts/esbuild.config.ts',
230
+ 'templates/scripts/acp.ts',
231
+ 'templates/scripts/update-version.ts',
232
+ 'templates/scripts/release.ts',
233
+ 'templates/scripts/help.ts',
234
+ 'templates/scripts/constants.ts',
235
+ 'templates/scripts/env.ts',
236
+ 'templates/scripts/reload.ts',
237
+ 'templates/scripts/typingsPlugin.ts'
238
+ ];
239
+ ```
240
+
241
+ So after injection, the target Obsidian plugin contains `scripts/esbuild.config.ts` (with SCSS logic) and `scripts/utils.ts` (with `renameMainCss` and `copyFilesToTargetDir`).
242
+
243
+ The injected `.gitignore` (`templates/gitignore.template`) explicitly ignores the intermediate file:
244
+
245
+ ```
246
+ # scss result
247
+ main.css
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 5. Key Files Summary
253
+
254
+ | Role | File | Functions / Lines |
255
+ | -------------------------------------------------------------------- | --------------------------------------------- | ------------------------------------ |
256
+ | SCSS detection + esbuild build | `templates/scripts/esbuild.config.ts` | `main()`, `createBuildContext()` |
257
+ | esbuild plugin to compile SCSS | `esbuild-sass-plugin` (dynamically imported) | `esbuild.config.ts` |
258
+ | Safety net (no-op SCSS): rename `main.css` → `styles.css` if present | `templates/scripts/utils.ts` | `renameMainCss()` |
259
+ | Final copy to plugins folder | `templates/scripts/utils.ts` | `copyFilesToTargetDir()` |
260
+ | Hook into esbuild `onEnd` | `templates/scripts/esbuild.config.ts` | `copy-to-plugins-folder` plugin |
261
+ | Copy templates to target plugin | `scripts/inject-core.ts` | `injectScripts()`, `buildFileList()` |
262
+ | Optional dependency | `esbuild-sass-plugin` (added by user if SCSS) | `README.md` § SASS Support |
263
+ | Ignore transient file | `templates/gitignore.template` | `main.css` |
264
+
265
+ ---
266
+
267
+ ## 6. Scenario Coverage
268
+
269
+ | Scenario | `buildPath` | CSS produced by esbuild | In place after build? |
270
+ | ------------------------------------ | --------------------------------- | ----------------------- | --------------------- |
271
+ | Watch, in-place (pluginDir in vault) | `pluginDir` | `pluginDir/styles.css` | ✅ direct |
272
+ | Watch, external → TEST_VAULT | `<vault>/.obsidian/plugins/<id>/` | `buildPath/styles.css` | ✅ direct |
273
+ | Prod + `-r`, external → REAL_VAULT | `<vault>/.obsidian/plugins/<id>/` | `buildPath/styles.css` | ✅ direct |
274
+ | Prod, initial folder (`yarn build`) | `pluginDir` | `pluginDir/styles.css` | ✅ direct |
275
+ | Release (GH Actions, `yarn build`) | `pluginDir` (no vault) | `pluginDir/styles.css` | ✅ direct |
276
+
277
+ In all SCSS scenarios, `copyFilesToTargetDir` does not find a source CSS file (`src/styles.css` absent, no root `styles.css`) and returns early — the CSS is already in `buildPath` directly produced by esbuild. Only `manifest.json` is copied by this function in the SCSS case.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obsidian-plugin-config",
3
- "version": "1.7.2",
3
+ "version": "1.7.5",
4
4
  "description": "Global CLI injection tool for Obsidian plugins",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,11 +34,17 @@
34
34
  "@types/node": "^22.15.26",
35
35
  "@typescript-eslint/eslint-plugin": "^8.58.0",
36
36
  "@typescript-eslint/parser": "^8.58.0",
37
+ "builtin-modules": "latest",
37
38
  "dedent": "^1.6.0",
39
+ "dotenv": "latest",
40
+ "esbuild": "latest",
38
41
  "eslint": "latest",
39
42
  "eslint-import-resolver-typescript": "latest",
40
43
  "jiti": "latest",
44
+ "obsidian": "*",
45
+ "obsidian-typings": "latest",
41
46
  "prettier": "^3.4.0",
47
+ "tslib": "2.4.0",
42
48
  "tsx": "^4.21.0",
43
49
  "typescript": "^5.8.2"
44
50
  },
package/scripts/acp.ts CHANGED
@@ -1,22 +1,23 @@
1
- import { execSync } from 'child_process';
2
- import fs from 'fs';
1
+ import { readFile } from 'fs/promises';
3
2
  import path from 'path';
4
3
  import {
5
4
  askQuestion,
6
5
  cleanInput,
7
6
  createReadlineInterface,
8
7
  gitExec,
9
- ensureGitSync
8
+ gitOutput,
9
+ ensureGitSync,
10
+ isValidPath
10
11
  } from './utils.js';
11
12
 
12
13
  const rl = createReadlineInterface();
13
14
 
14
15
  /** Check if we're in the centralized config repo */
15
- function isInCentralizedRepo(): boolean {
16
+ async function isInCentralizedRepo(): Promise<boolean> {
16
17
  const packageJsonPath = path.join(process.cwd(), 'package.json');
17
- if (!fs.existsSync(packageJsonPath)) return false;
18
+ if (!(await isValidPath(packageJsonPath))) return false;
18
19
 
19
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
20
+ const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'));
20
21
  return packageJson.name === 'obsidian-plugin-config';
21
22
  }
22
23
 
@@ -25,7 +26,7 @@ async function main(): Promise<void> {
25
26
  if (process.argv.includes('-b')) {
26
27
  console.log('Building...');
27
28
  // For obsidian-plugin-config, just run TypeScript check
28
- if (isInCentralizedRepo()) {
29
+ if (await isInCentralizedRepo()) {
29
30
  gitExec('npx tsc --noEmit');
30
31
  } else {
31
32
  gitExec('yarn build');
@@ -46,7 +47,7 @@ async function main(): Promise<void> {
46
47
  }
47
48
 
48
49
  // get current branch name
49
- const currentBranch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
50
+ const currentBranch = gitOutput('git rev-parse --abbrev-ref HEAD');
50
51
 
51
52
  // Ensure Git is synchronized before pushing
52
53
  await ensureGitSync();