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 +7 -7
- package/bin/obsidian-inject.js +21 -13
- package/docs/INTERACTIVE_INJECTION.md +41 -50
- package/docs/LLM-GUIDE.md +8 -7
- package/docs/SCSS-FLOW.md +277 -0
- package/package.json +7 -1
- package/scripts/acp.ts +9 -8
- package/scripts/build-npm.ts +397 -393
- package/scripts/help.ts +87 -87
- package/scripts/inject-core.ts +870 -897
- package/scripts/inject-path.ts +157 -156
- package/scripts/inject-prompt.ts +104 -104
- package/scripts/utils.ts +173 -151
- package/templates/.github/workflows/release.yml +2 -2
- package/templates/.vscode/tasks.json +1 -1
- package/templates/{package.json → package.json.template} +1 -1
- package/templates/scripts/acp.ts +2 -2
- package/templates/scripts/env.ts +4 -5
- package/templates/scripts/esbuild.config.ts +4 -5
- package/templates/scripts/release.ts +14 -17
- package/templates/scripts/utils.ts +42 -25
- package/tsconfig.json +2 -2
- /package/templates/{tsconfig.json → tsconfig.json.template} +0 -0
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 --
|
|
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
|
-
- `--
|
|
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
|
|
package/bin/obsidian-inject.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Obsidian Plugin Config - CLI Entry Point
|
|
5
5
|
* Global command: obsidian-inject
|
|
6
|
-
* Version: 1.7.
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
123
|
-
|
|
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
|
-
|
|
178
|
-
main();
|
|
186
|
+
main().catch(console.error);
|
|
@@ -1,76 +1,67 @@
|
|
|
1
|
-
# Injection
|
|
1
|
+
# Interactive Injection
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
5
|
+
## Entry Points
|
|
8
6
|
|
|
9
7
|
```bash
|
|
10
|
-
# CLI
|
|
11
|
-
obsidian-inject #
|
|
12
|
-
obsidian-inject ../my-plugin #
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
yarn inject-prompt #
|
|
16
|
-
yarn inject-path ../my-plugin #
|
|
17
|
-
yarn check-plugin ../my-plugin # Dry-run (
|
|
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
|
-
##
|
|
18
|
+
## File-by-File Behavior
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
During injection, each file is handled as follows:
|
|
23
21
|
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- `y` →
|
|
28
|
-
- `n` →
|
|
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
|
-
|
|
28
|
+
Special cases:
|
|
31
29
|
|
|
32
|
-
- `.env`
|
|
33
|
-
|
|
34
|
-
-
|
|
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-
|
|
42
|
-
obsidian-inject ../my-plugin --
|
|
43
|
-
yarn inject-path ../my-plugin --yes
|
|
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
|
-
#
|
|
41
|
+
# Verification only (writes nothing)
|
|
46
42
|
obsidian-inject ../my-plugin --dry-run
|
|
47
43
|
```
|
|
48
44
|
|
|
49
|
-
| Option
|
|
50
|
-
|
|
|
51
|
-
| `--
|
|
52
|
-
| `--yes`, `-y`
|
|
53
|
-
| `--dry-run`
|
|
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
|
-
##
|
|
51
|
+
## What is Injected
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
sélection par composant) :
|
|
53
|
+
All template files are considered during each injection (no component selection):
|
|
59
54
|
|
|
60
|
-
- `templates/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` → `<
|
|
59
|
+
- `templates/gitignore.template` → `<target>/.gitignore`
|
|
66
60
|
|
|
67
|
-
|
|
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
|
-
##
|
|
63
|
+
## Related Files
|
|
72
64
|
|
|
73
|
-
1. **scripts/inject-core.ts** —
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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 (!
|
|
18
|
+
if (!(await isValidPath(packageJsonPath))) return false;
|
|
18
19
|
|
|
19
|
-
const packageJson = JSON.parse(
|
|
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 =
|
|
50
|
+
const currentBranch = gitOutput('git rev-parse --abbrev-ref HEAD');
|
|
50
51
|
|
|
51
52
|
// Ensure Git is synchronized before pushing
|
|
52
53
|
await ensureGitSync();
|