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.
- package/dist/{check-QMAGWUOA.js → check-4HX4SNVV.js} +6 -8
- package/dist/{check-QMAGWUOA.js.map → check-4HX4SNVV.js.map} +1 -1
- package/dist/{chunk-CLSGXTOL.js → chunk-POPS3ZRQ.js} +479 -78
- package/dist/chunk-POPS3ZRQ.js.map +1 -0
- package/dist/cli.js +5 -9
- package/dist/cli.js.map +1 -1
- package/dist/{diff-2T7UDES7.js → diff-7QIV6Z5B.js} +5 -7
- package/dist/{diff-2T7UDES7.js.map → diff-7QIV6Z5B.js.map} +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/reset-RP7AGR2Y.js +73 -0
- package/dist/reset-RP7AGR2Y.js.map +1 -0
- package/dist/setup-ZYRPDTQI.js +91 -0
- package/dist/setup-ZYRPDTQI.js.map +1 -0
- package/dist/{upgrade-FALAUUKE.js → upgrade-K2FFESUH.js} +31 -43
- package/dist/upgrade-K2FFESUH.js.map +1 -0
- package/package.json +1 -1
- package/templates/SAFEWORD.md +1 -0
- package/templates/guides/cli-reference.md +9 -11
- package/dist/chunk-4URRFBUS.js +0 -88
- package/dist/chunk-4URRFBUS.js.map +0 -1
- package/dist/chunk-CLSGXTOL.js.map +0 -1
- package/dist/chunk-KQ6BLN6W.js +0 -493
- package/dist/chunk-KQ6BLN6W.js.map +0 -1
- package/dist/reset-QRXG7KZZ.js +0 -74
- package/dist/reset-QRXG7KZZ.js.map +0 -1
- package/dist/setup-QUUJ7SH3.js +0 -100
- package/dist/setup-QUUJ7SH3.js.map +0 -1
- package/dist/sync-ISBJ7X2T.js +0 -9
- package/dist/sync-ISBJ7X2T.js.map +0 -1
- package/dist/upgrade-FALAUUKE.js.map +0 -1
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
814
|
-
getConditionalEslintPackages,
|
|
1207
|
+
reconcile,
|
|
815
1208
|
SAFEWORD_SCHEMA,
|
|
816
|
-
|
|
1209
|
+
isGitRepo,
|
|
1210
|
+
createProjectContext,
|
|
1211
|
+
info,
|
|
1212
|
+
success,
|
|
1213
|
+
warn,
|
|
1214
|
+
error,
|
|
1215
|
+
header,
|
|
1216
|
+
listItem,
|
|
1217
|
+
keyValue
|
|
817
1218
|
};
|
|
818
|
-
//# sourceMappingURL=chunk-
|
|
1219
|
+
//# sourceMappingURL=chunk-POPS3ZRQ.js.map
|