obsidian-plugin-config 1.6.10 → 1.6.11

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.
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Obsidian Plugin Config - CLI Entry Point
5
5
  * Global command: obsidian-inject
6
- * Version: 1.6.10
6
+ * Version: 1.6.11
7
7
  */
8
8
 
9
9
  import { execSync } from 'child_process';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obsidian-plugin-config",
3
- "version": "1.6.10",
3
+ "version": "1.6.11",
4
4
  "description": "Global CLI injection tool for Obsidian plugins",
5
5
  "type": "module",
6
6
  "bin": {
@@ -195,7 +195,10 @@ export async function showInjectionPlan(
195
195
  /**
196
196
  * Clean old script files
197
197
  */
198
- export async function cleanOldScripts(scriptsPath: string): Promise<void> {
198
+ export async function cleanOldScripts(
199
+ scriptsPath: string,
200
+ approvedDests: Set<string>
201
+ ): Promise<void> {
199
202
  const scriptNames = [
200
203
  'utils',
201
204
  'esbuild.config',
@@ -210,10 +213,10 @@ export async function cleanOldScripts(scriptsPath: string): Promise<void> {
210
213
  for (const ext of extensions) {
211
214
  const scriptFile = path.join(scriptsPath, `${scriptName}${ext}`);
212
215
  if (await isValidPath(scriptFile)) {
213
- fs.unlinkSync(scriptFile);
214
- console.log(
215
- `šŸ—‘ļø Removed existing ${scriptName}${ext} (will be replaced)`
216
- );
216
+ if (approvedDests.has(scriptFile)) {
217
+ fs.unlinkSync(scriptFile);
218
+ console.log(`šŸ—‘ļø Removed existing ${scriptName}${ext} (will be replaced)`);
219
+ }
217
220
  }
218
221
  }
219
222
  }
@@ -270,12 +273,166 @@ export async function cleanOldLintFiles(targetPath: string): Promise<void> {
270
273
  }
271
274
  }
272
275
 
276
+ interface FileEntry {
277
+ src: string; // path relative to configRoot
278
+ dest: string; // absolute path in target plugin
279
+ optionKey: keyof InjectionOptions | null; // null = always inject
280
+ mergeEnv?: boolean; // special .env merge logic
281
+ }
282
+
283
+ /**
284
+ * Build the full list of files to inject, with source and destination paths
285
+ */
286
+ function buildFileList(targetPath: string, options: InjectionOptions): FileEntry[] {
287
+ const scriptsPath = path.join(targetPath, 'scripts');
288
+ const entries: FileEntry[] = [];
289
+
290
+ // Scripts
291
+ const scriptFiles = [
292
+ 'templates/scripts/utils.ts',
293
+ 'templates/scripts/esbuild.config.ts',
294
+ 'templates/scripts/acp.ts',
295
+ 'templates/scripts/update-version.ts',
296
+ 'templates/scripts/release.ts',
297
+ 'templates/scripts/help.ts'
298
+ ];
299
+ for (const src of scriptFiles) {
300
+ entries.push({
301
+ src,
302
+ dest: path.join(scriptsPath, path.basename(src)),
303
+ optionKey: 'scripts'
304
+ });
305
+ }
306
+
307
+ // Root config files
308
+ const configFileMap: Array<[string, string, keyof InjectionOptions | null, boolean?]> =
309
+ [
310
+ ['templates/tsconfig.json', 'tsconfig.json', 'tsconfig'],
311
+ ['templates/gitignore.template', '.gitignore', 'gitignore'],
312
+ ['templates/eslint.config.mts', 'eslint.config.mts', 'eslint'],
313
+ ['templates/.editorconfig', '.editorconfig', 'editorconfig'],
314
+ ['templates/.prettierrc', '.prettierrc', 'prettier'],
315
+ ['templates/npmrc.template', '.npmrc', null],
316
+ ['templates/env.template', '.env', 'env', true]
317
+ ];
318
+ for (const [src, destName, optionKey, mergeEnv] of configFileMap) {
319
+ entries.push({
320
+ src,
321
+ dest: path.join(targetPath, destName),
322
+ optionKey,
323
+ mergeEnv: !!mergeEnv
324
+ });
325
+ }
326
+
327
+ // VSCode config files
328
+ const configVscodeMap: Array<[string, string]> = [
329
+ ['templates/.vscode/settings.json', '.vscode/settings.json'],
330
+ ['templates/.vscode/tasks.json', '.vscode/tasks.json']
331
+ ];
332
+ for (const [src, destName] of configVscodeMap) {
333
+ entries.push({
334
+ src,
335
+ dest: path.join(targetPath, destName),
336
+ optionKey: 'vscode'
337
+ });
338
+ }
339
+
340
+ // GitHub workflow files
341
+ const workflowFiles = [
342
+ 'templates/.github/workflows/release.yml',
343
+ 'templates/.github/workflows/release-body.md'
344
+ ];
345
+ for (const src of workflowFiles) {
346
+ entries.push({
347
+ src,
348
+ dest: path.join(targetPath, src.replace('templates/', '')),
349
+ optionKey: 'github'
350
+ });
351
+ }
352
+
353
+ return entries;
354
+ }
355
+
356
+ /**
357
+ * Compare source templates with existing target files.
358
+ * Prompt user only when content differs and file already exists.
359
+ * Returns the Set of dest paths approved for injection.
360
+ */
361
+ export async function diffAndPromptFiles(
362
+ targetPath: string,
363
+ options: InjectionOptions
364
+ ): Promise<Set<string>> {
365
+ const { askConfirmation, createReadlineInterface } = await import('./utils.js');
366
+ const rl = createReadlineInterface();
367
+ const configRoot = findPluginConfigRoot();
368
+ const entries = buildFileList(targetPath, options);
369
+ const approved = new Set<string>();
370
+
371
+ console.log(`\nšŸ” Comparing files with existing content...`);
372
+
373
+ let hasChanges = false;
374
+
375
+ for (const entry of entries) {
376
+ // Skip if disabled by options
377
+ if (entry.optionKey !== null && !options[entry.optionKey]) continue;
378
+ // Skip .env merge (always approved, merge logic handled separately)
379
+ if (entry.mergeEnv) {
380
+ approved.add(entry.dest);
381
+ continue;
382
+ }
383
+
384
+ const srcPath = path.join(configRoot, entry.src);
385
+ let srcContent: string;
386
+ try {
387
+ srcContent = fs.readFileSync(srcPath, 'utf8');
388
+ } catch {
389
+ // Source doesn't exist, skip
390
+ continue;
391
+ }
392
+
393
+ // Target doesn't exist yet → inject without prompting
394
+ if (!fs.existsSync(entry.dest)) {
395
+ approved.add(entry.dest);
396
+ continue;
397
+ }
398
+
399
+ const destContent = fs.readFileSync(entry.dest, 'utf8');
400
+
401
+ // Identical → skip silently
402
+ if (srcContent === destContent) {
403
+ console.log(` āœ… ${path.relative(targetPath, entry.dest)} (unchanged)`);
404
+ continue;
405
+ }
406
+
407
+ // Different → ask user
408
+ hasChanges = true;
409
+ const relDest = path.relative(targetPath, entry.dest);
410
+ const update = await askConfirmation(
411
+ ` Update ${relDest}? (content differs)`,
412
+ rl
413
+ );
414
+ if (update) {
415
+ approved.add(entry.dest);
416
+ } else {
417
+ console.log(` ā­ļø Kept existing ${relDest}`);
418
+ }
419
+ }
420
+
421
+ if (!hasChanges) {
422
+ console.log(` āœ… All existing files are up to date`);
423
+ }
424
+
425
+ rl.close();
426
+ return approved;
427
+ }
428
+
273
429
  /**
274
430
  * Inject scripts and config files
275
431
  */
276
432
  export async function injectScripts(
277
433
  targetPath: string,
278
- options: InjectionOptions
434
+ options: InjectionOptions,
435
+ approvedDests: Set<string>
279
436
  ): Promise<void> {
280
437
  const scriptsPath = path.join(targetPath, 'scripts');
281
438
 
@@ -284,7 +441,7 @@ export async function injectScripts(
284
441
  console.log(`šŸ“ Created scripts directory`);
285
442
  }
286
443
 
287
- await cleanOldScripts(scriptsPath);
444
+ await cleanOldScripts(scriptsPath, approvedDests);
288
445
  await cleanOldLintFiles(targetPath);
289
446
 
290
447
  const scriptFiles = [
@@ -327,9 +484,13 @@ export async function injectScripts(
327
484
  if (options.scripts) {
328
485
  for (const scriptFile of scriptFiles) {
329
486
  try {
330
- const content = copyFromLocal(scriptFile);
331
487
  const fileName = path.basename(scriptFile);
332
488
  const targetFile = path.join(scriptsPath, fileName);
489
+ if (!approvedDests.has(targetFile)) {
490
+ console.log(` ā­ļø Skipped ${fileName} (kept existing)`);
491
+ continue;
492
+ }
493
+ const content = copyFromLocal(scriptFile);
333
494
  fs.writeFileSync(targetFile, content, 'utf8');
334
495
  console.log(` āœ… ${fileName}`);
335
496
  } catch (error) {
@@ -359,6 +520,12 @@ export async function injectScripts(
359
520
  continue;
360
521
  }
361
522
 
523
+ // Skip if not approved by diff step
524
+ const targetFile = path.join(targetPath, destName);
525
+ if (!approvedDests.has(targetFile)) {
526
+ continue; // already logged during diff step
527
+ }
528
+
362
529
  try {
363
530
  const targetFile = path.join(targetPath, destName);
364
531
  const templateContent = copyFromLocal(src);
@@ -401,8 +568,9 @@ export async function injectScripts(
401
568
  if (options.vscode) {
402
569
  for (const [src, destName] of Object.entries(configVscodeMap)) {
403
570
  try {
404
- const content = copyFromLocal(src);
405
571
  const targetFile = path.join(targetPath, destName);
572
+ if (!approvedDests.has(targetFile)) continue;
573
+ const content = copyFromLocal(src);
406
574
  const targetDir = path.dirname(targetFile);
407
575
  if (!(await isValidPath(targetDir))) {
408
576
  fs.mkdirSync(targetDir, { recursive: true });
@@ -425,6 +593,7 @@ export async function injectScripts(
425
593
  const content = copyFromLocal(workflowFile);
426
594
  const relativePath = workflowFile.replace('templates/', '');
427
595
  const targetFile = path.join(targetPath, relativePath);
596
+ if (!approvedDests.has(targetFile)) continue;
428
597
  const targetDir = path.dirname(targetFile);
429
598
 
430
599
  if (!(await isValidPath(targetDir))) {
@@ -766,9 +935,10 @@ export async function performInjection(
766
935
  console.log(`\nšŸš€ Starting injection process...`);
767
936
 
768
937
  try {
938
+ const approvedDests = await diffAndPromptFiles(targetPath, options);
769
939
  await cleanNpmArtifactsIfNeeded(targetPath);
770
940
  await ensureTsxInstalled(targetPath);
771
- await injectScripts(targetPath, options);
941
+ await injectScripts(targetPath, options, approvedDests);
772
942
 
773
943
  console.log(`\nšŸ“¦ Updating package.json...`);
774
944
  await updatePackageJson(targetPath, options, useSass);