safeword 0.8.9 → 0.8.10

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.
@@ -88,10 +88,428 @@ function writeJson(path, data) {
88
88
  `);
89
89
  }
90
90
 
91
+ // src/reconcile.ts
92
+ import nodePath2 from "path";
93
+ var HUSKY_DIR = ".husky";
94
+ function shouldSkipForNonGit(path, isGitRepo2) {
95
+ return path.startsWith(HUSKY_DIR) && !isGitRepo2;
96
+ }
97
+ function planMissingDirectories(directories, cwd, isGitRepo2) {
98
+ const actions = [];
99
+ const created = [];
100
+ for (const dir of directories) {
101
+ if (shouldSkipForNonGit(dir, isGitRepo2)) continue;
102
+ if (!exists(nodePath2.join(cwd, dir))) {
103
+ actions.push({ type: "mkdir", path: dir });
104
+ created.push(dir);
105
+ }
106
+ }
107
+ return { actions, created };
108
+ }
109
+ function planTextPatches(patches, cwd, isGitRepo2) {
110
+ const actions = [];
111
+ for (const [filePath, definition] of Object.entries(patches)) {
112
+ if (shouldSkipForNonGit(filePath, isGitRepo2)) continue;
113
+ const content = readFileSafe(nodePath2.join(cwd, filePath)) ?? "";
114
+ if (!content.includes(definition.marker)) {
115
+ actions.push({ type: "text-patch", path: filePath, definition });
116
+ }
117
+ }
118
+ return actions;
119
+ }
120
+ function planOwnedFileWrites(files, ctx) {
121
+ const actions = [];
122
+ const created = [];
123
+ for (const [filePath, definition] of Object.entries(files)) {
124
+ if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
125
+ const content = resolveFileContent(definition, ctx);
126
+ actions.push({ type: "write", path: filePath, content });
127
+ created.push(filePath);
128
+ }
129
+ return { actions, created };
130
+ }
131
+ function planManagedFileWrites(files, ctx) {
132
+ const actions = [];
133
+ const created = [];
134
+ for (const [filePath, definition] of Object.entries(files)) {
135
+ if (exists(nodePath2.join(ctx.cwd, filePath))) continue;
136
+ const content = resolveFileContent(definition, ctx);
137
+ actions.push({ type: "write", path: filePath, content });
138
+ created.push(filePath);
139
+ }
140
+ return { actions, created };
141
+ }
142
+ function planTextPatchesWithCreation(patches, ctx, wouldCreate) {
143
+ const actions = [];
144
+ for (const [filePath, definition] of Object.entries(patches)) {
145
+ if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
146
+ actions.push({ type: "text-patch", path: filePath, definition });
147
+ if (definition.createIfMissing && !exists(nodePath2.join(ctx.cwd, filePath))) {
148
+ wouldCreate.push(filePath);
149
+ }
150
+ }
151
+ return actions;
152
+ }
153
+ function planExistingDirectoriesRemoval(directories, cwd) {
154
+ const actions = [];
155
+ const removed = [];
156
+ for (const dir of directories) {
157
+ if (exists(nodePath2.join(cwd, dir))) {
158
+ actions.push({ type: "rmdir", path: dir });
159
+ removed.push(dir);
160
+ }
161
+ }
162
+ return { actions, removed };
163
+ }
164
+ function planExistingFilesRemoval(files, cwd) {
165
+ const actions = [];
166
+ const removed = [];
167
+ for (const filePath of files) {
168
+ if (exists(nodePath2.join(cwd, filePath))) {
169
+ actions.push({ type: "rm", path: filePath });
170
+ removed.push(filePath);
171
+ }
172
+ }
173
+ return { actions, removed };
174
+ }
175
+ function getClaudeParentDirectoryForCleanup(filePath) {
176
+ if (!filePath.startsWith(".claude/")) return void 0;
177
+ const parentDirectory = filePath.slice(0, Math.max(0, filePath.lastIndexOf("/")));
178
+ if (!parentDirectory || parentDirectory === ".claude" || parentDirectory === ".claude/skills" || parentDirectory === ".claude/commands") {
179
+ return void 0;
180
+ }
181
+ return parentDirectory;
182
+ }
183
+ async function reconcile(schema, mode, ctx, options) {
184
+ const dryRun = options?.dryRun ?? false;
185
+ const plan = computePlan(schema, mode, ctx);
186
+ if (dryRun) {
187
+ return {
188
+ actions: plan.actions,
189
+ applied: false,
190
+ created: plan.wouldCreate,
191
+ updated: plan.wouldUpdate,
192
+ removed: plan.wouldRemove,
193
+ packagesToInstall: plan.packagesToInstall,
194
+ packagesToRemove: plan.packagesToRemove
195
+ };
196
+ }
197
+ const result = executePlan(plan, ctx);
198
+ return {
199
+ actions: plan.actions,
200
+ applied: true,
201
+ created: result.created,
202
+ updated: result.updated,
203
+ removed: result.removed,
204
+ packagesToInstall: plan.packagesToInstall,
205
+ packagesToRemove: plan.packagesToRemove
206
+ };
207
+ }
208
+ function planDeprecatedFilesRemoval(deprecatedFiles, cwd) {
209
+ const actions = [];
210
+ const removed = [];
211
+ for (const filePath of deprecatedFiles) {
212
+ if (exists(nodePath2.join(cwd, filePath))) {
213
+ actions.push({ type: "rm", path: filePath });
214
+ removed.push(filePath);
215
+ }
216
+ }
217
+ return { actions, removed };
218
+ }
219
+ function computePlan(schema, mode, ctx) {
220
+ switch (mode) {
221
+ case "install": {
222
+ return computeInstallPlan(schema, ctx);
223
+ }
224
+ case "upgrade": {
225
+ return computeUpgradePlan(schema, ctx);
226
+ }
227
+ case "uninstall": {
228
+ return computeUninstallPlan(schema, ctx, false);
229
+ }
230
+ case "uninstall-full": {
231
+ return computeUninstallPlan(schema, ctx, true);
232
+ }
233
+ default: {
234
+ const _exhaustiveCheck = mode;
235
+ return _exhaustiveCheck;
236
+ }
237
+ }
238
+ }
239
+ function computeInstallPlan(schema, ctx) {
240
+ const actions = [];
241
+ const wouldCreate = [];
242
+ const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
243
+ const dirs = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);
244
+ actions.push(...dirs.actions);
245
+ wouldCreate.push(...dirs.created);
246
+ const owned = planOwnedFileWrites(schema.ownedFiles, ctx);
247
+ actions.push(...owned.actions);
248
+ wouldCreate.push(...owned.created);
249
+ const managed = planManagedFileWrites(schema.managedFiles, ctx);
250
+ actions.push(...managed.actions);
251
+ wouldCreate.push(...managed.created);
252
+ const chmodPaths = [".safeword/hooks", ".safeword/hooks/cursor", ".safeword/lib", ".safeword/scripts"];
253
+ if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);
254
+ actions.push({ type: "chmod", paths: chmodPaths });
255
+ for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
256
+ actions.push({ type: "json-merge", path: filePath, definition });
257
+ }
258
+ actions.push(...planTextPatchesWithCreation(schema.textPatches, ctx, wouldCreate));
259
+ const packagesToInstall = computePackagesToInstall(schema, ctx.projectType, ctx.developmentDeps, ctx.isGitRepo);
260
+ return { actions, wouldCreate, wouldUpdate: [], wouldRemove: [], packagesToInstall, packagesToRemove: [] };
261
+ }
262
+ function computeUpgradePlan(schema, ctx) {
263
+ const actions = [];
264
+ const wouldCreate = [];
265
+ const wouldUpdate = [];
266
+ const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
267
+ const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);
268
+ actions.push(...missingDirectories.actions);
269
+ wouldCreate.push(...missingDirectories.created);
270
+ for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
271
+ if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
272
+ const fullPath = nodePath2.join(ctx.cwd, filePath);
273
+ const newContent = resolveFileContent(definition, ctx);
274
+ if (!fileNeedsUpdate(fullPath, newContent)) continue;
275
+ actions.push({ type: "write", path: filePath, content: newContent });
276
+ if (exists(fullPath)) {
277
+ wouldUpdate.push(filePath);
278
+ } else {
279
+ wouldCreate.push(filePath);
280
+ }
281
+ }
282
+ for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
283
+ const fullPath = nodePath2.join(ctx.cwd, filePath);
284
+ const newContent = resolveFileContent(definition, ctx);
285
+ if (!exists(fullPath)) {
286
+ actions.push({ type: "write", path: filePath, content: newContent });
287
+ wouldCreate.push(filePath);
288
+ }
289
+ }
290
+ const deprecatedFiles = planDeprecatedFilesRemoval(schema.deprecatedFiles, ctx.cwd);
291
+ actions.push(...deprecatedFiles.actions);
292
+ const wouldRemove = deprecatedFiles.removed;
293
+ const deprecatedDirectories = planExistingDirectoriesRemoval(schema.deprecatedDirs, ctx.cwd);
294
+ actions.push(...deprecatedDirectories.actions);
295
+ wouldRemove.push(...deprecatedDirectories.removed);
296
+ const chmodPathsUpgrade = [
297
+ ".safeword/hooks",
298
+ ".safeword/hooks/cursor",
299
+ ".safeword/lib",
300
+ ".safeword/scripts"
301
+ ];
302
+ actions.push({ type: "chmod", paths: chmodPathsUpgrade });
303
+ for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
304
+ actions.push({ type: "json-merge", path: filePath, definition });
305
+ }
306
+ actions.push(...planTextPatches(schema.textPatches, ctx.cwd, ctx.isGitRepo));
307
+ const packagesToInstall = computePackagesToInstall(
308
+ schema,
309
+ ctx.projectType,
310
+ ctx.developmentDeps,
311
+ ctx.isGitRepo
312
+ );
313
+ const packagesToRemove = schema.deprecatedPackages.filter((pkg) => pkg in ctx.developmentDeps);
314
+ return {
315
+ actions,
316
+ wouldCreate,
317
+ wouldUpdate,
318
+ wouldRemove,
319
+ packagesToInstall,
320
+ packagesToRemove
321
+ };
322
+ }
323
+ function computeUninstallPlan(schema, ctx, full) {
324
+ const actions = [];
325
+ const wouldRemove = [];
326
+ const ownedFiles = planExistingFilesRemoval(Object.keys(schema.ownedFiles), ctx.cwd);
327
+ actions.push(...ownedFiles.actions);
328
+ wouldRemove.push(...ownedFiles.removed);
329
+ const directoriesToCleanup = /* @__PURE__ */ new Set();
330
+ for (const filePath of ownedFiles.removed) {
331
+ const parentDirectory = getClaudeParentDirectoryForCleanup(filePath);
332
+ if (parentDirectory) directoriesToCleanup.add(parentDirectory);
333
+ }
334
+ const cleanupDirectories = planExistingDirectoriesRemoval([...directoriesToCleanup], ctx.cwd);
335
+ actions.push(...cleanupDirectories.actions);
336
+ wouldRemove.push(...cleanupDirectories.removed);
337
+ for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
338
+ actions.push({ type: "json-unmerge", path: filePath, definition });
339
+ }
340
+ for (const [filePath, definition] of Object.entries(schema.textPatches)) {
341
+ const fullPath = nodePath2.join(ctx.cwd, filePath);
342
+ if (exists(fullPath)) {
343
+ const content = readFileSafe(fullPath) ?? "";
344
+ if (content.includes(definition.marker)) {
345
+ actions.push({ type: "text-unpatch", path: filePath, definition });
346
+ }
347
+ }
348
+ }
349
+ const preserved = planExistingDirectoriesRemoval(schema.preservedDirs.toReversed(), ctx.cwd);
350
+ actions.push(...preserved.actions);
351
+ wouldRemove.push(...preserved.removed);
352
+ const owned = planExistingDirectoriesRemoval(schema.ownedDirs.toReversed(), ctx.cwd);
353
+ actions.push(...owned.actions);
354
+ wouldRemove.push(...owned.removed);
355
+ if (full) {
356
+ const managed = planExistingFilesRemoval(Object.keys(schema.managedFiles), ctx.cwd);
357
+ actions.push(...managed.actions);
358
+ wouldRemove.push(...managed.removed);
359
+ }
360
+ const packagesToRemove = full ? computePackagesToRemove(schema, ctx.projectType, ctx.developmentDeps) : [];
361
+ return {
362
+ actions,
363
+ wouldCreate: [],
364
+ wouldUpdate: [],
365
+ wouldRemove,
366
+ packagesToInstall: [],
367
+ packagesToRemove
368
+ };
369
+ }
370
+ function executePlan(plan, ctx) {
371
+ const created = [];
372
+ const updated = [];
373
+ const removed = [];
374
+ const result = { created, updated, removed };
375
+ for (const action of plan.actions) {
376
+ executeAction(action, ctx, result);
377
+ }
378
+ return result;
379
+ }
380
+ function executeChmod(cwd, paths) {
381
+ for (const path of paths) {
382
+ const fullPath = nodePath2.join(cwd, path);
383
+ if (exists(fullPath)) makeScriptsExecutable(fullPath);
384
+ }
385
+ }
386
+ function executeRmdir(cwd, path, result) {
387
+ if (removeIfEmpty(nodePath2.join(cwd, path))) result.removed.push(path);
388
+ }
389
+ function executeAction(action, ctx, result) {
390
+ switch (action.type) {
391
+ case "mkdir":
392
+ ensureDirectory(nodePath2.join(ctx.cwd, action.path));
393
+ result.created.push(action.path);
394
+ break;
395
+ case "rmdir":
396
+ executeRmdir(ctx.cwd, action.path, result);
397
+ break;
398
+ case "write":
399
+ executeWrite(ctx.cwd, action.path, action.content, result);
400
+ break;
401
+ case "rm":
402
+ remove(nodePath2.join(ctx.cwd, action.path));
403
+ result.removed.push(action.path);
404
+ break;
405
+ case "chmod":
406
+ executeChmod(ctx.cwd, action.paths);
407
+ break;
408
+ case "json-merge":
409
+ executeJsonMerge(ctx.cwd, action.path, action.definition, ctx);
410
+ break;
411
+ case "json-unmerge":
412
+ executeJsonUnmerge(ctx.cwd, action.path, action.definition);
413
+ break;
414
+ case "text-patch":
415
+ executeTextPatch(ctx.cwd, action.path, action.definition);
416
+ break;
417
+ case "text-unpatch":
418
+ executeTextUnpatch(ctx.cwd, action.path, action.definition);
419
+ break;
420
+ }
421
+ }
422
+ function executeWrite(cwd, path, content, result) {
423
+ const fullPath = nodePath2.join(cwd, path);
424
+ const existed = exists(fullPath);
425
+ writeFile(fullPath, content);
426
+ (existed ? result.updated : result.created).push(path);
427
+ }
428
+ function resolveFileContent(definition, ctx) {
429
+ if (definition.template) {
430
+ const templatesDirectory = getTemplatesDirectory();
431
+ return readFile(nodePath2.join(templatesDirectory, definition.template));
432
+ }
433
+ if (definition.content) {
434
+ return typeof definition.content === "function" ? definition.content() : definition.content;
435
+ }
436
+ if (definition.generator) {
437
+ return definition.generator(ctx);
438
+ }
439
+ throw new Error("FileDefinition must have template, content, or generator");
440
+ }
441
+ function fileNeedsUpdate(installedPath, newContent) {
442
+ if (!exists(installedPath)) return true;
443
+ const currentContent = readFileSafe(installedPath);
444
+ return currentContent?.trim() !== newContent.trim();
445
+ }
446
+ var GIT_ONLY_PACKAGES = /* @__PURE__ */ new Set(["husky", "lint-staged"]);
447
+ function computePackagesToInstall(schema, projectType, installedDevelopmentDeps, isGitRepo2 = true) {
448
+ let needed = [...schema.packages.base];
449
+ if (!isGitRepo2) {
450
+ needed = needed.filter((pkg) => !GIT_ONLY_PACKAGES.has(pkg));
451
+ }
452
+ for (const [key, deps] of Object.entries(schema.packages.conditional)) {
453
+ if (projectType[key]) {
454
+ needed.push(...deps);
455
+ }
456
+ }
457
+ return needed.filter((pkg) => !(pkg in installedDevelopmentDeps));
458
+ }
459
+ function computePackagesToRemove(schema, projectType, installedDevelopmentDeps) {
460
+ const safewordPackages = [...schema.packages.base];
461
+ for (const [key, deps] of Object.entries(schema.packages.conditional)) {
462
+ if (projectType[key]) {
463
+ safewordPackages.push(...deps);
464
+ }
465
+ }
466
+ return safewordPackages.filter((pkg) => pkg in installedDevelopmentDeps);
467
+ }
468
+ function executeJsonMerge(cwd, path, definition, ctx) {
469
+ const fullPath = nodePath2.join(cwd, path);
470
+ const existing = readJson(fullPath) ?? {};
471
+ const merged = definition.merge(existing, ctx);
472
+ if (JSON.stringify(existing) === JSON.stringify(merged)) return;
473
+ writeJson(fullPath, merged);
474
+ }
475
+ function executeJsonUnmerge(cwd, path, definition) {
476
+ const fullPath = nodePath2.join(cwd, path);
477
+ if (!exists(fullPath)) return;
478
+ const existing = readJson(fullPath);
479
+ if (!existing) return;
480
+ const unmerged = definition.unmerge(existing);
481
+ if (definition.removeFileIfEmpty) {
482
+ const remainingKeys = Object.keys(unmerged).filter((k) => unmerged[k] !== void 0);
483
+ if (remainingKeys.length === 0) {
484
+ remove(fullPath);
485
+ return;
486
+ }
487
+ }
488
+ writeJson(fullPath, unmerged);
489
+ }
490
+ function executeTextPatch(cwd, path, definition) {
491
+ const fullPath = nodePath2.join(cwd, path);
492
+ let content = readFileSafe(fullPath) ?? "";
493
+ if (content.includes(definition.marker)) return;
494
+ content = definition.operation === "prepend" ? definition.content + content : content + definition.content;
495
+ writeFile(fullPath, content);
496
+ }
497
+ function executeTextUnpatch(cwd, path, definition) {
498
+ const fullPath = nodePath2.join(cwd, path);
499
+ const content = readFileSafe(fullPath);
500
+ if (!content) return;
501
+ let unpatched = content.replace(definition.content, "");
502
+ if (unpatched === content && content.includes(definition.marker)) {
503
+ const lines = content.split("\n");
504
+ const filtered = lines.filter((line) => !line.includes(definition.marker));
505
+ unpatched = filtered.join("\n").replace(/^\n+/, "");
506
+ }
507
+ writeFile(fullPath, unpatched);
508
+ }
509
+
91
510
  // src/templates/config.ts
92
511
  function getEslintConfig() {
93
- return `/* eslint-disable import-x/no-unresolved, no-undef -- dynamic imports for optional framework plugins */
94
- import { readFileSync } from "fs";
512
+ return `import { readFileSync } from "fs";
95
513
  import { dirname, join } from "path";
96
514
  import { fileURLToPath } from "url";
97
515
  import safeword from "eslint-plugin-safeword";
@@ -102,27 +520,10 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
102
520
  const pkg = JSON.parse(readFileSync(join(__dirname, "package.json"), "utf8"));
103
521
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
104
522
 
105
- // Helper for dynamic imports with actionable error messages
106
- async function tryImport(pkgName, frameworkName) {
107
- try {
108
- return await import(pkgName);
109
- } catch (err) {
110
- if (err.code === "ERR_MODULE_NOT_FOUND") {
111
- console.error(\`\\n\u2717 Missing ESLint plugin for \${frameworkName}\\n\`);
112
- console.error(\`Your package.json has \${frameworkName} but the ESLint plugin is not installed.\`);
113
- console.error(\`Run: npm install -D \${pkgName}\\n\`);
114
- console.error(\`Or run: npx safeword sync\\n\`);
115
- }
116
- throw err;
117
- }
118
- }
119
-
120
523
  // Build dynamic ignores based on detected frameworks
121
524
  const ignores = ["**/node_modules/", "**/dist/", "**/build/", "**/coverage/"];
122
525
  if (deps["next"]) ignores.push(".next/");
123
526
  if (deps["astro"]) ignores.push(".astro/");
124
- if (deps["vue"] || deps["nuxt"]) ignores.push(".nuxt/");
125
- if (deps["svelte"] || deps["@sveltejs/kit"]) ignores.push(".svelte-kit/");
126
527
 
127
528
  // Select appropriate safeword config based on detected framework
128
529
  // Order matters: most specific first
@@ -153,22 +554,6 @@ if (deps["playwright"] || deps["@playwright/test"]) {
153
554
  configs.push(...safeword.configs.playwright);
154
555
  }
155
556
 
156
- // Frameworks NOT in eslint-plugin-safeword (dynamic imports)
157
- if (deps["vue"] || deps["nuxt"]) {
158
- const vue = await tryImport("eslint-plugin-vue", "Vue");
159
- configs.push(...vue.default.configs["flat/recommended"]);
160
- }
161
-
162
- if (deps["svelte"] || deps["@sveltejs/kit"]) {
163
- const svelte = await tryImport("eslint-plugin-svelte", "Svelte");
164
- configs.push(...svelte.default.configs.recommended);
165
- }
166
-
167
- if (deps["electron"]) {
168
- const electron = await tryImport("@electron-toolkit/eslint-config", "Electron");
169
- configs.push(electron.default);
170
- }
171
-
172
557
  // eslint-config-prettier must be last to disable conflicting rules
173
558
  configs.push(eslintConfigPrettier);
174
559
 
@@ -268,7 +653,6 @@ function getPrettierConfig(projectType) {
268
653
  };
269
654
  const plugins = [];
270
655
  if (projectType.astro) plugins.push("prettier-plugin-astro");
271
- if (projectType.svelte) plugins.push("prettier-plugin-svelte");
272
656
  if (projectType.shell) plugins.push("prettier-plugin-sh");
273
657
  if (projectType.tailwind) plugins.push("prettier-plugin-tailwindcss");
274
658
  if (plugins.length > 0) {
@@ -303,16 +687,6 @@ var MCP_SERVERS = {
303
687
  };
304
688
 
305
689
  // src/schema.ts
306
- function isEslintPackage(pkg) {
307
- return pkg.startsWith("eslint") || pkg.startsWith("@eslint/") || pkg.startsWith("@microsoft/eslint") || pkg.startsWith("@next/eslint") || pkg.startsWith("@vitest/eslint") || pkg.startsWith("@electron-toolkit/eslint") || pkg === "typescript-eslint";
308
- }
309
- function getBaseEslintPackages() {
310
- return SAFEWORD_SCHEMA.packages.base.filter((pkg) => isEslintPackage(pkg));
311
- }
312
- function getConditionalEslintPackages(key) {
313
- const deps = SAFEWORD_SCHEMA.packages.conditional[key];
314
- return deps ? deps.filter((pkg) => isEslintPackage(pkg)) : [];
315
- }
316
690
  var SAFEWORD_SCHEMA = {
317
691
  version: VERSION,
318
692
  // Directories fully owned by safeword (created on setup, deleted on reset)
@@ -713,18 +1087,13 @@ var SAFEWORD_SCHEMA = {
713
1087
  // Core tools
714
1088
  "eslint",
715
1089
  "prettier",
716
- "eslint-config-prettier",
717
- // Safeword plugin (bundles all ESLint plugins)
1090
+ // Safeword plugin (bundles eslint-config-prettier + all ESLint plugins)
718
1091
  "eslint-plugin-safeword",
719
1092
  // Non-ESLint tools
720
1093
  "markdownlint-cli2",
721
1094
  "knip"
722
1095
  ],
723
1096
  conditional: {
724
- // Frameworks NOT in eslint-plugin-safeword
725
- vue: ["eslint-plugin-vue"],
726
- svelte: ["eslint-plugin-svelte", "prettier-plugin-svelte"],
727
- electron: ["@electron-toolkit/eslint-config"],
728
1097
  // Prettier plugins
729
1098
  astro: ["prettier-plugin-astro"],
730
1099
  tailwind: ["prettier-plugin-tailwindcss"],
@@ -735,9 +1104,18 @@ var SAFEWORD_SCHEMA = {
735
1104
  }
736
1105
  };
737
1106
 
1107
+ // src/utils/git.ts
1108
+ import nodePath3 from "path";
1109
+ function isGitRepo(cwd) {
1110
+ return exists(nodePath3.join(cwd, ".git"));
1111
+ }
1112
+
1113
+ // src/utils/context.ts
1114
+ import nodePath5 from "path";
1115
+
738
1116
  // src/utils/project-detector.ts
739
1117
  import { readdirSync as readdirSync2 } from "fs";
740
- import nodePath2 from "path";
1118
+ import nodePath4 from "path";
741
1119
  function hasShellScripts(cwd, maxDepth = 4) {
742
1120
  const excludeDirectories = /* @__PURE__ */ new Set(["node_modules", ".git", ".safeword"]);
743
1121
  function scan(dir, depth) {
@@ -748,7 +1126,7 @@ function hasShellScripts(cwd, maxDepth = 4) {
748
1126
  if (entry.isFile() && entry.name.endsWith(".sh")) {
749
1127
  return true;
750
1128
  }
751
- if (entry.isDirectory() && !excludeDirectories.has(entry.name) && scan(nodePath2.join(dir, entry.name), depth + 1)) {
1129
+ if (entry.isDirectory() && !excludeDirectories.has(entry.name) && scan(nodePath4.join(dir, entry.name), depth + 1)) {
752
1130
  return true;
753
1131
  }
754
1132
  }
@@ -766,11 +1144,6 @@ function detectProjectType(packageJson, cwd) {
766
1144
  const hasReact = "react" in deps || "react" in developmentDeps;
767
1145
  const hasNextJs = "next" in deps;
768
1146
  const hasAstro = "astro" in deps || "astro" in developmentDeps;
769
- const hasVue = "vue" in deps || "vue" in developmentDeps;
770
- const hasNuxt = "nuxt" in deps;
771
- const hasSvelte = "svelte" in deps || "svelte" in developmentDeps;
772
- const hasSvelteKit = "@sveltejs/kit" in deps || "@sveltejs/kit" in developmentDeps;
773
- const hasElectron = "electron" in deps || "electron" in developmentDeps;
774
1147
  const hasVitest = "vitest" in developmentDeps;
775
1148
  const hasPlaywright = "@playwright/test" in developmentDeps;
776
1149
  const hasTailwind = "tailwindcss" in allDeps;
@@ -783,13 +1156,6 @@ function detectProjectType(packageJson, cwd) {
783
1156
  // Next.js implies React
784
1157
  nextjs: hasNextJs,
785
1158
  astro: hasAstro,
786
- vue: hasVue || hasNuxt,
787
- // Nuxt implies Vue
788
- nuxt: hasNuxt,
789
- svelte: hasSvelte || hasSvelteKit,
790
- // SvelteKit implies Svelte
791
- sveltekit: hasSvelteKit,
792
- electron: hasElectron,
793
1159
  vitest: hasVitest,
794
1160
  playwright: hasPlaywright,
795
1161
  tailwind: hasTailwind,
@@ -798,21 +1164,56 @@ function detectProjectType(packageJson, cwd) {
798
1164
  };
799
1165
  }
800
1166
 
1167
+ // src/utils/context.ts
1168
+ function createProjectContext(cwd) {
1169
+ const packageJson = readJson(nodePath5.join(cwd, "package.json"));
1170
+ return {
1171
+ cwd,
1172
+ projectType: detectProjectType(packageJson ?? {}, cwd),
1173
+ developmentDeps: packageJson?.devDependencies ?? {},
1174
+ isGitRepo: isGitRepo(cwd)
1175
+ };
1176
+ }
1177
+
1178
+ // src/utils/output.ts
1179
+ function info(message) {
1180
+ console.log(message);
1181
+ }
1182
+ function success(message) {
1183
+ console.log(`\u2713 ${message}`);
1184
+ }
1185
+ function warn(message) {
1186
+ console.warn(`\u26A0 ${message}`);
1187
+ }
1188
+ function error(message) {
1189
+ console.error(`\u2717 ${message}`);
1190
+ }
1191
+ function header(title) {
1192
+ console.log(`
1193
+ ${title}`);
1194
+ console.log("\u2500".repeat(title.length));
1195
+ }
1196
+ function listItem(item, indent = 2) {
1197
+ console.log(`${" ".repeat(indent)}\u2022 ${item}`);
1198
+ }
1199
+ function keyValue(key, value) {
1200
+ console.log(` ${key}: ${value}`);
1201
+ }
1202
+
801
1203
  export {
802
- getTemplatesDirectory,
803
1204
  exists,
804
- ensureDirectory,
805
- readFile,
806
1205
  readFileSafe,
807
- writeFile,
808
- remove,
809
- removeIfEmpty,
810
- makeScriptsExecutable,
811
- readJson,
812
1206
  writeJson,
813
- getBaseEslintPackages,
814
- getConditionalEslintPackages,
1207
+ reconcile,
815
1208
  SAFEWORD_SCHEMA,
816
- detectProjectType
1209
+ isGitRepo,
1210
+ createProjectContext,
1211
+ info,
1212
+ success,
1213
+ warn,
1214
+ error,
1215
+ header,
1216
+ listItem,
1217
+ keyValue
817
1218
  };
818
- //# sourceMappingURL=chunk-CLSGXTOL.js.map
1219
+ //# sourceMappingURL=chunk-POPS3ZRQ.js.map