@drewpayment/mink 0.11.0-beta.1 → 0.11.0-beta.3
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/dashboard/out/404.html +1 -1
- package/dashboard/out/action-log.html +1 -1
- package/dashboard/out/action-log.txt +1 -1
- package/dashboard/out/activity.html +1 -1
- package/dashboard/out/activity.txt +1 -1
- package/dashboard/out/bugs.html +1 -1
- package/dashboard/out/bugs.txt +1 -1
- package/dashboard/out/capture.html +1 -1
- package/dashboard/out/capture.txt +1 -1
- package/dashboard/out/config.html +1 -1
- package/dashboard/out/config.txt +1 -1
- package/dashboard/out/daemon.html +1 -1
- package/dashboard/out/daemon.txt +1 -1
- package/dashboard/out/design.html +1 -1
- package/dashboard/out/design.txt +1 -1
- package/dashboard/out/discord.html +1 -1
- package/dashboard/out/discord.txt +1 -1
- package/dashboard/out/file-index.html +1 -1
- package/dashboard/out/file-index.txt +1 -1
- package/dashboard/out/index.html +1 -1
- package/dashboard/out/index.txt +1 -1
- package/dashboard/out/insights.html +1 -1
- package/dashboard/out/insights.txt +1 -1
- package/dashboard/out/learning.html +1 -1
- package/dashboard/out/learning.txt +1 -1
- package/dashboard/out/overview.html +1 -1
- package/dashboard/out/overview.txt +1 -1
- package/dashboard/out/scheduler.html +1 -1
- package/dashboard/out/scheduler.txt +1 -1
- package/dashboard/out/sync.html +1 -1
- package/dashboard/out/sync.txt +1 -1
- package/dashboard/out/tokens.html +1 -1
- package/dashboard/out/tokens.txt +1 -1
- package/dashboard/out/waste.html +1 -1
- package/dashboard/out/waste.txt +1 -1
- package/dashboard/out/wiki.html +1 -1
- package/dashboard/out/wiki.txt +1 -1
- package/dist/cli.js +110 -29
- package/package.json +1 -1
- package/src/commands/sync-migrate.ts +186 -28
- package/src/core/project-id.ts +10 -2
- package/src/core/sync.ts +7 -0
- /package/dashboard/out/_next/static/{WDjkNLHEd_wI-oOzLyblH → Mmf6YVSNZzpPOZiW-DG5M}/_buildManifest.js +0 -0
- /package/dashboard/out/_next/static/{WDjkNLHEd_wI-oOzLyblH → Mmf6YVSNZzpPOZiW-DG5M}/_ssgManifest.js +0 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
writeFileSync,
|
|
7
7
|
readFileSync,
|
|
8
8
|
renameSync,
|
|
9
|
+
rmSync,
|
|
9
10
|
unlinkSync,
|
|
10
11
|
} from "fs";
|
|
11
12
|
import { join } from "path";
|
|
@@ -236,7 +237,24 @@ function listProjectsNeedingMigration(): string[] {
|
|
|
236
237
|
// Idempotent: re-running after a clean pass walks every project, finds every
|
|
237
238
|
// id matches its directory name, and does nothing.
|
|
238
239
|
|
|
239
|
-
|
|
240
|
+
// Plan actions:
|
|
241
|
+
// rename old dir present, new dir absent → rename old → new, record alias.
|
|
242
|
+
// skip-converged old dir + new dir both present, alias NOT yet recorded → record
|
|
243
|
+
// alias on new meta and evict old dir to .identity-rollback/. Named
|
|
244
|
+
// "skip-converged" because the rename itself is unnecessary; the
|
|
245
|
+
// convergence work (alias + eviction) is the action.
|
|
246
|
+
// skip-evict old dir + new dir both present, alias already recorded → only
|
|
247
|
+
// evict the old dir. Reached when a previous migration recorded
|
|
248
|
+
// the alias but didn't (or couldn't) finish evicting. Without
|
|
249
|
+
// this, dry-run would keep proposing skip-converged forever.
|
|
250
|
+
// skip-no-cwd project's working-copy path is on a different device — leave alone.
|
|
251
|
+
// skip-unchanged newId === oldId — no work needed at all.
|
|
252
|
+
export type IdentityPlanAction =
|
|
253
|
+
| "rename"
|
|
254
|
+
| "skip-converged"
|
|
255
|
+
| "skip-evict"
|
|
256
|
+
| "skip-no-cwd"
|
|
257
|
+
| "skip-unchanged";
|
|
240
258
|
|
|
241
259
|
export interface IdentityPlanEntry {
|
|
242
260
|
oldId: string;
|
|
@@ -248,9 +266,15 @@ export interface IdentityPlanEntry {
|
|
|
248
266
|
|
|
249
267
|
// Walks every project on disk and returns the rename plan without touching it.
|
|
250
268
|
// Backbone for both --dry-run and the real migration so they share logic.
|
|
251
|
-
|
|
269
|
+
//
|
|
270
|
+
// Accepts an optional `flagOverride` so callers that have already snapshotted
|
|
271
|
+
// `projects.identity` (e.g. migrateSyncLayout, before its git-stash) can pass
|
|
272
|
+
// the snapshot in rather than re-reading from disk inside a stash window where
|
|
273
|
+
// the config file's uncommitted writes are temporarily hidden.
|
|
274
|
+
export function planIdentityMigration(flagOverride?: string): IdentityPlanEntry[] {
|
|
252
275
|
const plan: IdentityPlanEntry[] = [];
|
|
253
|
-
|
|
276
|
+
const flag = flagOverride ?? resolveConfigValue("projects.identity").value;
|
|
277
|
+
if (flag !== "git-remote") {
|
|
254
278
|
return plan;
|
|
255
279
|
}
|
|
256
280
|
|
|
@@ -288,7 +312,10 @@ export function planIdentityMigration(): IdentityPlanEntry[] {
|
|
|
288
312
|
|
|
289
313
|
let newId: string;
|
|
290
314
|
try {
|
|
291
|
-
newId = resolveProjectIdentity(
|
|
315
|
+
newId = resolveProjectIdentity(
|
|
316
|
+
meta.cwd,
|
|
317
|
+
flag === "git-remote" || flag === "path-derived" ? flag : undefined
|
|
318
|
+
).id;
|
|
292
319
|
} catch {
|
|
293
320
|
continue;
|
|
294
321
|
}
|
|
@@ -300,12 +327,23 @@ export function planIdentityMigration(): IdentityPlanEntry[] {
|
|
|
300
327
|
|
|
301
328
|
const newProjDir = join(projectsRoot, newId);
|
|
302
329
|
if (existsSync(newProjDir)) {
|
|
330
|
+
// If the canonical (new) project already records this oldId in its
|
|
331
|
+
// aliases list, the convergence bookkeeping is already done — only the
|
|
332
|
+
// leftover old directory remains to be cleaned up. Surfacing this as a
|
|
333
|
+
// distinct action (skip-evict) makes dry-run idempotent: once the alias
|
|
334
|
+
// is recorded AND the old directory is gone, the planner stops seeing
|
|
335
|
+
// the project entirely. Pre-fix it would keep proposing the same
|
|
336
|
+
// skip-converged action forever.
|
|
337
|
+
const newMeta = getProjectMeta(newProjDir);
|
|
338
|
+
const aliasAlreadyRecorded = newMeta?.aliases?.includes(oldId) ?? false;
|
|
303
339
|
plan.push({
|
|
304
340
|
oldId,
|
|
305
341
|
newId,
|
|
306
342
|
cwd: meta.cwd,
|
|
307
|
-
action: "skip-converged",
|
|
308
|
-
reason:
|
|
343
|
+
action: aliasAlreadyRecorded ? "skip-evict" : "skip-converged",
|
|
344
|
+
reason: aliasAlreadyRecorded
|
|
345
|
+
? "alias already recorded; will evict leftover old directory"
|
|
346
|
+
: "destination already exists (from sync); will record alias and evict old directory",
|
|
309
347
|
});
|
|
310
348
|
continue;
|
|
311
349
|
}
|
|
@@ -358,26 +396,61 @@ function copyDirRecursive(src: string, dest: string, excludeNames: Set<string>):
|
|
|
358
396
|
}
|
|
359
397
|
}
|
|
360
398
|
|
|
361
|
-
|
|
399
|
+
// Accepts the identity-mode value as a parameter so the caller can snapshot it
|
|
400
|
+
// before any disk side-effects (notably the migrating git-stash in
|
|
401
|
+
// migrateSyncLayout, which would hide uncommitted writes to the config file
|
|
402
|
+
// that drives this very decision). Falls back to a fresh read for callers that
|
|
403
|
+
// don't operate inside a stash window (e.g. session-start triggers and the
|
|
404
|
+
// --dry-run path).
|
|
405
|
+
export interface IdentityMigrationOutcome {
|
|
362
406
|
renamed: number;
|
|
407
|
+
converged: number;
|
|
408
|
+
evicted: number;
|
|
363
409
|
visited: number;
|
|
364
410
|
backupDir: string | null;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
411
|
+
renames: Array<{ from: string; to: string }>;
|
|
412
|
+
evictions: string[];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function migrateProjectIdentities(
|
|
416
|
+
deviceId: string,
|
|
417
|
+
flag: string = resolveConfigValue("projects.identity").value
|
|
418
|
+
): IdentityMigrationOutcome {
|
|
419
|
+
if (flag !== "git-remote") {
|
|
420
|
+
return {
|
|
421
|
+
renamed: 0,
|
|
422
|
+
converged: 0,
|
|
423
|
+
evicted: 0,
|
|
424
|
+
visited: 0,
|
|
425
|
+
backupDir: null,
|
|
426
|
+
renames: [],
|
|
427
|
+
evictions: [],
|
|
428
|
+
};
|
|
368
429
|
}
|
|
369
430
|
|
|
370
|
-
const plan = planIdentityMigration();
|
|
371
|
-
|
|
431
|
+
const plan = planIdentityMigration(flag);
|
|
432
|
+
// Any action that moves an old directory aside needs a backup destination.
|
|
433
|
+
// rename, skip-converged (record alias + evict), and skip-evict (alias
|
|
434
|
+
// already recorded; only evict) all qualify.
|
|
435
|
+
const willTouchOldDir = plan.filter(
|
|
436
|
+
(p) =>
|
|
437
|
+
p.action === "rename" ||
|
|
438
|
+
p.action === "skip-converged" ||
|
|
439
|
+
p.action === "skip-evict"
|
|
440
|
+
);
|
|
372
441
|
|
|
373
442
|
// Compute the backup root up-front so all snapshots for this migration pass
|
|
374
443
|
// land in one timestamped directory the user can find and reason about.
|
|
375
444
|
let backupRoot: string | null = null;
|
|
376
|
-
if (
|
|
445
|
+
if (willTouchOldDir.length > 0) {
|
|
377
446
|
backupRoot = identityBackupRoot(ensureIdentityBackupTimestamp());
|
|
378
447
|
}
|
|
379
448
|
|
|
380
449
|
let renamed = 0;
|
|
450
|
+
let converged = 0;
|
|
451
|
+
let evicted = 0;
|
|
452
|
+
const renames: Array<{ from: string; to: string }> = [];
|
|
453
|
+
const evictions: string[] = [];
|
|
381
454
|
let visited = plan.length;
|
|
382
455
|
const projectsRoot = join(minkRoot(), "projects");
|
|
383
456
|
|
|
@@ -394,16 +467,53 @@ function migrateProjectIdentities(deviceId: string): {
|
|
|
394
467
|
}
|
|
395
468
|
}
|
|
396
469
|
|
|
397
|
-
if (
|
|
470
|
+
if (
|
|
471
|
+
(entry.action === "skip-converged" || entry.action === "skip-evict") &&
|
|
472
|
+
entry.newId
|
|
473
|
+
) {
|
|
398
474
|
const newProjDir = join(projectsRoot, entry.newId);
|
|
475
|
+
// Record the alias and lift the device path before evicting — if the
|
|
476
|
+
// canonical meta is broken (read returns null in addProjectAlias), the
|
|
477
|
+
// safer path is to leave the old directory alone rather than discard
|
|
478
|
+
// it without ever surfacing the alias. We detect that case below.
|
|
479
|
+
let aliasOnRecord = false;
|
|
399
480
|
try {
|
|
400
|
-
|
|
481
|
+
if (entry.action === "skip-evict") {
|
|
482
|
+
aliasOnRecord = true;
|
|
483
|
+
} else {
|
|
484
|
+
addProjectAlias(newProjDir, entry.oldId);
|
|
485
|
+
const newMeta = getProjectMeta(newProjDir);
|
|
486
|
+
aliasOnRecord = newMeta?.aliases?.includes(entry.oldId) ?? false;
|
|
487
|
+
}
|
|
401
488
|
if (entry.cwd) {
|
|
402
489
|
setProjectPathForDevice(newProjDir, deviceId, entry.cwd);
|
|
403
490
|
}
|
|
404
491
|
} catch {
|
|
405
492
|
// best-effort
|
|
406
493
|
}
|
|
494
|
+
|
|
495
|
+
if (aliasOnRecord && backupRoot) {
|
|
496
|
+
// Snapshot the old dir before eviction so any local-only state
|
|
497
|
+
// (writes that landed here before sync converged) is recoverable.
|
|
498
|
+
const ok = backupProjectForRollback(
|
|
499
|
+
oldProjDir,
|
|
500
|
+
join(backupRoot, entry.oldId)
|
|
501
|
+
);
|
|
502
|
+
if (ok) {
|
|
503
|
+
let removed = false;
|
|
504
|
+
try {
|
|
505
|
+
rmSync(oldProjDir, { recursive: true, force: true });
|
|
506
|
+
removed = true;
|
|
507
|
+
} catch {
|
|
508
|
+
// best-effort; leave the directory rather than partially deleted
|
|
509
|
+
}
|
|
510
|
+
if (removed) {
|
|
511
|
+
evictions.push(entry.oldId);
|
|
512
|
+
evicted++;
|
|
513
|
+
if (entry.action === "skip-converged") converged++;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
407
517
|
continue;
|
|
408
518
|
}
|
|
409
519
|
|
|
@@ -438,9 +548,18 @@ function migrateProjectIdentities(deviceId: string): {
|
|
|
438
548
|
// best-effort
|
|
439
549
|
}
|
|
440
550
|
renamed++;
|
|
551
|
+
renames.push({ from: entry.oldId, to: entry.newId });
|
|
441
552
|
}
|
|
442
553
|
|
|
443
|
-
return {
|
|
554
|
+
return {
|
|
555
|
+
renamed,
|
|
556
|
+
converged,
|
|
557
|
+
evicted,
|
|
558
|
+
visited,
|
|
559
|
+
backupDir: backupRoot,
|
|
560
|
+
renames,
|
|
561
|
+
evictions,
|
|
562
|
+
};
|
|
444
563
|
}
|
|
445
564
|
|
|
446
565
|
// ── v3 identity rollback ──────────────────────────────────────────────────
|
|
@@ -527,6 +646,7 @@ export interface MigrateResult {
|
|
|
527
646
|
fromVersion: number;
|
|
528
647
|
toVersion: number;
|
|
529
648
|
message?: string;
|
|
649
|
+
identity?: IdentityMigrationOutcome;
|
|
530
650
|
}
|
|
531
651
|
|
|
532
652
|
// Idempotent. Safe to invoke from `mink sync migrate` directly or from a
|
|
@@ -542,6 +662,11 @@ export interface MigrateResult {
|
|
|
542
662
|
export function migrateSyncLayout(): MigrateResult {
|
|
543
663
|
const fromVersion = readSyncVersion();
|
|
544
664
|
const pending = listProjectsNeedingMigration();
|
|
665
|
+
// Snapshot the identity mode BEFORE the migrating stash below. The stash
|
|
666
|
+
// hides any uncommitted edits to ~/.mink/config — including the very
|
|
667
|
+
// `projects.identity = git-remote` write that should be driving this
|
|
668
|
+
// migration. Reading the flag after the stash would see the stale,
|
|
669
|
+
// last-committed config and the v3 identity step would no-op.
|
|
545
670
|
const identityMode = resolveConfigValue("projects.identity").value;
|
|
546
671
|
if (
|
|
547
672
|
fromVersion >= MINK_SYNC_VERSION &&
|
|
@@ -611,9 +736,19 @@ export function migrateSyncLayout(): MigrateResult {
|
|
|
611
736
|
// v3 identity migration: rename per-project directories to their stable
|
|
612
737
|
// git-derived identifier when the user has opted in. Cheap no-op when the
|
|
613
738
|
// flag is off or every project's identifier already matches its directory.
|
|
614
|
-
|
|
739
|
+
// Pass the pre-stash snapshot of identityMode so we don't re-read the
|
|
740
|
+
// config from a stash-hidden working tree.
|
|
741
|
+
let identity: IdentityMigrationOutcome = {
|
|
742
|
+
renamed: 0,
|
|
743
|
+
converged: 0,
|
|
744
|
+
evicted: 0,
|
|
745
|
+
visited: 0,
|
|
746
|
+
backupDir: null,
|
|
747
|
+
renames: [],
|
|
748
|
+
evictions: [],
|
|
749
|
+
};
|
|
615
750
|
try {
|
|
616
|
-
identity = migrateProjectIdentities(deviceId);
|
|
751
|
+
identity = migrateProjectIdentities(deviceId, identityMode);
|
|
617
752
|
} catch {
|
|
618
753
|
// best-effort; never block the rest of migration
|
|
619
754
|
}
|
|
@@ -625,16 +760,19 @@ export function migrateSyncLayout(): MigrateResult {
|
|
|
625
760
|
writeSyncVersion(MINK_SYNC_VERSION);
|
|
626
761
|
}
|
|
627
762
|
|
|
628
|
-
if (
|
|
763
|
+
if (
|
|
764
|
+
isSyncInitialized() &&
|
|
765
|
+
(processed > 0 || identity.renamed > 0 || identity.evicted > 0)
|
|
766
|
+
) {
|
|
629
767
|
// Skip the lock file — it's part of migration coordination, not state.
|
|
630
768
|
gitSafe("add -A");
|
|
631
769
|
gitSafe(`reset HEAD ".sync-migrate.lock"`);
|
|
632
|
-
const
|
|
633
|
-
identity.renamed > 0
|
|
634
|
-
?
|
|
635
|
-
:
|
|
770
|
+
const identityNote =
|
|
771
|
+
identity.renamed > 0 || identity.evicted > 0
|
|
772
|
+
? `, ${identity.renamed} renamed + ${identity.evicted} evicted for identity v3`
|
|
773
|
+
: "";
|
|
636
774
|
gitSafe(
|
|
637
|
-
`commit -m "mink: migrate sync layout v${fromVersion} -> v${MINK_SYNC_VERSION} (device ${deviceId.slice(0, 8)}, ${
|
|
775
|
+
`commit -m "mink: migrate sync layout v${fromVersion} -> v${MINK_SYNC_VERSION} (device ${deviceId.slice(0, 8)}, ${processed} projects${identityNote})"`
|
|
638
776
|
);
|
|
639
777
|
}
|
|
640
778
|
|
|
@@ -646,6 +784,7 @@ export function migrateSyncLayout(): MigrateResult {
|
|
|
646
784
|
ranMigration: true,
|
|
647
785
|
fromVersion,
|
|
648
786
|
toVersion: MINK_SYNC_VERSION,
|
|
787
|
+
identity,
|
|
649
788
|
};
|
|
650
789
|
} finally {
|
|
651
790
|
releaseLock();
|
|
@@ -671,20 +810,24 @@ export function syncMigrateCommand(args: string[] = []): void {
|
|
|
671
810
|
}
|
|
672
811
|
const renames = plan.filter((p) => p.action === "rename");
|
|
673
812
|
const converged = plan.filter((p) => p.action === "skip-converged");
|
|
813
|
+
const evictOnly = plan.filter((p) => p.action === "skip-evict");
|
|
674
814
|
const skippedNoCwd = plan.filter((p) => p.action === "skip-no-cwd");
|
|
675
815
|
const unchanged = plan.filter((p) => p.action === "skip-unchanged");
|
|
676
816
|
|
|
677
817
|
console.log(
|
|
678
|
-
`[mink] sync migrate --dry-run: ${renames.length} rename(s), ${converged.length} alias-only, ${skippedNoCwd.length} skipped (no cwd), ${unchanged.length} unchanged`
|
|
818
|
+
`[mink] sync migrate --dry-run: ${renames.length} rename(s), ${converged.length} converge (alias + evict), ${evictOnly.length} evict-only, ${skippedNoCwd.length} skipped (no cwd), ${unchanged.length} unchanged`
|
|
679
819
|
);
|
|
680
820
|
for (const p of renames) {
|
|
681
|
-
console.log(` rename:
|
|
821
|
+
console.log(` rename: ${p.oldId} → ${p.newId}`);
|
|
682
822
|
}
|
|
683
823
|
for (const p of converged) {
|
|
684
|
-
console.log(`
|
|
824
|
+
console.log(` converge: ${p.oldId} → ${p.newId} (record alias on ${p.newId}, evict ${p.oldId} to .identity-rollback/)`);
|
|
825
|
+
}
|
|
826
|
+
for (const p of evictOnly) {
|
|
827
|
+
console.log(` evict: ${p.oldId} → .identity-rollback/ (alias already on ${p.newId})`);
|
|
685
828
|
}
|
|
686
829
|
for (const p of skippedNoCwd) {
|
|
687
|
-
console.log(` skip:
|
|
830
|
+
console.log(` skip: ${p.oldId} — ${p.reason}`);
|
|
688
831
|
}
|
|
689
832
|
return;
|
|
690
833
|
}
|
|
@@ -724,4 +867,19 @@ export function syncMigrateCommand(args: string[] = []): void {
|
|
|
724
867
|
console.log(
|
|
725
868
|
`[mink] sync migrate: v${result.fromVersion} → v${result.toVersion} complete`
|
|
726
869
|
);
|
|
870
|
+
const identity = result.identity;
|
|
871
|
+
if (identity && (identity.renamed > 0 || identity.evicted > 0)) {
|
|
872
|
+
console.log(
|
|
873
|
+
` identity: ${identity.renamed} renamed, ${identity.converged} converged, ${identity.evicted} evicted`
|
|
874
|
+
);
|
|
875
|
+
for (const r of identity.renames) {
|
|
876
|
+
console.log(` renamed: ${r.from} → ${r.to}`);
|
|
877
|
+
}
|
|
878
|
+
for (const id of identity.evictions) {
|
|
879
|
+
console.log(` evicted: ${id} → .identity-rollback/`);
|
|
880
|
+
}
|
|
881
|
+
if (identity.backupDir) {
|
|
882
|
+
console.log(` rollback snapshot: ${identity.backupDir}`);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
727
885
|
}
|
package/src/core/project-id.ts
CHANGED
|
@@ -136,8 +136,16 @@ function readIdentityMode(): "path-derived" | "git-remote" {
|
|
|
136
136
|
return "path-derived";
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// Accepts an optional `modeOverride` so callers that have already snapshotted
|
|
140
|
+
// `projects.identity` (e.g. the v3 migration, which runs inside a git-stash
|
|
141
|
+
// window where the config file's uncommitted writes are hidden from disk) can
|
|
142
|
+
// pass the snapshot in. Without the override, the internal mode read can
|
|
143
|
+
// disagree with the caller's view of the world and produce the wrong id.
|
|
144
|
+
export function resolveProjectIdentity(
|
|
145
|
+
cwd: string,
|
|
146
|
+
modeOverride?: "path-derived" | "git-remote"
|
|
147
|
+
): ProjectIdentity {
|
|
148
|
+
const mode = modeOverride ?? readIdentityMode();
|
|
141
149
|
if (mode === "path-derived") {
|
|
142
150
|
return { id: generateProjectId(cwd), source: "path-derived" };
|
|
143
151
|
}
|
package/src/core/sync.ts
CHANGED
|
@@ -51,6 +51,13 @@ config.local
|
|
|
51
51
|
# Migration coordination — never sync this
|
|
52
52
|
.sync-migrate.lock
|
|
53
53
|
|
|
54
|
+
# Per-device identity migration recovery snapshots — local recovery state
|
|
55
|
+
# for the migrating device, not authoritative project state. A device that
|
|
56
|
+
# needs to recover from its migration must do so from its own snapshot;
|
|
57
|
+
# syncing would make rollback dirs appear on devices that never produced
|
|
58
|
+
# the corresponding migration.
|
|
59
|
+
.identity-rollback/
|
|
60
|
+
|
|
54
61
|
# Local backups and per-device caches — machine-specific snapshots
|
|
55
62
|
projects/*/backups/
|
|
56
63
|
projects/*/session.json
|
|
File without changes
|
/package/dashboard/out/_next/static/{WDjkNLHEd_wI-oOzLyblH → Mmf6YVSNZzpPOZiW-DG5M}/_ssgManifest.js
RENAMED
|
File without changes
|