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.
@@ -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
- return readline.createInterface({
8
- input: process.stdin as NodeJS.ReadableStream,
9
- output: process.stdout as NodeJS.WritableStream
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
- question: string,
15
- rl: readline.Interface
15
+ question: string,
16
+ rl: readline.Interface
16
17
  ): Promise<string> => {
17
- try {
18
- return await new Promise((resolve) =>
19
- rl.question(question, (input) => resolve(input.trim()))
20
- );
21
- } catch (error) {
22
- console.error('Error asking question:', error);
23
- throw error;
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
- question: string,
35
- rl: readline.Interface
35
+ question: string,
36
+ rl: readline.Interface
36
37
  ): Promise<boolean> => {
37
- const answer = await askQuestion(`${question} [Y/n]: `, rl);
38
- const response = answer.toLowerCase();
39
-
40
- // Accept: y, yes, Y, YES, or empty (default to yes)
41
- // Reject: n, no, N, NO
42
- const isYes = response === '' || response === 'y' || response === 'yes';
43
- const isNo = response === 'n' || response === 'no';
44
-
45
- if (isNo) {
46
- return false;
47
- } else if (isYes) {
48
- return true;
49
- } else {
50
- console.log('Please answer Y (yes) or n (no). Defaulting to no for safety.');
51
- return false;
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
- if (!inputStr) return '';
57
- return inputStr.trim().replace(/["`]/g, "'").replace(/\r\n/g, '\n');
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
- if (!pathToCheck) return false;
62
-
63
- try {
64
- // Using async fs.access is preferred over synchronous existsSync
65
- // as it doesn't block the main thread/event loop
66
- await access(pathToCheck.trim());
67
- return true;
68
- } catch {
69
- return false;
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
- const pluginDir = process.cwd();
75
- const manifestSrc = path.join(pluginDir, 'manifest.json');
76
- const manifestDest = path.join(buildPath, 'manifest.json');
77
- const cssDest = path.join(buildPath, 'styles.css');
78
- const folderToRemove = path.join(buildPath, '_.._');
79
-
80
- try {
81
- await mkdir(buildPath, { recursive: true });
82
- } catch (error: unknown) {
83
- if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {
84
- console.error(
85
- `Error creating directory: ${error instanceof Error ? error.message : String(error)}`
86
- );
87
- }
88
- }
89
-
90
- // Copy manifest
91
- try {
92
- await copyFile(manifestSrc, manifestDest);
93
- } catch (error: unknown) {
94
- console.error(
95
- `Error copying manifest: ${error instanceof Error ? error.message : String(error)}`
96
- );
97
- }
98
-
99
- // Copy CSS
100
- try {
101
- const srcStylesPath = path.join(pluginDir, 'src/styles.css');
102
- const rootStylesPath = path.join(pluginDir, 'styles.css');
103
-
104
- // First check if CSS exists in src/styles.css
105
- if (await isValidPath(srcStylesPath)) {
106
- await copyFile(srcStylesPath, cssDest);
107
- }
108
- // Otherwise, check if it exists in the root
109
- else if (await isValidPath(rootStylesPath)) {
110
- await copyFile(rootStylesPath, cssDest);
111
- if (await isValidPath(folderToRemove)) {
112
- await rm(folderToRemove, { recursive: true });
113
- }
114
- } else {
115
- return;
116
- }
117
- } catch (error: unknown) {
118
- console.error(
119
- `Error copying CSS: ${error instanceof Error ? error.message : String(error)}`
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
- try {
126
- execSync(command, { stdio: 'inherit' });
127
- } catch (error: unknown) {
128
- console.error(
129
- `Error executing '${command}':`,
130
- error instanceof Error ? error.message : String(error)
131
- );
132
- throw error;
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
- try {
141
- console.log('🔄 Checking Git synchronization...');
142
-
143
- // Fetch latest changes from remote
144
- execSync('git fetch origin', { stdio: 'pipe' });
145
-
146
- // Check if branch is behind remote
147
- const status = execSync('git status --porcelain -b', { encoding: 'utf8' });
148
-
149
- if (status.includes('behind')) {
150
- console.log('📥 Branch behind remote. Pulling changes...');
151
- execSync('git pull', { stdio: 'inherit' });
152
- console.log('✅ Successfully pulled remote changes');
153
- } else {
154
- console.log('✅ Repository is synchronized with remote');
155
- }
156
- } catch (error: unknown) {
157
- console.error(
158
- `❌ Git sync failed: ${error instanceof Error ? error.message : String(error)}`
159
- );
160
- throw error;
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
- const mainCssPath = path.join(outdir, 'main.css');
170
- try {
171
- await rm(mainCssPath);
172
- } catch (error: unknown) {
173
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
174
- console.warn(
175
- `Warning: Could not remove main.css: ${error instanceof Error ? error.message : String(error)}`
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
  }
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "module": "NodeNext",
11
11
  "moduleResolution": "NodeNext",
12
- "target": "ES2021",
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", "ES2021"]
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
@@ -20,5 +20,5 @@
20
20
  "lib": ["DOM", "ES2021"]
21
21
  },
22
22
  "include": ["./scripts/**/*.ts"],
23
- "exclude": ["node_modules", "eslint.config.ts", "templates/tsconfig.json"]
23
+ "exclude": ["node_modules", "eslint.config.ts", "templates"]
24
24
  }
@@ -1,5 +0,0 @@
1
- {
2
- "devDependencies": {
3
- "esbuild-sass-plugin": "latest"
4
- }
5
- }