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
package/dist/chunk-KQ6BLN6W.js
DELETED
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
detectProjectType,
|
|
3
|
-
ensureDirectory,
|
|
4
|
-
exists,
|
|
5
|
-
getTemplatesDirectory,
|
|
6
|
-
makeScriptsExecutable,
|
|
7
|
-
readFile,
|
|
8
|
-
readFileSafe,
|
|
9
|
-
readJson,
|
|
10
|
-
remove,
|
|
11
|
-
removeIfEmpty,
|
|
12
|
-
writeFile,
|
|
13
|
-
writeJson
|
|
14
|
-
} from "./chunk-CLSGXTOL.js";
|
|
15
|
-
|
|
16
|
-
// src/reconcile.ts
|
|
17
|
-
import nodePath from "path";
|
|
18
|
-
var HUSKY_DIR = ".husky";
|
|
19
|
-
function shouldSkipForNonGit(path, isGitRepo2) {
|
|
20
|
-
return path.startsWith(HUSKY_DIR) && !isGitRepo2;
|
|
21
|
-
}
|
|
22
|
-
function planMissingDirectories(directories, cwd, isGitRepo2) {
|
|
23
|
-
const actions = [];
|
|
24
|
-
const created = [];
|
|
25
|
-
for (const dir of directories) {
|
|
26
|
-
if (shouldSkipForNonGit(dir, isGitRepo2)) continue;
|
|
27
|
-
if (!exists(nodePath.join(cwd, dir))) {
|
|
28
|
-
actions.push({ type: "mkdir", path: dir });
|
|
29
|
-
created.push(dir);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return { actions, created };
|
|
33
|
-
}
|
|
34
|
-
function planTextPatches(patches, cwd, isGitRepo2) {
|
|
35
|
-
const actions = [];
|
|
36
|
-
for (const [filePath, definition] of Object.entries(patches)) {
|
|
37
|
-
if (shouldSkipForNonGit(filePath, isGitRepo2)) continue;
|
|
38
|
-
const content = readFileSafe(nodePath.join(cwd, filePath)) ?? "";
|
|
39
|
-
if (!content.includes(definition.marker)) {
|
|
40
|
-
actions.push({ type: "text-patch", path: filePath, definition });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return actions;
|
|
44
|
-
}
|
|
45
|
-
function planExistingDirectoriesRemoval(directories, cwd) {
|
|
46
|
-
const actions = [];
|
|
47
|
-
const removed = [];
|
|
48
|
-
for (const dir of directories) {
|
|
49
|
-
if (exists(nodePath.join(cwd, dir))) {
|
|
50
|
-
actions.push({ type: "rmdir", path: dir });
|
|
51
|
-
removed.push(dir);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return { actions, removed };
|
|
55
|
-
}
|
|
56
|
-
function planExistingFilesRemoval(files, cwd) {
|
|
57
|
-
const actions = [];
|
|
58
|
-
const removed = [];
|
|
59
|
-
for (const filePath of files) {
|
|
60
|
-
if (exists(nodePath.join(cwd, filePath))) {
|
|
61
|
-
actions.push({ type: "rm", path: filePath });
|
|
62
|
-
removed.push(filePath);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return { actions, removed };
|
|
66
|
-
}
|
|
67
|
-
function getClaudeParentDirectoryForCleanup(filePath) {
|
|
68
|
-
if (!filePath.startsWith(".claude/")) return void 0;
|
|
69
|
-
const parentDirectory = filePath.slice(0, Math.max(0, filePath.lastIndexOf("/")));
|
|
70
|
-
if (!parentDirectory || parentDirectory === ".claude" || parentDirectory === ".claude/skills" || parentDirectory === ".claude/commands") {
|
|
71
|
-
return void 0;
|
|
72
|
-
}
|
|
73
|
-
return parentDirectory;
|
|
74
|
-
}
|
|
75
|
-
async function reconcile(schema, mode, ctx, options) {
|
|
76
|
-
const dryRun = options?.dryRun ?? false;
|
|
77
|
-
const plan = computePlan(schema, mode, ctx);
|
|
78
|
-
if (dryRun) {
|
|
79
|
-
return {
|
|
80
|
-
actions: plan.actions,
|
|
81
|
-
applied: false,
|
|
82
|
-
created: plan.wouldCreate,
|
|
83
|
-
updated: plan.wouldUpdate,
|
|
84
|
-
removed: plan.wouldRemove,
|
|
85
|
-
packagesToInstall: plan.packagesToInstall,
|
|
86
|
-
packagesToRemove: plan.packagesToRemove
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
const result = executePlan(plan, ctx);
|
|
90
|
-
return {
|
|
91
|
-
actions: plan.actions,
|
|
92
|
-
applied: true,
|
|
93
|
-
created: result.created,
|
|
94
|
-
updated: result.updated,
|
|
95
|
-
removed: result.removed,
|
|
96
|
-
packagesToInstall: plan.packagesToInstall,
|
|
97
|
-
packagesToRemove: plan.packagesToRemove
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
function planDeprecatedFilesRemoval(deprecatedFiles, cwd) {
|
|
101
|
-
const actions = [];
|
|
102
|
-
const removed = [];
|
|
103
|
-
for (const filePath of deprecatedFiles) {
|
|
104
|
-
if (exists(nodePath.join(cwd, filePath))) {
|
|
105
|
-
actions.push({ type: "rm", path: filePath });
|
|
106
|
-
removed.push(filePath);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return { actions, removed };
|
|
110
|
-
}
|
|
111
|
-
function computePlan(schema, mode, ctx) {
|
|
112
|
-
switch (mode) {
|
|
113
|
-
case "install": {
|
|
114
|
-
return computeInstallPlan(schema, ctx);
|
|
115
|
-
}
|
|
116
|
-
case "upgrade": {
|
|
117
|
-
return computeUpgradePlan(schema, ctx);
|
|
118
|
-
}
|
|
119
|
-
case "uninstall": {
|
|
120
|
-
return computeUninstallPlan(schema, ctx, false);
|
|
121
|
-
}
|
|
122
|
-
case "uninstall-full": {
|
|
123
|
-
return computeUninstallPlan(schema, ctx, true);
|
|
124
|
-
}
|
|
125
|
-
default: {
|
|
126
|
-
const _exhaustiveCheck = mode;
|
|
127
|
-
return _exhaustiveCheck;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function computeInstallPlan(schema, ctx) {
|
|
132
|
-
const actions = [];
|
|
133
|
-
const wouldCreate = [];
|
|
134
|
-
const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
|
|
135
|
-
const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);
|
|
136
|
-
actions.push(...missingDirectories.actions);
|
|
137
|
-
wouldCreate.push(...missingDirectories.created);
|
|
138
|
-
for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
|
|
139
|
-
if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
|
|
140
|
-
const content = resolveFileContent(definition, ctx);
|
|
141
|
-
actions.push({ type: "write", path: filePath, content });
|
|
142
|
-
wouldCreate.push(filePath);
|
|
143
|
-
}
|
|
144
|
-
for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
|
|
145
|
-
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
146
|
-
if (!exists(fullPath)) {
|
|
147
|
-
const content = resolveFileContent(definition, ctx);
|
|
148
|
-
actions.push({ type: "write", path: filePath, content });
|
|
149
|
-
wouldCreate.push(filePath);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const chmodPaths = [
|
|
153
|
-
".safeword/hooks",
|
|
154
|
-
".safeword/hooks/cursor",
|
|
155
|
-
".safeword/lib",
|
|
156
|
-
".safeword/scripts"
|
|
157
|
-
];
|
|
158
|
-
if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);
|
|
159
|
-
actions.push({ type: "chmod", paths: chmodPaths });
|
|
160
|
-
for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
|
|
161
|
-
actions.push({ type: "json-merge", path: filePath, definition });
|
|
162
|
-
}
|
|
163
|
-
for (const [filePath, definition] of Object.entries(schema.textPatches)) {
|
|
164
|
-
if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
|
|
165
|
-
actions.push({ type: "text-patch", path: filePath, definition });
|
|
166
|
-
if (definition.createIfMissing && !exists(nodePath.join(ctx.cwd, filePath))) {
|
|
167
|
-
wouldCreate.push(filePath);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const packagesToInstall = computePackagesToInstall(
|
|
171
|
-
schema,
|
|
172
|
-
ctx.projectType,
|
|
173
|
-
ctx.developmentDeps,
|
|
174
|
-
ctx.isGitRepo
|
|
175
|
-
);
|
|
176
|
-
return {
|
|
177
|
-
actions,
|
|
178
|
-
wouldCreate,
|
|
179
|
-
wouldUpdate: [],
|
|
180
|
-
wouldRemove: [],
|
|
181
|
-
packagesToInstall,
|
|
182
|
-
packagesToRemove: []
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
function computeUpgradePlan(schema, ctx) {
|
|
186
|
-
const actions = [];
|
|
187
|
-
const wouldCreate = [];
|
|
188
|
-
const wouldUpdate = [];
|
|
189
|
-
const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];
|
|
190
|
-
const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);
|
|
191
|
-
actions.push(...missingDirectories.actions);
|
|
192
|
-
wouldCreate.push(...missingDirectories.created);
|
|
193
|
-
for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
|
|
194
|
-
if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
|
|
195
|
-
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
196
|
-
const newContent = resolveFileContent(definition, ctx);
|
|
197
|
-
if (!fileNeedsUpdate(fullPath, newContent)) continue;
|
|
198
|
-
actions.push({ type: "write", path: filePath, content: newContent });
|
|
199
|
-
if (exists(fullPath)) {
|
|
200
|
-
wouldUpdate.push(filePath);
|
|
201
|
-
} else {
|
|
202
|
-
wouldCreate.push(filePath);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
|
|
206
|
-
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
207
|
-
const newContent = resolveFileContent(definition, ctx);
|
|
208
|
-
if (!exists(fullPath)) {
|
|
209
|
-
actions.push({ type: "write", path: filePath, content: newContent });
|
|
210
|
-
wouldCreate.push(filePath);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const deprecatedFiles = planDeprecatedFilesRemoval(schema.deprecatedFiles, ctx.cwd);
|
|
214
|
-
actions.push(...deprecatedFiles.actions);
|
|
215
|
-
const wouldRemove = deprecatedFiles.removed;
|
|
216
|
-
const deprecatedDirectories = planExistingDirectoriesRemoval(schema.deprecatedDirs, ctx.cwd);
|
|
217
|
-
actions.push(...deprecatedDirectories.actions);
|
|
218
|
-
wouldRemove.push(...deprecatedDirectories.removed);
|
|
219
|
-
const chmodPathsUpgrade = [
|
|
220
|
-
".safeword/hooks",
|
|
221
|
-
".safeword/hooks/cursor",
|
|
222
|
-
".safeword/lib",
|
|
223
|
-
".safeword/scripts"
|
|
224
|
-
];
|
|
225
|
-
actions.push({ type: "chmod", paths: chmodPathsUpgrade });
|
|
226
|
-
for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
|
|
227
|
-
actions.push({ type: "json-merge", path: filePath, definition });
|
|
228
|
-
}
|
|
229
|
-
actions.push(...planTextPatches(schema.textPatches, ctx.cwd, ctx.isGitRepo));
|
|
230
|
-
const packagesToInstall = computePackagesToInstall(
|
|
231
|
-
schema,
|
|
232
|
-
ctx.projectType,
|
|
233
|
-
ctx.developmentDeps,
|
|
234
|
-
ctx.isGitRepo
|
|
235
|
-
);
|
|
236
|
-
const packagesToRemove = schema.deprecatedPackages.filter((pkg) => pkg in ctx.developmentDeps);
|
|
237
|
-
return {
|
|
238
|
-
actions,
|
|
239
|
-
wouldCreate,
|
|
240
|
-
wouldUpdate,
|
|
241
|
-
wouldRemove,
|
|
242
|
-
packagesToInstall,
|
|
243
|
-
packagesToRemove
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
function computeUninstallPlan(schema, ctx, full) {
|
|
247
|
-
const actions = [];
|
|
248
|
-
const wouldRemove = [];
|
|
249
|
-
const ownedFiles = planExistingFilesRemoval(Object.keys(schema.ownedFiles), ctx.cwd);
|
|
250
|
-
actions.push(...ownedFiles.actions);
|
|
251
|
-
wouldRemove.push(...ownedFiles.removed);
|
|
252
|
-
const directoriesToCleanup = /* @__PURE__ */ new Set();
|
|
253
|
-
for (const filePath of ownedFiles.removed) {
|
|
254
|
-
const parentDirectory = getClaudeParentDirectoryForCleanup(filePath);
|
|
255
|
-
if (parentDirectory) directoriesToCleanup.add(parentDirectory);
|
|
256
|
-
}
|
|
257
|
-
const cleanupDirectories = planExistingDirectoriesRemoval([...directoriesToCleanup], ctx.cwd);
|
|
258
|
-
actions.push(...cleanupDirectories.actions);
|
|
259
|
-
wouldRemove.push(...cleanupDirectories.removed);
|
|
260
|
-
for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {
|
|
261
|
-
actions.push({ type: "json-unmerge", path: filePath, definition });
|
|
262
|
-
}
|
|
263
|
-
for (const [filePath, definition] of Object.entries(schema.textPatches)) {
|
|
264
|
-
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
265
|
-
if (exists(fullPath)) {
|
|
266
|
-
const content = readFileSafe(fullPath) ?? "";
|
|
267
|
-
if (content.includes(definition.marker)) {
|
|
268
|
-
actions.push({ type: "text-unpatch", path: filePath, definition });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const preserved = planExistingDirectoriesRemoval(schema.preservedDirs.toReversed(), ctx.cwd);
|
|
273
|
-
actions.push(...preserved.actions);
|
|
274
|
-
wouldRemove.push(...preserved.removed);
|
|
275
|
-
const owned = planExistingDirectoriesRemoval(schema.ownedDirs.toReversed(), ctx.cwd);
|
|
276
|
-
actions.push(...owned.actions);
|
|
277
|
-
wouldRemove.push(...owned.removed);
|
|
278
|
-
if (full) {
|
|
279
|
-
const managed = planExistingFilesRemoval(Object.keys(schema.managedFiles), ctx.cwd);
|
|
280
|
-
actions.push(...managed.actions);
|
|
281
|
-
wouldRemove.push(...managed.removed);
|
|
282
|
-
}
|
|
283
|
-
const packagesToRemove = full ? computePackagesToRemove(schema, ctx.projectType, ctx.developmentDeps) : [];
|
|
284
|
-
return {
|
|
285
|
-
actions,
|
|
286
|
-
wouldCreate: [],
|
|
287
|
-
wouldUpdate: [],
|
|
288
|
-
wouldRemove,
|
|
289
|
-
packagesToInstall: [],
|
|
290
|
-
packagesToRemove
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
function executePlan(plan, ctx) {
|
|
294
|
-
const created = [];
|
|
295
|
-
const updated = [];
|
|
296
|
-
const removed = [];
|
|
297
|
-
const result = { created, updated, removed };
|
|
298
|
-
for (const action of plan.actions) {
|
|
299
|
-
executeAction(action, ctx, result);
|
|
300
|
-
}
|
|
301
|
-
return result;
|
|
302
|
-
}
|
|
303
|
-
function executeAction(action, ctx, result) {
|
|
304
|
-
switch (action.type) {
|
|
305
|
-
case "mkdir": {
|
|
306
|
-
ensureDirectory(nodePath.join(ctx.cwd, action.path));
|
|
307
|
-
result.created.push(action.path);
|
|
308
|
-
break;
|
|
309
|
-
}
|
|
310
|
-
case "rmdir": {
|
|
311
|
-
if (removeIfEmpty(nodePath.join(ctx.cwd, action.path))) {
|
|
312
|
-
result.removed.push(action.path);
|
|
313
|
-
}
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
case "write": {
|
|
317
|
-
executeWrite(ctx.cwd, action.path, action.content, result);
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
case "rm": {
|
|
321
|
-
remove(nodePath.join(ctx.cwd, action.path));
|
|
322
|
-
result.removed.push(action.path);
|
|
323
|
-
break;
|
|
324
|
-
}
|
|
325
|
-
case "chmod": {
|
|
326
|
-
for (const path of action.paths) {
|
|
327
|
-
const fullPath = nodePath.join(ctx.cwd, path);
|
|
328
|
-
if (exists(fullPath)) makeScriptsExecutable(fullPath);
|
|
329
|
-
}
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
case "json-merge": {
|
|
333
|
-
executeJsonMerge(ctx.cwd, action.path, action.definition, ctx);
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
case "json-unmerge": {
|
|
337
|
-
executeJsonUnmerge(ctx.cwd, action.path, action.definition);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
case "text-patch": {
|
|
341
|
-
executeTextPatch(ctx.cwd, action.path, action.definition);
|
|
342
|
-
break;
|
|
343
|
-
}
|
|
344
|
-
case "text-unpatch": {
|
|
345
|
-
executeTextUnpatch(ctx.cwd, action.path, action.definition);
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
function executeWrite(cwd, path, content, result) {
|
|
351
|
-
const fullPath = nodePath.join(cwd, path);
|
|
352
|
-
const existed = exists(fullPath);
|
|
353
|
-
writeFile(fullPath, content);
|
|
354
|
-
(existed ? result.updated : result.created).push(path);
|
|
355
|
-
}
|
|
356
|
-
function resolveFileContent(definition, ctx) {
|
|
357
|
-
if (definition.template) {
|
|
358
|
-
const templatesDirectory = getTemplatesDirectory();
|
|
359
|
-
return readFile(nodePath.join(templatesDirectory, definition.template));
|
|
360
|
-
}
|
|
361
|
-
if (definition.content) {
|
|
362
|
-
return typeof definition.content === "function" ? definition.content() : definition.content;
|
|
363
|
-
}
|
|
364
|
-
if (definition.generator) {
|
|
365
|
-
return definition.generator(ctx);
|
|
366
|
-
}
|
|
367
|
-
throw new Error("FileDefinition must have template, content, or generator");
|
|
368
|
-
}
|
|
369
|
-
function fileNeedsUpdate(installedPath, newContent) {
|
|
370
|
-
if (!exists(installedPath)) return true;
|
|
371
|
-
const currentContent = readFileSafe(installedPath);
|
|
372
|
-
return currentContent?.trim() !== newContent.trim();
|
|
373
|
-
}
|
|
374
|
-
var GIT_ONLY_PACKAGES = /* @__PURE__ */ new Set(["husky", "lint-staged"]);
|
|
375
|
-
function computePackagesToInstall(schema, projectType, installedDevelopmentDeps, isGitRepo2 = true) {
|
|
376
|
-
let needed = [...schema.packages.base];
|
|
377
|
-
if (!isGitRepo2) {
|
|
378
|
-
needed = needed.filter((pkg) => !GIT_ONLY_PACKAGES.has(pkg));
|
|
379
|
-
}
|
|
380
|
-
for (const [key, deps] of Object.entries(schema.packages.conditional)) {
|
|
381
|
-
if (projectType[key]) {
|
|
382
|
-
needed.push(...deps);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return needed.filter((pkg) => !(pkg in installedDevelopmentDeps));
|
|
386
|
-
}
|
|
387
|
-
function computePackagesToRemove(schema, projectType, installedDevelopmentDeps) {
|
|
388
|
-
const safewordPackages = [...schema.packages.base];
|
|
389
|
-
for (const [key, deps] of Object.entries(schema.packages.conditional)) {
|
|
390
|
-
if (projectType[key]) {
|
|
391
|
-
safewordPackages.push(...deps);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return safewordPackages.filter((pkg) => pkg in installedDevelopmentDeps);
|
|
395
|
-
}
|
|
396
|
-
function executeJsonMerge(cwd, path, definition, ctx) {
|
|
397
|
-
const fullPath = nodePath.join(cwd, path);
|
|
398
|
-
const existing = readJson(fullPath) ?? {};
|
|
399
|
-
const merged = definition.merge(existing, ctx);
|
|
400
|
-
if (JSON.stringify(existing) === JSON.stringify(merged)) return;
|
|
401
|
-
writeJson(fullPath, merged);
|
|
402
|
-
}
|
|
403
|
-
function executeJsonUnmerge(cwd, path, definition) {
|
|
404
|
-
const fullPath = nodePath.join(cwd, path);
|
|
405
|
-
if (!exists(fullPath)) return;
|
|
406
|
-
const existing = readJson(fullPath);
|
|
407
|
-
if (!existing) return;
|
|
408
|
-
const unmerged = definition.unmerge(existing);
|
|
409
|
-
if (definition.removeFileIfEmpty) {
|
|
410
|
-
const remainingKeys = Object.keys(unmerged).filter((k) => unmerged[k] !== void 0);
|
|
411
|
-
if (remainingKeys.length === 0) {
|
|
412
|
-
remove(fullPath);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
writeJson(fullPath, unmerged);
|
|
417
|
-
}
|
|
418
|
-
function executeTextPatch(cwd, path, definition) {
|
|
419
|
-
const fullPath = nodePath.join(cwd, path);
|
|
420
|
-
let content = readFileSafe(fullPath) ?? "";
|
|
421
|
-
if (content.includes(definition.marker)) return;
|
|
422
|
-
content = definition.operation === "prepend" ? definition.content + content : content + definition.content;
|
|
423
|
-
writeFile(fullPath, content);
|
|
424
|
-
}
|
|
425
|
-
function executeTextUnpatch(cwd, path, definition) {
|
|
426
|
-
const fullPath = nodePath.join(cwd, path);
|
|
427
|
-
const content = readFileSafe(fullPath);
|
|
428
|
-
if (!content) return;
|
|
429
|
-
let unpatched = content.replace(definition.content, "");
|
|
430
|
-
if (unpatched === content && content.includes(definition.marker)) {
|
|
431
|
-
const lines = content.split("\n");
|
|
432
|
-
const filtered = lines.filter((line) => !line.includes(definition.marker));
|
|
433
|
-
unpatched = filtered.join("\n").replace(/^\n+/, "");
|
|
434
|
-
}
|
|
435
|
-
writeFile(fullPath, unpatched);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// src/utils/git.ts
|
|
439
|
-
import nodePath2 from "path";
|
|
440
|
-
function isGitRepo(cwd) {
|
|
441
|
-
return exists(nodePath2.join(cwd, ".git"));
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// src/utils/context.ts
|
|
445
|
-
import nodePath3 from "path";
|
|
446
|
-
function createProjectContext(cwd) {
|
|
447
|
-
const packageJson = readJson(nodePath3.join(cwd, "package.json"));
|
|
448
|
-
return {
|
|
449
|
-
cwd,
|
|
450
|
-
projectType: detectProjectType(packageJson ?? {}, cwd),
|
|
451
|
-
developmentDeps: packageJson?.devDependencies ?? {},
|
|
452
|
-
isGitRepo: isGitRepo(cwd)
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// src/utils/output.ts
|
|
457
|
-
function info(message) {
|
|
458
|
-
console.log(message);
|
|
459
|
-
}
|
|
460
|
-
function success(message) {
|
|
461
|
-
console.log(`\u2713 ${message}`);
|
|
462
|
-
}
|
|
463
|
-
function warn(message) {
|
|
464
|
-
console.warn(`\u26A0 ${message}`);
|
|
465
|
-
}
|
|
466
|
-
function error(message) {
|
|
467
|
-
console.error(`\u2717 ${message}`);
|
|
468
|
-
}
|
|
469
|
-
function header(title) {
|
|
470
|
-
console.log(`
|
|
471
|
-
${title}`);
|
|
472
|
-
console.log("\u2500".repeat(title.length));
|
|
473
|
-
}
|
|
474
|
-
function listItem(item, indent = 2) {
|
|
475
|
-
console.log(`${" ".repeat(indent)}\u2022 ${item}`);
|
|
476
|
-
}
|
|
477
|
-
function keyValue(key, value) {
|
|
478
|
-
console.log(` ${key}: ${value}`);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
export {
|
|
482
|
-
reconcile,
|
|
483
|
-
isGitRepo,
|
|
484
|
-
createProjectContext,
|
|
485
|
-
info,
|
|
486
|
-
success,
|
|
487
|
-
warn,
|
|
488
|
-
error,
|
|
489
|
-
header,
|
|
490
|
-
listItem,
|
|
491
|
-
keyValue
|
|
492
|
-
};
|
|
493
|
-
//# sourceMappingURL=chunk-KQ6BLN6W.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reconcile.ts","../src/utils/git.ts","../src/utils/context.ts","../src/utils/output.ts"],"sourcesContent":["/**\n * Reconciliation Engine\n *\n * Computes and executes plans based on SAFEWORD_SCHEMA and project state.\n * This is the single source of truth for all file/dir/config operations.\n */\n\nimport nodePath from 'node:path';\n\nimport type {\n FileDefinition,\n JsonMergeDefinition,\n ProjectContext,\n SafewordSchema,\n TextPatchDefinition,\n} from './schema.js';\nimport {\n ensureDirectory,\n exists,\n getTemplatesDirectory,\n makeScriptsExecutable,\n readFile,\n readFileSafe,\n readJson,\n remove,\n removeIfEmpty,\n writeFile,\n writeJson,\n} from './utils/fs.js';\nimport type { ProjectType } from './utils/project-detector.js';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst HUSKY_DIR = '.husky';\n\n/**\n * Check if path should be skipped in non-git repos (husky files)\n * @param path\n * @param isGitRepo\n */\nfunction shouldSkipForNonGit(path: string, isGitRepo: boolean): boolean {\n return path.startsWith(HUSKY_DIR) && !isGitRepo;\n}\n\n/**\n * Plan mkdir actions for directories that don't exist\n * @param dirs\n * @param cwd\n * @param isGitRepo\n */\nfunction planMissingDirectories(\n directories: string[],\n cwd: string,\n isGitRepo: boolean,\n): { actions: Action[]; created: string[] } {\n const actions: Action[] = [];\n const created: string[] = [];\n for (const dir of directories) {\n if (shouldSkipForNonGit(dir, isGitRepo)) continue;\n if (!exists(nodePath.join(cwd, dir))) {\n actions.push({ type: 'mkdir', path: dir });\n created.push(dir);\n }\n }\n return { actions, created };\n}\n\n/**\n * Plan text-patch actions for files missing the marker\n * @param patches\n * @param cwd\n * @param isGitRepo\n */\nfunction planTextPatches(\n patches: Record<string, TextPatchDefinition>,\n cwd: string,\n isGitRepo: boolean,\n): Action[] {\n const actions: Action[] = [];\n for (const [filePath, definition] of Object.entries(patches)) {\n if (shouldSkipForNonGit(filePath, isGitRepo)) continue;\n const content = readFileSafe(nodePath.join(cwd, filePath)) ?? '';\n if (!content.includes(definition.marker)) {\n actions.push({ type: 'text-patch', path: filePath, definition });\n }\n }\n return actions;\n}\n\n/**\n * Plan rmdir actions for directories that exist\n * @param dirs\n * @param cwd\n */\nfunction planExistingDirectoriesRemoval(\n directories: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const dir of directories) {\n if (exists(nodePath.join(cwd, dir))) {\n actions.push({ type: 'rmdir', path: dir });\n removed.push(dir);\n }\n }\n return { actions, removed };\n}\n\n/**\n * Plan rm actions for files that exist\n * @param files\n * @param cwd\n */\nfunction planExistingFilesRemoval(\n files: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const filePath of files) {\n if (exists(nodePath.join(cwd, filePath))) {\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n }\n return { actions, removed };\n}\n\n/**\n * Check if a .claude path needs parent dir cleanup\n * @param filePath\n */\nfunction getClaudeParentDirectoryForCleanup(filePath: string): string | undefined {\n if (!filePath.startsWith('.claude/')) return undefined;\n const parentDirectory = filePath.slice(0, Math.max(0, filePath.lastIndexOf('/')));\n if (\n !parentDirectory ||\n parentDirectory === '.claude' ||\n parentDirectory === '.claude/skills' ||\n parentDirectory === '.claude/commands'\n ) {\n return undefined;\n }\n return parentDirectory;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ReconcileMode = 'install' | 'upgrade' | 'uninstall' | 'uninstall-full';\n\nexport type Action =\n | { type: 'mkdir'; path: string }\n | { type: 'rmdir'; path: string }\n | { type: 'write'; path: string; content: string }\n | { type: 'rm'; path: string }\n | { type: 'chmod'; paths: string[] }\n | { type: 'json-merge'; path: string; definition: JsonMergeDefinition }\n | { type: 'json-unmerge'; path: string; definition: JsonMergeDefinition }\n | { type: 'text-patch'; path: string; definition: TextPatchDefinition }\n | { type: 'text-unpatch'; path: string; definition: TextPatchDefinition };\n\nexport interface ReconcileResult {\n actions: Action[];\n applied: boolean;\n created: string[];\n updated: string[];\n removed: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\nexport interface ReconcileOptions {\n dryRun?: boolean;\n}\n\n// ============================================================================\n// Main reconcile function\n// ============================================================================\n\n/**\n *\n * @param schema\n * @param mode\n * @param ctx\n * @param options\n */\nexport async function reconcile(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n options?: ReconcileOptions,\n): Promise<ReconcileResult> {\n const dryRun = options?.dryRun ?? false;\n\n const plan = computePlan(schema, mode, ctx);\n\n if (dryRun) {\n return {\n actions: plan.actions,\n applied: false,\n created: plan.wouldCreate,\n updated: plan.wouldUpdate,\n removed: plan.wouldRemove,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n };\n }\n\n const result = executePlan(plan, ctx);\n\n return {\n actions: plan.actions,\n applied: true,\n created: result.created,\n updated: result.updated,\n removed: result.removed,\n packagesToInstall: plan.packagesToInstall,\n packagesToRemove: plan.packagesToRemove,\n };\n}\n\n// ============================================================================\n// Plan computation\n// ============================================================================\n\ninterface ReconcilePlan {\n actions: Action[];\n wouldCreate: string[];\n wouldUpdate: string[];\n wouldRemove: string[];\n packagesToInstall: string[];\n packagesToRemove: string[];\n}\n\n/**\n *\n * @param deprecatedFiles\n * @param cwd\n */\nfunction planDeprecatedFilesRemoval(\n deprecatedFiles: string[],\n cwd: string,\n): { actions: Action[]; removed: string[] } {\n const actions: Action[] = [];\n const removed: string[] = [];\n for (const filePath of deprecatedFiles) {\n if (exists(nodePath.join(cwd, filePath))) {\n actions.push({ type: 'rm', path: filePath });\n removed.push(filePath);\n }\n }\n return { actions, removed };\n}\n\n/**\n *\n * @param schema\n * @param mode\n * @param ctx\n */\nfunction computePlan(\n schema: SafewordSchema,\n mode: ReconcileMode,\n ctx: ProjectContext,\n): ReconcilePlan {\n switch (mode) {\n case 'install': {\n return computeInstallPlan(schema, ctx);\n }\n case 'upgrade': {\n return computeUpgradePlan(schema, ctx);\n }\n case 'uninstall': {\n return computeUninstallPlan(schema, ctx, false);\n }\n case 'uninstall-full': {\n return computeUninstallPlan(schema, ctx, true);\n }\n default: {\n // Exhaustive check - TypeScript ensures all cases are handled\n const _exhaustiveCheck: never = mode;\n return _exhaustiveCheck;\n }\n }\n}\n\n/**\n *\n * @param schema\n * @param ctx\n */\nfunction computeInstallPlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n\n // 1. Create all directories (skip .husky if not a git repo)\n const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);\n actions.push(...missingDirectories.actions);\n wouldCreate.push(...missingDirectories.created);\n\n // 2. Write all owned files (skip .husky files if not a git repo)\n for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n\n const content = resolveFileContent(definition, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n\n // 3. Write managed files (only if missing)\n for (const [filePath, definition] of Object.entries(schema.managedFiles)) {\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (!exists(fullPath)) {\n const content = resolveFileContent(definition, ctx);\n actions.push({ type: 'write', path: filePath, content });\n wouldCreate.push(filePath);\n }\n }\n\n // 4. chmod hook/lib/scripts directories (only .husky if git repo)\n const chmodPaths = [\n '.safeword/hooks',\n '.safeword/hooks/cursor',\n '.safeword/lib',\n '.safeword/scripts',\n ];\n if (ctx.isGitRepo) chmodPaths.push(HUSKY_DIR);\n actions.push({ type: 'chmod', paths: chmodPaths });\n\n // 5. JSON merges\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition });\n }\n\n // 6. Text patches (skip .husky files in non-git repos)\n for (const [filePath, definition] of Object.entries(schema.textPatches)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n actions.push({ type: 'text-patch', path: filePath, definition });\n if (definition.createIfMissing && !exists(nodePath.join(ctx.cwd, filePath))) {\n wouldCreate.push(filePath);\n }\n }\n\n // 7. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.developmentDeps,\n ctx.isGitRepo,\n );\n\n return {\n actions,\n wouldCreate,\n wouldUpdate: [],\n wouldRemove: [],\n packagesToInstall,\n packagesToRemove: [],\n };\n}\n\n/**\n *\n * @param schema\n * @param ctx\n */\nfunction computeUpgradePlan(schema: SafewordSchema, ctx: ProjectContext): ReconcilePlan {\n const actions: Action[] = [];\n const wouldCreate: string[] = [];\n const wouldUpdate: string[] = [];\n\n // 1. Ensure directories exist (skip .husky if not a git repo)\n const allDirectories = [...schema.ownedDirs, ...schema.sharedDirs, ...schema.preservedDirs];\n const missingDirectories = planMissingDirectories(allDirectories, ctx.cwd, ctx.isGitRepo);\n actions.push(...missingDirectories.actions);\n wouldCreate.push(...missingDirectories.created);\n\n // 2. Update owned files if content changed (skip .husky files if not a git repo)\n for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {\n if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;\n\n const fullPath = nodePath.join(ctx.cwd, filePath);\n const newContent = resolveFileContent(definition, ctx);\n\n if (!fileNeedsUpdate(fullPath, newContent)) continue;\n\n actions.push({ type: 'write', path: filePath, content: newContent });\n if (exists(fullPath)) {\n wouldUpdate.push(filePath);\n } else {\n wouldCreate.push(filePath);\n }\n }\n\n // 3. Update managed files only if content matches current template\n for (const [filePath, definition] of Object.entries(schema.managedFiles)) {\n const fullPath = nodePath.join(ctx.cwd, filePath);\n const newContent = resolveFileContent(definition, ctx);\n\n if (!exists(fullPath)) {\n // Missing - create it\n actions.push({ type: 'write', path: filePath, content: newContent });\n wouldCreate.push(filePath);\n }\n // If file exists, don't update during upgrade - user may have customized it\n }\n\n // 4. Remove deprecated files (renamed or removed in newer versions)\n const deprecatedFiles = planDeprecatedFilesRemoval(schema.deprecatedFiles, ctx.cwd);\n actions.push(...deprecatedFiles.actions);\n const wouldRemove = deprecatedFiles.removed;\n\n // 4b. Remove deprecated directories (no longer managed by safeword)\n const deprecatedDirectories = planExistingDirectoriesRemoval(schema.deprecatedDirs, ctx.cwd);\n actions.push(...deprecatedDirectories.actions);\n wouldRemove.push(...deprecatedDirectories.removed);\n\n // 5. chmod\n const chmodPathsUpgrade = [\n '.safeword/hooks',\n '.safeword/hooks/cursor',\n '.safeword/lib',\n '.safeword/scripts',\n ];\n actions.push({ type: 'chmod', paths: chmodPathsUpgrade });\n\n // 6. JSON merges (always apply to ensure keys are present)\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-merge', path: filePath, definition });\n }\n\n // 7. Text patches (only if marker missing, skip .husky in non-git repos)\n actions.push(...planTextPatches(schema.textPatches, ctx.cwd, ctx.isGitRepo));\n\n // 8. Compute packages to install (husky/lint-staged skipped if no git repo)\n const packagesToInstall = computePackagesToInstall(\n schema,\n ctx.projectType,\n ctx.developmentDeps,\n ctx.isGitRepo,\n );\n\n // 9. Compute deprecated packages to remove (only those actually installed)\n const packagesToRemove = schema.deprecatedPackages.filter(pkg => pkg in ctx.developmentDeps);\n\n return {\n actions,\n wouldCreate,\n wouldUpdate,\n wouldRemove,\n packagesToInstall,\n packagesToRemove,\n };\n}\n\n/**\n *\n * @param schema\n * @param ctx\n * @param full\n */\nfunction computeUninstallPlan(\n schema: SafewordSchema,\n ctx: ProjectContext,\n full: boolean,\n): ReconcilePlan {\n const actions: Action[] = [];\n const wouldRemove: string[] = [];\n\n // 1. Remove all owned files and track parent dirs for cleanup\n const ownedFiles = planExistingFilesRemoval(Object.keys(schema.ownedFiles), ctx.cwd);\n actions.push(...ownedFiles.actions);\n wouldRemove.push(...ownedFiles.removed);\n\n // Collect parent dirs that need cleanup (for .claude/* skill dirs)\n const directoriesToCleanup = new Set<string>();\n for (const filePath of ownedFiles.removed) {\n const parentDirectory = getClaudeParentDirectoryForCleanup(filePath);\n if (parentDirectory) directoriesToCleanup.add(parentDirectory);\n }\n const cleanupDirectories = planExistingDirectoriesRemoval([...directoriesToCleanup], ctx.cwd);\n actions.push(...cleanupDirectories.actions);\n wouldRemove.push(...cleanupDirectories.removed);\n\n // 2. JSON unmerges\n for (const [filePath, definition] of Object.entries(schema.jsonMerges)) {\n actions.push({ type: 'json-unmerge', path: filePath, definition });\n }\n\n // 3. Text unpatches\n for (const [filePath, definition] of Object.entries(schema.textPatches)) {\n const fullPath = nodePath.join(ctx.cwd, filePath);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (content.includes(definition.marker)) {\n actions.push({ type: 'text-unpatch', path: filePath, definition });\n }\n }\n }\n\n // 4. Remove preserved directories first (reverse order, only if empty)\n const preserved = planExistingDirectoriesRemoval(schema.preservedDirs.toReversed(), ctx.cwd);\n actions.push(...preserved.actions);\n wouldRemove.push(...preserved.removed);\n\n // 5. Remove owned directories (reverse order ensures children before parents)\n const owned = planExistingDirectoriesRemoval(schema.ownedDirs.toReversed(), ctx.cwd);\n actions.push(...owned.actions);\n wouldRemove.push(...owned.removed);\n\n // 6. Full uninstall: remove managed files\n if (full) {\n const managed = planExistingFilesRemoval(Object.keys(schema.managedFiles), ctx.cwd);\n actions.push(...managed.actions);\n wouldRemove.push(...managed.removed);\n }\n\n // 7. Compute packages to remove (full only)\n const packagesToRemove = full\n ? computePackagesToRemove(schema, ctx.projectType, ctx.developmentDeps)\n : [];\n\n return {\n actions,\n wouldCreate: [],\n wouldUpdate: [],\n wouldRemove,\n packagesToInstall: [],\n packagesToRemove,\n };\n}\n\n// ============================================================================\n// Plan execution\n// ============================================================================\n\ninterface ExecutionResult {\n created: string[];\n updated: string[];\n removed: string[];\n}\n\n/**\n *\n * @param plan\n * @param ctx\n */\nfunction executePlan(plan: ReconcilePlan, ctx: ProjectContext): ExecutionResult {\n const created: string[] = [];\n const updated: string[] = [];\n const removed: string[] = [];\n const result = { created, updated, removed };\n\n for (const action of plan.actions) {\n executeAction(action, ctx, result);\n }\n\n return result;\n}\n\n/**\n *\n * @param action\n * @param ctx\n * @param result\n */\nfunction executeAction(action: Action, ctx: ProjectContext, result: ExecutionResult): void {\n switch (action.type) {\n case 'mkdir': {\n ensureDirectory(nodePath.join(ctx.cwd, action.path));\n result.created.push(action.path);\n break;\n }\n\n case 'rmdir': {\n // Use removeIfEmpty to preserve directories with user content\n if (removeIfEmpty(nodePath.join(ctx.cwd, action.path))) {\n result.removed.push(action.path);\n }\n break;\n }\n\n case 'write': {\n executeWrite(ctx.cwd, action.path, action.content, result);\n break;\n }\n\n case 'rm': {\n remove(nodePath.join(ctx.cwd, action.path));\n result.removed.push(action.path);\n break;\n }\n\n case 'chmod': {\n for (const path of action.paths) {\n const fullPath = nodePath.join(ctx.cwd, path);\n if (exists(fullPath)) makeScriptsExecutable(fullPath);\n }\n break;\n }\n\n case 'json-merge': {\n executeJsonMerge(ctx.cwd, action.path, action.definition, ctx);\n break;\n }\n\n case 'json-unmerge': {\n executeJsonUnmerge(ctx.cwd, action.path, action.definition);\n break;\n }\n\n case 'text-patch': {\n executeTextPatch(ctx.cwd, action.path, action.definition);\n break;\n }\n\n case 'text-unpatch': {\n executeTextUnpatch(ctx.cwd, action.path, action.definition);\n break;\n }\n }\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param content\n * @param result\n */\nfunction executeWrite(cwd: string, path: string, content: string, result: ExecutionResult): void {\n const fullPath = nodePath.join(cwd, path);\n const existed = exists(fullPath);\n writeFile(fullPath, content);\n (existed ? result.updated : result.created).push(path);\n}\n\n// ============================================================================\n// Helper functions\n// ============================================================================\n\n/**\n *\n * @param definition\n * @param ctx\n */\nfunction resolveFileContent(definition: FileDefinition, ctx: ProjectContext): string {\n if (definition.template) {\n const templatesDirectory = getTemplatesDirectory();\n return readFile(nodePath.join(templatesDirectory, definition.template));\n }\n\n if (definition.content) {\n return typeof definition.content === 'function' ? definition.content() : definition.content;\n }\n\n if (definition.generator) {\n return definition.generator(ctx);\n }\n\n throw new Error('FileDefinition must have template, content, or generator');\n}\n\n/**\n *\n * @param installedPath\n * @param newContent\n */\nfunction fileNeedsUpdate(installedPath: string, newContent: string): boolean {\n if (!exists(installedPath)) return true;\n const currentContent = readFileSafe(installedPath);\n return currentContent?.trim() !== newContent.trim();\n}\n\n// Packages that require git repo\nconst GIT_ONLY_PACKAGES = new Set(['husky', 'lint-staged']);\n\n/**\n *\n * @param schema\n * @param projectType\n * @param installedDevDeps\n * @param isGitRepo\n */\nexport function computePackagesToInstall(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevelopmentDeps: Record<string, string>,\n isGitRepo = true,\n): string[] {\n let needed = [...schema.packages.base];\n\n // Filter out git-only packages when not in a git repo\n if (!isGitRepo) {\n needed = needed.filter(pkg => !GIT_ONLY_PACKAGES.has(pkg));\n }\n\n for (const [key, deps] of Object.entries(schema.packages.conditional)) {\n if (projectType[key as keyof ProjectType]) {\n needed.push(...deps);\n }\n }\n\n return needed.filter(pkg => !(pkg in installedDevelopmentDeps));\n}\n\n/**\n *\n * @param schema\n * @param projectType\n * @param installedDevDeps\n */\nfunction computePackagesToRemove(\n schema: SafewordSchema,\n projectType: ProjectType,\n installedDevelopmentDeps: Record<string, string>,\n): string[] {\n const safewordPackages = [...schema.packages.base];\n\n for (const [key, deps] of Object.entries(schema.packages.conditional)) {\n if (projectType[key as keyof ProjectType]) {\n safewordPackages.push(...deps);\n }\n }\n\n // Only remove packages that are actually installed\n return safewordPackages.filter(pkg => pkg in installedDevelopmentDeps);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n * @param ctx\n */\nfunction executeJsonMerge(\n cwd: string,\n path: string,\n definition: JsonMergeDefinition,\n ctx: ProjectContext,\n): void {\n const fullPath = nodePath.join(cwd, path);\n const existing = readJson<Record<string, unknown>>(fullPath) ?? {};\n const merged = definition.merge(existing, ctx);\n\n // Skip write if content is unchanged (avoids formatting churn)\n if (JSON.stringify(existing) === JSON.stringify(merged)) return;\n\n writeJson(fullPath, merged);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n */\nfunction executeJsonUnmerge(cwd: string, path: string, definition: JsonMergeDefinition): void {\n const fullPath = nodePath.join(cwd, path);\n if (!exists(fullPath)) return;\n\n const existing = readJson<Record<string, unknown>>(fullPath);\n if (!existing) return;\n\n const unmerged = definition.unmerge(existing);\n\n // Check if file should be removed\n if (definition.removeFileIfEmpty) {\n const remainingKeys = Object.keys(unmerged).filter(k => unmerged[k] !== undefined);\n if (remainingKeys.length === 0) {\n remove(fullPath);\n return;\n }\n }\n\n writeJson(fullPath, unmerged);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n */\nfunction executeTextPatch(cwd: string, path: string, definition: TextPatchDefinition): void {\n const fullPath = nodePath.join(cwd, path);\n let content = readFileSafe(fullPath) ?? '';\n\n // Check if already patched\n if (content.includes(definition.marker)) return;\n\n // Apply patch\n content =\n definition.operation === 'prepend'\n ? definition.content + content\n : content + definition.content;\n\n writeFile(fullPath, content);\n}\n\n/**\n *\n * @param cwd\n * @param path\n * @param definition\n */\nfunction executeTextUnpatch(cwd: string, path: string, definition: TextPatchDefinition): void {\n const fullPath = nodePath.join(cwd, path);\n const content = readFileSafe(fullPath);\n if (!content) return;\n\n // Remove the patched content\n // First try to remove the full content block\n let unpatched = content.replace(definition.content, '');\n\n // If full content wasn't found but marker exists, remove lines containing the marker\n if (unpatched === content && content.includes(definition.marker)) {\n // Remove lines containing the marker\n const lines = content.split('\\n');\n const filtered = lines.filter(line => !line.includes(definition.marker));\n unpatched = filtered.join('\\n').replace(/^\\n+/, ''); // Remove leading empty lines\n }\n\n writeFile(fullPath, unpatched);\n}\n","/**\n * Git utilities for CLI operations\n */\n\nimport nodePath from 'node:path';\n\nimport { exists } from './fs.js';\n\n/**\n * Check if directory is a git repository\n * @param cwd\n */\nexport function isGitRepo(cwd: string): boolean {\n return exists(nodePath.join(cwd, '.git'));\n}\n","/**\n * Project Context Utilities\n *\n * Shared helpers for creating ProjectContext objects used by reconcile().\n */\n\nimport nodePath from 'node:path';\n\nimport type { ProjectContext } from '../schema.js';\nimport { readJson } from './fs.js';\nimport { isGitRepo } from './git.js';\nimport { detectProjectType, type PackageJson } from './project-detector.js';\n\n/**\n * Create a ProjectContext from the current working directory.\n *\n * Reads package.json and detects project type for use with reconcile().\n * @param cwd\n */\nexport function createProjectContext(cwd: string): ProjectContext {\n const packageJson = readJson<PackageJson>(nodePath.join(cwd, 'package.json'));\n\n return {\n cwd,\n projectType: detectProjectType(packageJson ?? {}, cwd),\n developmentDeps: packageJson?.devDependencies ?? {},\n isGitRepo: isGitRepo(cwd),\n };\n}\n","/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n * @param message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n * @param message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n * @param message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n * @param message\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log('');\n}\n\n/**\n * Print a section header\n * @param title\n */\nexport function header(title: string): void {\n console.log(`\\n${title}`);\n console.log('─'.repeat(title.length));\n}\n\n/**\n * Print a list item\n * @param item\n * @param indent\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n * @param key\n * @param value\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n"],"mappings":"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","names":["isGitRepo","nodePath","nodePath","nodePath","nodePath"]}
|
package/dist/reset-QRXG7KZZ.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createProjectContext,
|
|
3
|
-
error,
|
|
4
|
-
header,
|
|
5
|
-
info,
|
|
6
|
-
listItem,
|
|
7
|
-
reconcile,
|
|
8
|
-
success,
|
|
9
|
-
warn
|
|
10
|
-
} from "./chunk-KQ6BLN6W.js";
|
|
11
|
-
import {
|
|
12
|
-
SAFEWORD_SCHEMA,
|
|
13
|
-
exists
|
|
14
|
-
} from "./chunk-CLSGXTOL.js";
|
|
15
|
-
import "./chunk-ORQHKDT2.js";
|
|
16
|
-
|
|
17
|
-
// src/commands/reset.ts
|
|
18
|
-
import { execSync } from "child_process";
|
|
19
|
-
import nodePath from "path";
|
|
20
|
-
async function reset(options) {
|
|
21
|
-
const cwd = process.cwd();
|
|
22
|
-
const safewordDirectory = nodePath.join(cwd, ".safeword");
|
|
23
|
-
if (!exists(safewordDirectory)) {
|
|
24
|
-
info("Nothing to remove. Project is not configured with safeword.");
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
const fullReset = options.full ?? false;
|
|
28
|
-
header("Safeword Reset");
|
|
29
|
-
if (fullReset) {
|
|
30
|
-
info("Performing full reset (including linting configuration)...");
|
|
31
|
-
} else {
|
|
32
|
-
info("Removing safeword configuration...");
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const mode = fullReset ? "uninstall-full" : "uninstall";
|
|
36
|
-
const ctx = createProjectContext(cwd);
|
|
37
|
-
const result = await reconcile(SAFEWORD_SCHEMA, mode, ctx);
|
|
38
|
-
if (fullReset && result.packagesToRemove.length > 0) {
|
|
39
|
-
info("\nUninstalling devDependencies...");
|
|
40
|
-
try {
|
|
41
|
-
const uninstallCmd = `npm uninstall ${result.packagesToRemove.join(" ")}`;
|
|
42
|
-
info(`Running: ${uninstallCmd}`);
|
|
43
|
-
execSync(uninstallCmd, { cwd, stdio: "inherit" });
|
|
44
|
-
success("Uninstalled safeword devDependencies");
|
|
45
|
-
} catch {
|
|
46
|
-
warn("Failed to uninstall some packages. Run manually:");
|
|
47
|
-
listItem(`npm uninstall ${result.packagesToRemove.join(" ")}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
header("Reset Complete");
|
|
51
|
-
if (result.removed.length > 0) {
|
|
52
|
-
info("\nRemoved:");
|
|
53
|
-
for (const item of result.removed) {
|
|
54
|
-
listItem(item);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!fullReset) {
|
|
58
|
-
info("\nPreserved (use --full to remove):");
|
|
59
|
-
listItem("eslint.config.mjs");
|
|
60
|
-
listItem(".prettierrc");
|
|
61
|
-
listItem(".markdownlint-cli2.jsonc");
|
|
62
|
-
listItem("package.json (scripts, lint-staged config)");
|
|
63
|
-
listItem("devDependencies (eslint, prettier, husky, lint-staged, etc.)");
|
|
64
|
-
}
|
|
65
|
-
success("\nSafeword configuration removed");
|
|
66
|
-
} catch (error_) {
|
|
67
|
-
error(`Reset failed: ${error_ instanceof Error ? error_.message : "Unknown error"}`);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
export {
|
|
72
|
-
reset
|
|
73
|
-
};
|
|
74
|
-
//# sourceMappingURL=reset-QRXG7KZZ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/reset.ts"],"sourcesContent":["/**\n * Reset command - Remove safeword configuration from project\n *\n * Uses reconcile() with mode='uninstall' or 'uninstall-full' to remove configuration.\n *\n * By default, preserves linting configuration (eslint, prettier, etc.)\n * Use --full to also remove linting config and uninstall npm packages\n */\n\nimport { execSync } from 'node:child_process';\nimport nodePath from 'node:path';\n\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists } from '../utils/fs.js';\nimport { error, header, info, listItem, success, warn } from '../utils/output.js';\n\nexport interface ResetOptions {\n yes?: boolean;\n full?: boolean;\n}\n\n/**\n *\n * @param options\n */\nexport async function reset(options: ResetOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDirectory)) {\n info('Nothing to remove. Project is not configured with safeword.');\n return;\n }\n\n const fullReset = options.full ?? false;\n\n header('Safeword Reset');\n if (fullReset) {\n info('Performing full reset (including linting configuration)...');\n } else {\n info('Removing safeword configuration...');\n }\n\n try {\n // Use reconcile with appropriate mode\n const mode = fullReset ? 'uninstall-full' : 'uninstall';\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, mode, ctx);\n\n // Handle npm uninstall for full reset\n if (fullReset && result.packagesToRemove.length > 0) {\n info('\\nUninstalling devDependencies...');\n\n try {\n const uninstallCmd = `npm uninstall ${result.packagesToRemove.join(' ')}`;\n info(`Running: ${uninstallCmd}`);\n execSync(uninstallCmd, { cwd, stdio: 'inherit' });\n success('Uninstalled safeword devDependencies');\n } catch {\n warn('Failed to uninstall some packages. Run manually:');\n listItem(`npm uninstall ${result.packagesToRemove.join(' ')}`);\n }\n }\n\n // Print summary\n header('Reset Complete');\n\n if (result.removed.length > 0) {\n info('\\nRemoved:');\n for (const item of result.removed) {\n listItem(item);\n }\n }\n\n // Note about preserved linting (only shown if not full reset)\n if (!fullReset) {\n info('\\nPreserved (use --full to remove):');\n listItem('eslint.config.mjs');\n listItem('.prettierrc');\n listItem('.markdownlint-cli2.jsonc');\n listItem('package.json (scripts, lint-staged config)');\n listItem('devDependencies (eslint, prettier, husky, lint-staged, etc.)');\n }\n\n success('\\nSafeword configuration removed');\n } catch (error_) {\n error(`Reset failed: ${error_ instanceof Error ? error_.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AASA,SAAS,gBAAgB;AACzB,OAAO,cAAc;AAiBrB,eAAsB,MAAM,SAAsC;AAChE,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,oBAAoB,SAAS,KAAK,KAAK,WAAW;AAGxD,MAAI,CAAC,OAAO,iBAAiB,GAAG;AAC9B,SAAK,6DAA6D;AAClE;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,QAAQ;AAElC,SAAO,gBAAgB;AACvB,MAAI,WAAW;AACb,SAAK,4DAA4D;AAAA,EACnE,OAAO;AACL,SAAK,oCAAoC;AAAA,EAC3C;AAEA,MAAI;AAEF,UAAM,OAAO,YAAY,mBAAmB;AAC5C,UAAM,MAAM,qBAAqB,GAAG;AACpC,UAAM,SAAS,MAAM,UAAU,iBAAiB,MAAM,GAAG;AAGzD,QAAI,aAAa,OAAO,iBAAiB,SAAS,GAAG;AACnD,WAAK,mCAAmC;AAExC,UAAI;AACF,cAAM,eAAe,iBAAiB,OAAO,iBAAiB,KAAK,GAAG,CAAC;AACvE,aAAK,YAAY,YAAY,EAAE;AAC/B,iBAAS,cAAc,EAAE,KAAK,OAAO,UAAU,CAAC;AAChD,gBAAQ,sCAAsC;AAAA,MAChD,QAAQ;AACN,aAAK,kDAAkD;AACvD,iBAAS,iBAAiB,OAAO,iBAAiB,KAAK,GAAG,CAAC,EAAE;AAAA,MAC/D;AAAA,IACF;AAGA,WAAO,gBAAgB;AAEvB,QAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,WAAK,YAAY;AACjB,iBAAW,QAAQ,OAAO,SAAS;AACjC,iBAAS,IAAI;AAAA,MACf;AAAA,IACF;AAGA,QAAI,CAAC,WAAW;AACd,WAAK,qCAAqC;AAC1C,eAAS,mBAAmB;AAC5B,eAAS,aAAa;AACtB,eAAS,0BAA0B;AACnC,eAAS,4CAA4C;AACrD,eAAS,8DAA8D;AAAA,IACzE;AAEA,YAAQ,kCAAkC;AAAA,EAC5C,SAAS,QAAQ;AACf,UAAM,iBAAiB,kBAAkB,QAAQ,OAAO,UAAU,eAAe,EAAE;AACnF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
|