obsidian-plugin-config 1.6.18 → 1.7.1
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/.editorconfig +9 -14
- package/.prettierrc +3 -12
- package/README.md +8 -12
- package/bin/obsidian-inject.js +2 -10
- package/docs/INTERACTIVE_INJECTION.md +55 -116
- package/docs/LLM-GUIDE.md +17 -8
- package/eslint.config.mts +63 -64
- package/package.json +3 -4
- package/scripts/acp.ts +53 -57
- package/scripts/build-npm.ts +180 -167
- package/scripts/help.ts +3 -6
- package/scripts/inject-core.ts +765 -829
- package/scripts/inject-path.ts +144 -160
- package/scripts/inject-prompt.ts +86 -91
- package/scripts/update-version-config.ts +121 -89
- package/scripts/utils.ts +112 -112
- package/templates/.editorconfig +11 -14
- package/templates/.prettierrc +3 -12
- package/templates/env.template +12 -9
- package/templates/eslint.config.mts +1 -1
- package/templates/package.json +2 -3
- package/templates/scripts/acp.ts +50 -74
- package/templates/scripts/constants.ts +26 -0
- package/templates/scripts/env.ts +94 -0
- package/templates/scripts/esbuild.config.ts +134 -287
- package/templates/scripts/release.ts +92 -87
- package/templates/scripts/reload.ts +46 -0
- package/templates/scripts/typingsPlugin.ts +26 -0
- package/templates/scripts/update-version.ts +120 -123
- package/templates/scripts/utils.ts +234 -134
- package/templates/tsconfig.json +2 -2
- package/tsconfig.json +1 -1
- package/templates/package-sass.json +0 -5
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import { access, mkdir, copyFile, rm } from 'fs/promises';
|
|
1
|
+
import { access, mkdir, copyFile, rm, writeFile } from 'fs/promises';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import * as readline from 'readline';
|
|
4
5
|
import { execSync } from 'child_process';
|
|
5
6
|
|
|
6
7
|
export function createReadlineInterface(): readline.Interface {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
return readline.createInterface({
|
|
9
|
+
input: process.stdin as NodeJS.ReadableStream,
|
|
10
|
+
output: process.stdout as NodeJS.WritableStream
|
|
11
|
+
});
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const askQuestion = async (
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
question: string,
|
|
16
|
+
rl: readline.Interface
|
|
16
17
|
): Promise<string> => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
try {
|
|
19
|
+
return await new Promise((resolve) =>
|
|
20
|
+
rl.question(question, (input) => resolve(input.trim()))
|
|
21
|
+
);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('Error asking question:', error);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -31,134 +32,134 @@ export const askQuestion = async (
|
|
|
31
32
|
* Invalid input defaults to no for safety
|
|
32
33
|
*/
|
|
33
34
|
export const askConfirmation = async (
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
question: string,
|
|
36
|
+
rl: readline.Interface
|
|
36
37
|
): Promise<boolean> => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
const answer = await askQuestion(`${question} [Y/n]: `, rl);
|
|
39
|
+
const response = answer.toLowerCase();
|
|
40
|
+
|
|
41
|
+
// Accept: y, yes, Y, YES, or empty (default to yes)
|
|
42
|
+
// Reject: n, no, N, NO
|
|
43
|
+
const isYes = response === '' || response === 'y' || response === 'yes';
|
|
44
|
+
const isNo = response === 'n' || response === 'no';
|
|
45
|
+
|
|
46
|
+
if (isNo) {
|
|
47
|
+
return false;
|
|
48
|
+
} else if (isYes) {
|
|
49
|
+
return true;
|
|
50
|
+
} else {
|
|
51
|
+
console.log('Please answer Y (yes) or n (no). Defaulting to no for safety.');
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
export const cleanInput = (inputStr: string): string => {
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if (!inputStr) return '';
|
|
58
|
+
return inputStr.trim().replace(/["`]/g, "'").replace(/\r\n/g, '\n');
|
|
58
59
|
};
|
|
59
60
|
|
|
60
61
|
export const isValidPath = async (pathToCheck: string): Promise<boolean> => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
if (!pathToCheck) return false;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Using async fs.access is preferred over synchronous existsSync
|
|
66
|
+
// as it doesn't block the main thread/event loop
|
|
67
|
+
await access(pathToCheck.trim());
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
export async function copyFilesToTargetDir(buildPath: string): Promise<void> {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
75
|
+
const pluginDir = process.cwd();
|
|
76
|
+
const manifestSrc = path.join(pluginDir, 'manifest.json');
|
|
77
|
+
const manifestDest = path.join(buildPath, 'manifest.json');
|
|
78
|
+
const cssDest = path.join(buildPath, 'styles.css');
|
|
79
|
+
const folderToRemove = path.join(buildPath, '_.._');
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
await mkdir(buildPath, { recursive: true });
|
|
83
|
+
} catch (error: unknown) {
|
|
84
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
|
|
85
|
+
console.error(
|
|
86
|
+
`Error creating directory: ${error instanceof Error ? error.message : String(error)}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Copy manifest
|
|
92
|
+
try {
|
|
93
|
+
await copyFile(manifestSrc, manifestDest);
|
|
94
|
+
} catch (error: unknown) {
|
|
95
|
+
console.error(
|
|
96
|
+
`Error copying manifest: ${error instanceof Error ? error.message : String(error)}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Copy CSS
|
|
101
|
+
try {
|
|
102
|
+
const srcStylesPath = path.join(pluginDir, 'src/styles.css');
|
|
103
|
+
const rootStylesPath = path.join(pluginDir, 'styles.css');
|
|
104
|
+
|
|
105
|
+
// First check if CSS exists in src/styles.css
|
|
106
|
+
if (await isValidPath(srcStylesPath)) {
|
|
107
|
+
await copyFile(srcStylesPath, cssDest);
|
|
108
|
+
}
|
|
109
|
+
// Otherwise, check if it exists in the root
|
|
110
|
+
else if (await isValidPath(rootStylesPath)) {
|
|
111
|
+
await copyFile(rootStylesPath, cssDest);
|
|
112
|
+
if (await isValidPath(folderToRemove)) {
|
|
113
|
+
await rm(folderToRemove, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
} catch (error: unknown) {
|
|
119
|
+
console.error(
|
|
120
|
+
`Error copying CSS: ${error instanceof Error ? error.message : String(error)}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
export function gitExec(command: string): void {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
try {
|
|
127
|
+
execSync(command, { stdio: 'inherit' });
|
|
128
|
+
} catch (error: unknown) {
|
|
129
|
+
console.error(
|
|
130
|
+
`Error executing '${command}':`,
|
|
131
|
+
error instanceof Error ? error.message : String(error)
|
|
132
|
+
);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
/**
|
|
137
138
|
* Ensure Git repository is synchronized with remote before pushing
|
|
138
139
|
*/
|
|
139
140
|
export async function ensureGitSync(): Promise<void> {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
141
|
+
try {
|
|
142
|
+
console.log('🔄 Checking Git synchronization...');
|
|
143
|
+
|
|
144
|
+
// Fetch latest changes from remote
|
|
145
|
+
execSync('git fetch origin', { stdio: 'pipe' });
|
|
146
|
+
|
|
147
|
+
// Check if branch is behind remote
|
|
148
|
+
const status = execSync('git status --porcelain -b', { encoding: 'utf8' });
|
|
149
|
+
|
|
150
|
+
if (status.includes('behind')) {
|
|
151
|
+
console.log('📥 Branch behind remote. Pulling changes...');
|
|
152
|
+
execSync('git pull', { stdio: 'inherit' });
|
|
153
|
+
console.log('✅ Successfully pulled remote changes');
|
|
154
|
+
} else {
|
|
155
|
+
console.log('✅ Repository is synchronized with remote');
|
|
156
|
+
}
|
|
157
|
+
} catch (error: unknown) {
|
|
158
|
+
console.error(
|
|
159
|
+
`❌ Git sync failed: ${error instanceof Error ? error.message : String(error)}`
|
|
160
|
+
);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
/**
|
|
@@ -166,14 +167,113 @@ export async function ensureGitSync(): Promise<void> {
|
|
|
166
167
|
* This prevents the unwanted main.css from being included in the plugin
|
|
167
168
|
*/
|
|
168
169
|
export async function removeMainCss(outdir: string): Promise<void> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
const mainCssPath = path.join(outdir, 'main.css');
|
|
171
|
+
try {
|
|
172
|
+
await rm(mainCssPath);
|
|
173
|
+
} catch (error: unknown) {
|
|
174
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
175
|
+
console.warn(
|
|
176
|
+
`Warning: Could not remove main.css: ${error instanceof Error ? error.message : String(error)}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Returns true if the current path is inside an Obsidian plugins folder */
|
|
183
|
+
export function isInPluginsFolder(currentPath: string): boolean {
|
|
184
|
+
return currentPath.includes(path.join('.obsidian', 'plugins'));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Validates that a path points to an Obsidian vault with a plugins directory */
|
|
188
|
+
export function validateVaultPath(vaultPath: string): boolean {
|
|
189
|
+
// Normalize path to handle both forward and backward slashes
|
|
190
|
+
const normalizedPath = path.normalize(vaultPath);
|
|
191
|
+
return (
|
|
192
|
+
existsSync(path.join(normalizedPath, '.obsidian')) &&
|
|
193
|
+
existsSync(path.join(normalizedPath, '.obsidian', 'plugins'))
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Resolves the full plugin install path from a vault path */
|
|
198
|
+
export function getVaultPath(vaultPath: string, pluginId: string): string {
|
|
199
|
+
if (!validateVaultPath(vaultPath)) {
|
|
200
|
+
console.error(`❌ Invalid vault path: ${vaultPath}`);
|
|
201
|
+
console.error(` The path must contain a .obsidian/plugins directory`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
const pluginsPath = path.join('.obsidian', 'plugins');
|
|
205
|
+
return vaultPath.includes(pluginsPath)
|
|
206
|
+
? path.join(vaultPath, pluginId)
|
|
207
|
+
: path.join(vaultPath, '.obsidian', 'plugins', pluginId);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Updates or adds an env key in the .env file */
|
|
211
|
+
export async function updateEnvFile(
|
|
212
|
+
envKey: string,
|
|
213
|
+
vaultPath: string,
|
|
214
|
+
envPath: string
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
let envContent = '';
|
|
217
|
+
try {
|
|
218
|
+
envContent = readFileSync(envPath, 'utf8');
|
|
219
|
+
} catch {
|
|
220
|
+
/* file doesn't exist yet */
|
|
221
|
+
}
|
|
222
|
+
const regex = new RegExp(`^${envKey}=.*$`, 'm');
|
|
223
|
+
const newLine = `${envKey}=${vaultPath}`;
|
|
224
|
+
envContent = regex.test(envContent)
|
|
225
|
+
? envContent.replace(regex, newLine)
|
|
226
|
+
: envContent + (envContent.endsWith('\n') ? '' : '\n') + newLine + '\n';
|
|
227
|
+
await writeFile(envPath, envContent);
|
|
228
|
+
console.log(`✅ Updated ${envKey} in .env file`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Prompt the user for a vault path if it's missing or still a placeholder in the .env file.
|
|
233
|
+
*/
|
|
234
|
+
export async function promptForVaultPath(
|
|
235
|
+
envKey: string,
|
|
236
|
+
rl: readline.Interface
|
|
237
|
+
): Promise<string> {
|
|
238
|
+
const vaultType = envKey === 'REAL_VAULT' ? 'real' : 'test';
|
|
239
|
+
const usage =
|
|
240
|
+
envKey === 'REAL_VAULT'
|
|
241
|
+
? 'for final plugin installation'
|
|
242
|
+
: 'for development and testing';
|
|
243
|
+
|
|
244
|
+
console.log(`❓ ${envKey} path is required ${usage}`);
|
|
245
|
+
const vaultPath = await askQuestion(
|
|
246
|
+
`📝 Enter your ${vaultType} vault path (or Ctrl+C to cancel): `,
|
|
247
|
+
rl
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (!vaultPath) {
|
|
251
|
+
console.log('❌ No path provided, exiting...');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return vaultPath;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Creates a .env file with placeholder paths and API config if it doesn't exist */
|
|
259
|
+
export async function ensureEnvFile(envPath: string): Promise<void> {
|
|
260
|
+
if (await isValidPath(envPath)) return;
|
|
261
|
+
const template =
|
|
262
|
+
[
|
|
263
|
+
'# Environment variables for plugin development',
|
|
264
|
+
'#',
|
|
265
|
+
'# Vault paths',
|
|
266
|
+
'# TEST_VAULT: path to your test/development vault (used with yarn dev)',
|
|
267
|
+
'# REAL_VAULT: path to your production vault (used with yarn real)',
|
|
268
|
+
'TEST_VAULT=/path/to/your/test/vault',
|
|
269
|
+
'REAL_VAULT=/path/to/your/real/vault',
|
|
270
|
+
'#',
|
|
271
|
+
'# Obsidian Local REST API (for automatic reload after yarn real)',
|
|
272
|
+
'# 1. Install the "Local REST API" plugin in Obsidian',
|
|
273
|
+
'# 2. Enable "Enable Non-encrypted (HTTP) Server" in plugin settings',
|
|
274
|
+
'# 3. Copy the API Key from plugin settings and paste it below',
|
|
275
|
+
'OBSIDIAN_REST_API_KEY=your_api_key_here'
|
|
276
|
+
].join('\n') + '\n';
|
|
277
|
+
await writeFile(envPath, template);
|
|
278
|
+
console.log('📄 Created .env with placeholder paths and API configuration');
|
|
179
279
|
}
|
package/templates/tsconfig.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"module": "NodeNext",
|
|
11
11
|
"moduleResolution": "NodeNext",
|
|
12
|
-
"target": "
|
|
12
|
+
"target": "ES2022",
|
|
13
13
|
"inlineSourceMap": true,
|
|
14
14
|
"inlineSources": true,
|
|
15
15
|
"allowJs": true,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"forceConsistentCasingInFileNames": true,
|
|
24
24
|
"strictNullChecks": true,
|
|
25
25
|
"resolveJsonModule": true,
|
|
26
|
-
"lib": ["DOM", "
|
|
26
|
+
"lib": ["DOM", "ES2024"]
|
|
27
27
|
},
|
|
28
28
|
"include": ["./src/**/*.ts", "./scripts/**/*.ts"],
|
|
29
29
|
"exclude": ["node_modules", "eslint.config.ts"]
|
package/tsconfig.json
CHANGED