@alessandroraffa/tangyr 1.0.3 → 2.0.0
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/README.md +2 -2
- package/dist/index.js +63 -43
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -342,9 +342,9 @@ In `tangyr-kit.yaml`, `discipline` is optional: declare it to opt into the corpu
|
|
|
342
342
|
|
|
343
343
|
## Conflict policy
|
|
344
344
|
|
|
345
|
-
Before writing, each destination is classified and resolved according to `onConflict` in `tangyr.config.yaml`: `ask` (default; prompts, and resolves to
|
|
345
|
+
Before writing, each destination is classified and resolved according to `onConflict` in `tangyr.config.yaml`: `ask` (default; prompts, and resolves to **skip** when there is no one to ask), `skip`, `backup`, or `overwrite`.
|
|
346
346
|
|
|
347
|
-
`--yes` supplies the confirmation a non-interactive run needs; it does **not** override the posture. Under `--yes`, `skip` leaves conflicting destinations unchanged and says which, `overwrite` replaces without a backup, and `backup` copies the existing file — or the existing directory, recursively — into `.tangyr/backups/<timestamp>/` and records it in the manifest so `uninstall` can put it back. A symlink is
|
|
347
|
+
`--yes` supplies the confirmation a non-interactive run needs; it does **not** override the posture. Under `--yes`, `skip` leaves conflicting destinations unchanged and says which, `overwrite` replaces without a backup, `ask` leaves the destination alone and names the postures that replace, and `backup` copies the existing file — or the existing directory, recursively — into `.tangyr/backups/<timestamp>/` and records it in the manifest so `uninstall` can put it back. A symlink is backed up as a symlink and restored as one: its target is untouched either way, but the link itself is the operator's artifact — often the one committed with the project — so replacing it without a record left nothing to put back.
|
|
348
348
|
|
|
349
349
|
One decision is taken per destination, not per target, so a file several targets write — `AGENTS.md` is claimed by four — is backed up once.
|
|
350
350
|
|
package/dist/index.js
CHANGED
|
@@ -16052,22 +16052,53 @@ function isArtifactManifestPath(relativePath) {
|
|
|
16052
16052
|
const [tool] = relativePath.split("/");
|
|
16053
16053
|
return artifactManifestPrefixes.has(tool);
|
|
16054
16054
|
}
|
|
16055
|
-
function
|
|
16056
|
-
const
|
|
16057
|
-
const
|
|
16058
|
-
|
|
16055
|
+
function resolveBackupPath(originalPath, scopePath) {
|
|
16056
|
+
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/gu, "").replace(/\.\d+Z$/u, "Z");
|
|
16057
|
+
const scopeRoot = path6.dirname(scopePath);
|
|
16058
|
+
let relativeToRoot = path6.relative(scopeRoot, originalPath);
|
|
16059
|
+
if (relativeToRoot.startsWith("..") || path6.isAbsolute(relativeToRoot)) {
|
|
16060
|
+
const digest = crypto2.createHash("sha256").update(path6.dirname(originalPath)).digest("hex").slice(0, 12);
|
|
16061
|
+
relativeToRoot = path6.join(
|
|
16062
|
+
`_outside-${digest}`,
|
|
16063
|
+
path6.basename(originalPath)
|
|
16064
|
+
);
|
|
16065
|
+
}
|
|
16066
|
+
const base = path6.join(scopePath, "backups", ts2, relativeToRoot);
|
|
16067
|
+
let candidate = base;
|
|
16068
|
+
for (let n7 = 2; fs6.existsSync(candidate) || isPresentLink(candidate); n7++) {
|
|
16069
|
+
candidate = `${base}-${String(n7)}`;
|
|
16070
|
+
}
|
|
16071
|
+
return candidate;
|
|
16072
|
+
}
|
|
16073
|
+
function isPresentLink(p) {
|
|
16074
|
+
try {
|
|
16075
|
+
return fs6.lstatSync(p).isSymbolicLink();
|
|
16076
|
+
} catch {
|
|
16077
|
+
return false;
|
|
16078
|
+
}
|
|
16079
|
+
}
|
|
16080
|
+
function copyForBackup(originalPath, backupPath) {
|
|
16081
|
+
fs6.mkdirSync(path6.dirname(backupPath), { recursive: true });
|
|
16059
16082
|
const stat = fs6.lstatSync(originalPath);
|
|
16060
16083
|
if (stat.isSymbolicLink()) {
|
|
16061
16084
|
fs6.symlinkSync(fs6.readlinkSync(originalPath), backupPath);
|
|
16062
16085
|
} else if (stat.isDirectory()) {
|
|
16063
16086
|
fs6.cpSync(originalPath, backupPath, {
|
|
16064
16087
|
recursive: true,
|
|
16088
|
+
// Preserve links as links: following them would copy the kit into the
|
|
16089
|
+
// backup and, worse, make the backup look like the operator's content.
|
|
16065
16090
|
dereference: false,
|
|
16066
16091
|
verbatimSymlinks: true
|
|
16067
16092
|
});
|
|
16068
16093
|
} else {
|
|
16069
16094
|
fs6.copyFileSync(originalPath, backupPath);
|
|
16070
16095
|
}
|
|
16096
|
+
}
|
|
16097
|
+
function backupFile(originalPath, manifest, scopePath) {
|
|
16098
|
+
const priorSuffixes = manifest.backups.filter((b) => b.originalPath === originalPath).map((b) => b.integerSuffix);
|
|
16099
|
+
const suffix = priorSuffixes.length > 0 ? Math.max(...priorSuffixes) + 1 : 1;
|
|
16100
|
+
const backupPath = resolveBackupPath(originalPath, scopePath);
|
|
16101
|
+
copyForBackup(originalPath, backupPath);
|
|
16071
16102
|
manifest.backups.push({
|
|
16072
16103
|
originalPath,
|
|
16073
16104
|
backupLocation: backupPath,
|
|
@@ -22273,13 +22304,11 @@ async function runUninstallCommand(options, logger) {
|
|
|
22273
22304
|
}
|
|
22274
22305
|
}
|
|
22275
22306
|
if (fullUninstall) {
|
|
22276
|
-
const newestFirst = [...manifest.backups].reverse();
|
|
22277
22307
|
const restored = /* @__PURE__ */ new Set();
|
|
22278
|
-
|
|
22308
|
+
const kept = [];
|
|
22309
|
+
for (const backup of manifest.backups) {
|
|
22279
22310
|
if (restored.has(backup.originalPath)) {
|
|
22280
|
-
|
|
22281
|
-
` superseded by a newer backup, not restored: ${backup.backupLocation}`
|
|
22282
|
-
);
|
|
22311
|
+
kept.push(backup);
|
|
22283
22312
|
continue;
|
|
22284
22313
|
}
|
|
22285
22314
|
restored.add(backup.originalPath);
|
|
@@ -22291,6 +22320,19 @@ async function runUninstallCommand(options, logger) {
|
|
|
22291
22320
|
);
|
|
22292
22321
|
}
|
|
22293
22322
|
}
|
|
22323
|
+
if (kept.length > 0) {
|
|
22324
|
+
logger.warn(
|
|
22325
|
+
`
|
|
22326
|
+
${String(kept.length)} later backup(s) were NOT restored: the earliest copy of each location is the one that comes back, because it is the content from before the first install.`
|
|
22327
|
+
);
|
|
22328
|
+
for (const backup of kept) {
|
|
22329
|
+
logger.warn(` ${backup.originalPath}`);
|
|
22330
|
+
logger.warn(` later copy left at: ${backup.backupLocation}`);
|
|
22331
|
+
}
|
|
22332
|
+
logger.warn(
|
|
22333
|
+
" The manifest is removed below, so nothing will reference these copies afterwards. Move what you need out of the backups directory now."
|
|
22334
|
+
);
|
|
22335
|
+
}
|
|
22294
22336
|
const manifestPath = path24.join(scopePath, "manifest.json");
|
|
22295
22337
|
if (fs27.existsSync(manifestPath)) {
|
|
22296
22338
|
fs27.unlinkSync(manifestPath);
|
|
@@ -25091,7 +25133,6 @@ function toConflictPolicy2(value) {
|
|
|
25091
25133
|
}
|
|
25092
25134
|
|
|
25093
25135
|
// src/commands/install.ts
|
|
25094
|
-
import crypto11 from "crypto";
|
|
25095
25136
|
import fs43 from "fs";
|
|
25096
25137
|
import path43 from "path";
|
|
25097
25138
|
|
|
@@ -26487,7 +26528,7 @@ function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath,
|
|
|
26487
26528
|
const shimContent = buildAgentsMdShim(shimPath, agentsMdPath, "claude-code");
|
|
26488
26529
|
const shimLink = lstatOrNull(shimPath);
|
|
26489
26530
|
if (shimLink?.isSymbolicLink()) {
|
|
26490
|
-
backupFile(shimPath, manifest);
|
|
26531
|
+
backupFile(shimPath, manifest, scopePath);
|
|
26491
26532
|
writeManifest(scopePath, manifest);
|
|
26492
26533
|
fs36.unlinkSync(shimPath);
|
|
26493
26534
|
logger?.verbose(
|
|
@@ -26522,7 +26563,7 @@ function applyClaudeCodeShimUpgrade(shimPath, agentsMdPath, manifest, scopePath,
|
|
|
26522
26563
|
);
|
|
26523
26564
|
return;
|
|
26524
26565
|
}
|
|
26525
|
-
backupFile(shimPath, manifest);
|
|
26566
|
+
backupFile(shimPath, manifest, scopePath);
|
|
26526
26567
|
writeManifest(scopePath, manifest);
|
|
26527
26568
|
fs36.writeFileSync(shimPath, shimContent);
|
|
26528
26569
|
addArtifactToManifest(manifest, {
|
|
@@ -26710,7 +26751,7 @@ function handleConflict(destination, config, options, logger, manifest, scopePat
|
|
|
26710
26751
|
}
|
|
26711
26752
|
if (config.onConflict === "backup") {
|
|
26712
26753
|
if (manifest && scopePath) {
|
|
26713
|
-
backupFile(destination, manifest);
|
|
26754
|
+
backupFile(destination, manifest, scopePath);
|
|
26714
26755
|
writeManifest(scopePath, manifest);
|
|
26715
26756
|
fs36.rmSync(destination, { recursive: true, force: true });
|
|
26716
26757
|
} else {
|
|
@@ -28606,7 +28647,7 @@ function handleConflict2(destination, config, options, logger, manifest, scopePa
|
|
|
28606
28647
|
}
|
|
28607
28648
|
if (config.onConflict === "backup") {
|
|
28608
28649
|
if (manifest && scopePath) {
|
|
28609
|
-
backupFile(destination, manifest);
|
|
28650
|
+
backupFile(destination, manifest, scopePath);
|
|
28610
28651
|
writeManifest(scopePath, manifest);
|
|
28611
28652
|
fs38.rmSync(destination, { recursive: true, force: true });
|
|
28612
28653
|
} else {
|
|
@@ -29187,7 +29228,7 @@ function handleConflict3(destination, config, options, logger, manifest, scopePa
|
|
|
29187
29228
|
}
|
|
29188
29229
|
if (config.onConflict === "backup") {
|
|
29189
29230
|
if (manifest && scopePath) {
|
|
29190
|
-
backupFile(destination, manifest);
|
|
29231
|
+
backupFile(destination, manifest, scopePath);
|
|
29191
29232
|
writeManifest(scopePath, manifest);
|
|
29192
29233
|
fs39.rmSync(destination, { recursive: true, force: true });
|
|
29193
29234
|
} else {
|
|
@@ -29813,7 +29854,7 @@ function handleConflict4(destination, config, options, logger, manifest, scopePa
|
|
|
29813
29854
|
}
|
|
29814
29855
|
if (config.onConflict === "backup") {
|
|
29815
29856
|
if (manifest && scopePath) {
|
|
29816
|
-
backupFile(destination, manifest);
|
|
29857
|
+
backupFile(destination, manifest, scopePath);
|
|
29817
29858
|
writeManifest(scopePath, manifest);
|
|
29818
29859
|
fs40.rmSync(destination, { recursive: true, force: true });
|
|
29819
29860
|
} else {
|
|
@@ -30288,7 +30329,7 @@ function handleConflict5(destination, config, options, logger, manifest, scopePa
|
|
|
30288
30329
|
}
|
|
30289
30330
|
if (config.onConflict === "backup") {
|
|
30290
30331
|
if (manifest && scopePath) {
|
|
30291
|
-
backupFile(destination, manifest);
|
|
30332
|
+
backupFile(destination, manifest, scopePath);
|
|
30292
30333
|
writeManifest(scopePath, manifest);
|
|
30293
30334
|
fs41.rmSync(destination, { recursive: true, force: true });
|
|
30294
30335
|
} else {
|
|
@@ -30781,7 +30822,7 @@ function handleConflict6(destination, config, options, logger, manifest, scopePa
|
|
|
30781
30822
|
}
|
|
30782
30823
|
if (config.onConflict === "backup") {
|
|
30783
30824
|
if (manifest && scopePath) {
|
|
30784
|
-
backupFile(destination, manifest);
|
|
30825
|
+
backupFile(destination, manifest, scopePath);
|
|
30785
30826
|
writeManifest(scopePath, manifest);
|
|
30786
30827
|
fs42.rmSync(destination, { recursive: true, force: true });
|
|
30787
30828
|
} else {
|
|
@@ -30849,29 +30890,8 @@ function isBackupablePath(targetPath) {
|
|
|
30849
30890
|
}
|
|
30850
30891
|
}
|
|
30851
30892
|
function backupConflictingTarget(targetPath, scopePath) {
|
|
30852
|
-
const
|
|
30853
|
-
|
|
30854
|
-
let relativeToRoot = path43.relative(scopeRoot, targetPath);
|
|
30855
|
-
if (relativeToRoot.startsWith("..") || path43.isAbsolute(relativeToRoot)) {
|
|
30856
|
-
const digest = crypto11.createHash("sha256").update(path43.dirname(targetPath)).digest("hex").slice(0, 12);
|
|
30857
|
-
relativeToRoot = path43.join(`_outside-${digest}`, path43.basename(targetPath));
|
|
30858
|
-
}
|
|
30859
|
-
const backupPath = path43.join(scopePath, "backups", ts2, relativeToRoot);
|
|
30860
|
-
fs43.mkdirSync(path43.dirname(backupPath), { recursive: true });
|
|
30861
|
-
const stat = fs43.lstatSync(targetPath);
|
|
30862
|
-
if (stat.isSymbolicLink()) {
|
|
30863
|
-
fs43.symlinkSync(fs43.readlinkSync(targetPath), backupPath);
|
|
30864
|
-
} else if (stat.isDirectory()) {
|
|
30865
|
-
fs43.cpSync(targetPath, backupPath, {
|
|
30866
|
-
recursive: true,
|
|
30867
|
-
// Preserve links as links: following them would copy the kit into the
|
|
30868
|
-
// backup and, worse, make the backup look like the user's own content.
|
|
30869
|
-
dereference: false,
|
|
30870
|
-
verbatimSymlinks: true
|
|
30871
|
-
});
|
|
30872
|
-
} else {
|
|
30873
|
-
fs43.copyFileSync(targetPath, backupPath);
|
|
30874
|
-
}
|
|
30893
|
+
const backupPath = resolveBackupPath(targetPath, scopePath);
|
|
30894
|
+
copyForBackup(targetPath, backupPath);
|
|
30875
30895
|
return backupPath;
|
|
30876
30896
|
}
|
|
30877
30897
|
async function runInstallCommand(options, logger) {
|
|
@@ -32488,7 +32508,7 @@ function commandProbeRows(target, key, command, args) {
|
|
|
32488
32508
|
}
|
|
32489
32509
|
|
|
32490
32510
|
// src/commands/status.ts
|
|
32491
|
-
import
|
|
32511
|
+
import crypto11 from "crypto";
|
|
32492
32512
|
import fs45 from "fs";
|
|
32493
32513
|
import path46 from "path";
|
|
32494
32514
|
function runStatusCommand(options, logger) {
|
|
@@ -33612,7 +33632,7 @@ function containsTangyrSource(value) {
|
|
|
33612
33632
|
return typeof value._tangyr_source === "string" || typeof value._proteus_source === "string" || Object.values(value).some((entry) => containsTangyrSource(entry));
|
|
33613
33633
|
}
|
|
33614
33634
|
function hashString6(value) {
|
|
33615
|
-
return `sha256:${
|
|
33635
|
+
return `sha256:${crypto11.createHash("sha256").update(value).digest("hex")}`;
|
|
33616
33636
|
}
|
|
33617
33637
|
|
|
33618
33638
|
// src/commands/sync.ts
|