@savvy-web/silk-effects 5.8.0 → 5.9.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/index.d.ts +67 -3
- package/package.json +4 -4
- package/repos/index.js +6 -4
- package/repos/schemas/reports.js +18 -1
- package/repos/services/drift.js +8 -2
- package/repos/services/manager.js +73 -5
package/index.d.ts
CHANGED
|
@@ -9188,6 +9188,25 @@ declare const ReposRestoreResult: Schema.Struct<{
|
|
|
9188
9188
|
}>;
|
|
9189
9189
|
/** @public */
|
|
9190
9190
|
type ReposRestoreResult = typeof ReposRestoreResult.Type;
|
|
9191
|
+
/**
|
|
9192
|
+
* Result of clearing a stale submodule registration from the superproject's
|
|
9193
|
+
* LOCAL git config.
|
|
9194
|
+
*
|
|
9195
|
+
* @remarks
|
|
9196
|
+
* `section` is the registration name the section was cleared under (e.g.
|
|
9197
|
+
* `.repos/effect-old`), and `removedKeys` lists the full config keys the
|
|
9198
|
+
* removed section actually carried — the achieved-state report, read from the
|
|
9199
|
+
* config BEFORE the removal so a caller can see exactly what is gone. There
|
|
9200
|
+
* is no `commitMessage`: the local config is unversioned, so a deregister
|
|
9201
|
+
* stages nothing and leaves nothing to commit.
|
|
9202
|
+
* @public
|
|
9203
|
+
*/
|
|
9204
|
+
declare const ReposDeregisterResult: Schema.Struct<{
|
|
9205
|
+
readonly section: Schema.String;
|
|
9206
|
+
readonly removedKeys: Schema.$Array<Schema.String>;
|
|
9207
|
+
}>;
|
|
9208
|
+
/** @public */
|
|
9209
|
+
type ReposDeregisterResult = typeof ReposDeregisterResult.Type;
|
|
9191
9210
|
/**
|
|
9192
9211
|
* Result of an agent-note mutation against a vendored repo.
|
|
9193
9212
|
* @public
|
|
@@ -9233,6 +9252,24 @@ declare class ReposConfigStore extends ReposConfigStore_base {
|
|
|
9233
9252
|
}
|
|
9234
9253
|
//#endregion
|
|
9235
9254
|
//#region src/repos/services/drift.d.ts
|
|
9255
|
+
/**
|
|
9256
|
+
* Extracts the submodule NAME from a `submodule.<name>.<property>` config key.
|
|
9257
|
+
*
|
|
9258
|
+
* @remarks
|
|
9259
|
+
* A submodule's name is routinely a PATH (`git submodule add` derives the
|
|
9260
|
+
* section name from the path), so the name itself contains dots — which means
|
|
9261
|
+
* the key cannot be split on `.` and read positionally. Strip the fixed
|
|
9262
|
+
* `submodule.` prefix and the final `.<property>` segment instead; everything
|
|
9263
|
+
* between is the name, dots and all. Returns `undefined` for any key that is
|
|
9264
|
+
* not a `submodule.*.*` key.
|
|
9265
|
+
*
|
|
9266
|
+
* Shared with `ReposManager.deregister`, which must attribute config keys to
|
|
9267
|
+
* a registration name the exact same way this check does — two parsers here
|
|
9268
|
+
* would let the report and the remedy disagree about what a section is named.
|
|
9269
|
+
*
|
|
9270
|
+
* @internal
|
|
9271
|
+
*/
|
|
9272
|
+
declare const submoduleNameFromKey: (key: string) => string | undefined;
|
|
9236
9273
|
/**
|
|
9237
9274
|
* The {@link ReposDrift} service shape.
|
|
9238
9275
|
* @public
|
|
@@ -9424,6 +9461,31 @@ interface ReposManagerShape {
|
|
|
9424
9461
|
*/
|
|
9425
9462
|
readonly rename: (root: string, oldName: string, newName: string) => Effect.Effect<ReposRenameResult, ReposConfigError | GitSubmoduleError | RepoNotFoundError | ReposLockdownError>;
|
|
9426
9463
|
readonly restore: (root: string, names?: ReadonlyArray<string>) => Effect.Effect<ReposRestoreResult, ReposConfigError | GitSubmoduleError | RepoNotFoundError | ReposLockdownError>;
|
|
9464
|
+
/**
|
|
9465
|
+
* Clears a STALE submodule registration — a `submodule.<section>.*`
|
|
9466
|
+
* section in the superproject's LOCAL git config left behind by a rename
|
|
9467
|
+
* or an unvendoring, which `git submodule status` then lists as a phantom
|
|
9468
|
+
* entry the manifest has never heard of (`ReposDrift` reports it as
|
|
9469
|
+
* `localRegistrationDivergence`).
|
|
9470
|
+
*
|
|
9471
|
+
* `section` is the registration name exactly as the drift report states it
|
|
9472
|
+
* (e.g. `.repos/effect-old`) — the `submodule.` prefix is implied, never
|
|
9473
|
+
* passed. Refuses (typed `ReposConfigError`): a section outside
|
|
9474
|
+
* `.repos/` (this machinery does not own it — it may be a real submodule
|
|
9475
|
+
* of the host repo), the canonical registration of a live manifest entry,
|
|
9476
|
+
* and a registration whose module gitdir still backs a live entry under a
|
|
9477
|
+
* DIVERGED name (the same gitdir attribution `ReposDrift` uses — that
|
|
9478
|
+
* state's remedy is a re-vendor, not a deregister) — UNLESS that entry's
|
|
9479
|
+
* canonical section is also registered, in which case the old-named twin
|
|
9480
|
+
* is a genuine orphan and removing it is a crashed-rename recovery's own
|
|
9481
|
+
* final step. Reconciling a LIVE
|
|
9482
|
+
* registration is `sync`'s job and unvendoring is `remove`'s, so
|
|
9483
|
+
* deregistration only ever touches state nothing else owns. Touches only
|
|
9484
|
+
* the superproject's local config — no vendored worktree — so unlike the
|
|
9485
|
+
* other mutating ops it needs no lockdown bracket and stages nothing to
|
|
9486
|
+
* commit.
|
|
9487
|
+
*/
|
|
9488
|
+
readonly deregister: (root: string, section: string) => Effect.Effect<ReposDeregisterResult, ReposConfigError | GitSubmoduleError>;
|
|
9427
9489
|
}
|
|
9428
9490
|
declare const ReposManager_base: Context.ServiceClass<ReposManager, "@savvy-web/silk-effects/ReposManager", ReposManagerShape>;
|
|
9429
9491
|
/**
|
|
@@ -9431,8 +9493,10 @@ declare const ReposManager_base: Context.ServiceClass<ReposManager, "@savvy-web/
|
|
|
9431
9493
|
* (presence, dirtiness, stale notes), reconciles the working tree with the
|
|
9432
9494
|
* manifest, vendors new entries (`add`), re-pins existing entries to a new
|
|
9433
9495
|
* ref (`pin`), adds/removes/promotes agent notes (`note`), unvendors
|
|
9434
|
-
* (`remove`), renames (`rename`),
|
|
9435
|
-
* checkouts back to their pinned commit (`restore`)
|
|
9496
|
+
* (`remove`), renames (`rename`), explicitly hard-resets dirty
|
|
9497
|
+
* checkouts back to their pinned commit (`restore`), and clears stale
|
|
9498
|
+
* submodule registrations from the superproject's local git config
|
|
9499
|
+
* (`deregister`).
|
|
9436
9500
|
* @public
|
|
9437
9501
|
*/
|
|
9438
9502
|
declare class ReposManager extends ReposManager_base {
|
|
@@ -9450,7 +9514,7 @@ declare class ReposManager extends ReposManager_base {
|
|
|
9450
9514
|
static readonly layer: Layer.Layer<ReposManager, never, ReposConfigStore | Git | FileSystem.FileSystem | Path.Path | ReposLockdown>;
|
|
9451
9515
|
}
|
|
9452
9516
|
declare namespace index_d_exports$4 {
|
|
9453
|
-
export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NOTE_LIMIT, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreShape, ReposDrift, ReposDriftReport, ReposDriftShape, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposLockdownShape, ReposManager, ReposManagerShape, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, resolveModuleDir };
|
|
9517
|
+
export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NOTE_LIMIT, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposConfigStoreShape, ReposDeregisterResult, ReposDrift, ReposDriftReport, ReposDriftShape, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposLockdownShape, ReposManager, ReposManagerShape, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, resolveModuleDir, submoduleNameFromKey };
|
|
9454
9518
|
}
|
|
9455
9519
|
//#endregion
|
|
9456
9520
|
//#region src/schemas/BiomeConfig.d.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/silk-effects",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Effect library for Silk Suite conventions",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/silk-effects",
|
|
@@ -34,13 +34,13 @@
|
|
|
34
34
|
"@changesets/get-github-info": "^1.0.0",
|
|
35
35
|
"@changesets/get-release-plan": "^5.0.0",
|
|
36
36
|
"@effected/commands": "^0.4.0",
|
|
37
|
-
"@effected/git": "^0.
|
|
37
|
+
"@effected/git": "^0.8.0",
|
|
38
38
|
"@effected/glob": "^0.3.0",
|
|
39
39
|
"@effected/jsonc": "^0.6.0",
|
|
40
|
-
"@effected/package-json": "^0.
|
|
40
|
+
"@effected/package-json": "^0.9.0",
|
|
41
41
|
"@effected/templates": "^0.2.0",
|
|
42
42
|
"@effected/walker": "^0.4.0",
|
|
43
|
-
"@effected/workspaces": "^0.
|
|
43
|
+
"@effected/workspaces": "^0.13.0",
|
|
44
44
|
"@effected/yaml": "^0.8.0",
|
|
45
45
|
"@manypkg/get-packages": "^3.1.0",
|
|
46
46
|
"mdast-util-heading-range": "^4.0.0",
|
package/repos/index.js
CHANGED
|
@@ -3,10 +3,10 @@ import { MANIFEST_PATH, REPOS_DIR } from "./constants.js";
|
|
|
3
3
|
import { GitSubmoduleError, GitSubmoduleErrorBase, NoteNotFoundError, NoteNotFoundErrorBase, RepoNotFoundError, RepoNotFoundErrorBase, ReposConfigError, ReposConfigErrorBase, ReposLockdownError, ReposLockdownErrorBase } from "./errors.js";
|
|
4
4
|
import { DriftKind, RepoDrift, ReposDriftReport } from "./schemas/drift.js";
|
|
5
5
|
import { RepoEntry, RepoName, RepoNote, RepoOrientation, ReposManifestFile } from "./schemas/manifest.js";
|
|
6
|
-
import { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport } from "./schemas/reports.js";
|
|
6
|
+
import { RepoStatusEntry, ReposAddResult, ReposDeregisterResult, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport } from "./schemas/reports.js";
|
|
7
7
|
import { ReposConfigStore } from "./services/config-store.js";
|
|
8
8
|
import { ReposLockdown, resolveModuleDir } from "./services/lockdown.js";
|
|
9
|
-
import { ReposDrift } from "./services/drift.js";
|
|
9
|
+
import { ReposDrift, submoduleNameFromKey } from "./services/drift.js";
|
|
10
10
|
import { ReposManager, STALE_LOCK_MAX_AGE_MS } from "./services/manager.js";
|
|
11
11
|
|
|
12
12
|
//#region src/repos/index.ts
|
|
@@ -31,6 +31,7 @@ var repos_exports = /* @__PURE__ */ __exportAll({
|
|
|
31
31
|
ReposConfigError: () => ReposConfigError,
|
|
32
32
|
ReposConfigErrorBase: () => ReposConfigErrorBase,
|
|
33
33
|
ReposConfigStore: () => ReposConfigStore,
|
|
34
|
+
ReposDeregisterResult: () => ReposDeregisterResult,
|
|
34
35
|
ReposDrift: () => ReposDrift,
|
|
35
36
|
ReposDriftReport: () => ReposDriftReport,
|
|
36
37
|
ReposLockdown: () => ReposLockdown,
|
|
@@ -46,8 +47,9 @@ var repos_exports = /* @__PURE__ */ __exportAll({
|
|
|
46
47
|
ReposStatusReport: () => ReposStatusReport,
|
|
47
48
|
ReposSyncReport: () => ReposSyncReport,
|
|
48
49
|
STALE_LOCK_MAX_AGE_MS: () => STALE_LOCK_MAX_AGE_MS,
|
|
49
|
-
resolveModuleDir: () => resolveModuleDir
|
|
50
|
+
resolveModuleDir: () => resolveModuleDir,
|
|
51
|
+
submoduleNameFromKey: () => submoduleNameFromKey
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
//#endregion
|
|
53
|
-
export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposDrift, ReposDriftReport, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposManager, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repos_exports, resolveModuleDir };
|
|
55
|
+
export { DriftKind, GitSubmoduleError, GitSubmoduleErrorBase, MANIFEST_PATH, NoteNotFoundError, NoteNotFoundErrorBase, REPOS_DIR, RepoDrift, RepoEntry, RepoName, RepoNotFoundError, RepoNotFoundErrorBase, RepoNote, RepoOrientation, RepoStatusEntry, ReposAddResult, ReposConfigError, ReposConfigErrorBase, ReposConfigStore, ReposDeregisterResult, ReposDrift, ReposDriftReport, ReposLockdown, ReposLockdownError, ReposLockdownErrorBase, ReposManager, ReposManifestFile, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport, STALE_LOCK_MAX_AGE_MS, repos_exports, resolveModuleDir, submoduleNameFromKey };
|
package/repos/schemas/reports.js
CHANGED
|
@@ -150,6 +150,23 @@ const ReposRestoreResult = Schema.Struct({
|
|
|
150
150
|
stillDirty: Schema.Array(Schema.String)
|
|
151
151
|
});
|
|
152
152
|
/**
|
|
153
|
+
* Result of clearing a stale submodule registration from the superproject's
|
|
154
|
+
* LOCAL git config.
|
|
155
|
+
*
|
|
156
|
+
* @remarks
|
|
157
|
+
* `section` is the registration name the section was cleared under (e.g.
|
|
158
|
+
* `.repos/effect-old`), and `removedKeys` lists the full config keys the
|
|
159
|
+
* removed section actually carried — the achieved-state report, read from the
|
|
160
|
+
* config BEFORE the removal so a caller can see exactly what is gone. There
|
|
161
|
+
* is no `commitMessage`: the local config is unversioned, so a deregister
|
|
162
|
+
* stages nothing and leaves nothing to commit.
|
|
163
|
+
* @public
|
|
164
|
+
*/
|
|
165
|
+
const ReposDeregisterResult = Schema.Struct({
|
|
166
|
+
section: Schema.String,
|
|
167
|
+
removedKeys: Schema.Array(Schema.String)
|
|
168
|
+
});
|
|
169
|
+
/**
|
|
153
170
|
* Result of an agent-note mutation against a vendored repo.
|
|
154
171
|
* @public
|
|
155
172
|
*/
|
|
@@ -165,4 +182,4 @@ const ReposNoteResult = Schema.Struct({
|
|
|
165
182
|
});
|
|
166
183
|
|
|
167
184
|
//#endregion
|
|
168
|
-
export { RepoStatusEntry, ReposAddResult, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport };
|
|
185
|
+
export { RepoStatusEntry, ReposAddResult, ReposDeregisterResult, ReposNoteResult, ReposPinResult, ReposRemoveResult, ReposRenameResult, ReposRestoreResult, ReposStatusReport, ReposSyncReport };
|
package/repos/services/drift.js
CHANGED
|
@@ -17,6 +17,12 @@ import { Git, Gitmodules } from "@effected/git";
|
|
|
17
17
|
* `submodule.` prefix and the final `.<property>` segment instead; everything
|
|
18
18
|
* between is the name, dots and all. Returns `undefined` for any key that is
|
|
19
19
|
* not a `submodule.*.*` key.
|
|
20
|
+
*
|
|
21
|
+
* Shared with `ReposManager.deregister`, which must attribute config keys to
|
|
22
|
+
* a registration name the exact same way this check does — two parsers here
|
|
23
|
+
* would let the report and the remedy disagree about what a section is named.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
20
26
|
*/
|
|
21
27
|
const submoduleNameFromKey = (key) => {
|
|
22
28
|
if (!key.startsWith("submodule.")) return;
|
|
@@ -91,7 +97,7 @@ var ReposDrift = class extends Context.Service()("@savvy-web/silk-effects/ReposD
|
|
|
91
97
|
found.push(RepoDrift.make({
|
|
92
98
|
name: registeredName,
|
|
93
99
|
kind: "localRegistrationDivergence",
|
|
94
|
-
detail: `local git config registers submodule "${registeredName}", which matches no manifest entry — a stale registration left behind by a rename or an unvendoring; clear it with \`
|
|
100
|
+
detail: `local git config registers submodule "${registeredName}", which matches no manifest entry — a stale registration left behind by a rename or an unvendoring; clear it with \`savvy repos deregister ${registeredName}\``,
|
|
95
101
|
observedValue: registeredName
|
|
96
102
|
}));
|
|
97
103
|
}
|
|
@@ -247,4 +253,4 @@ var ReposDrift = class extends Context.Service()("@savvy-web/silk-effects/ReposD
|
|
|
247
253
|
};
|
|
248
254
|
|
|
249
255
|
//#endregion
|
|
250
|
-
export { ReposDrift };
|
|
256
|
+
export { ReposDrift, submoduleNameFromKey };
|
|
@@ -3,6 +3,7 @@ import { GitSubmoduleError, NoteNotFoundError, RepoNotFoundError, ReposConfigErr
|
|
|
3
3
|
import { RepoName } from "../schemas/manifest.js";
|
|
4
4
|
import { ReposConfigStore } from "./config-store.js";
|
|
5
5
|
import { ReposLockdown, resolveModuleDir } from "./lockdown.js";
|
|
6
|
+
import { submoduleNameFromKey } from "./drift.js";
|
|
6
7
|
import { Clock, Context, Effect, Exit, FileSystem, Layer, Option, Path, Result, Schema } from "effect";
|
|
7
8
|
import { Git, GitConfig, Gitmodules, LsRemoteEntry } from "@effected/git";
|
|
8
9
|
import { createHash } from "node:crypto";
|
|
@@ -31,8 +32,10 @@ const STALE_LOCK_MAX_AGE_MS = 6e5;
|
|
|
31
32
|
* (presence, dirtiness, stale notes), reconciles the working tree with the
|
|
32
33
|
* manifest, vendors new entries (`add`), re-pins existing entries to a new
|
|
33
34
|
* ref (`pin`), adds/removes/promotes agent notes (`note`), unvendors
|
|
34
|
-
* (`remove`), renames (`rename`),
|
|
35
|
-
* checkouts back to their pinned commit (`restore`)
|
|
35
|
+
* (`remove`), renames (`rename`), explicitly hard-resets dirty
|
|
36
|
+
* checkouts back to their pinned commit (`restore`), and clears stale
|
|
37
|
+
* submodule registrations from the superproject's local git config
|
|
38
|
+
* (`deregister`).
|
|
36
39
|
* @public
|
|
37
40
|
*/
|
|
38
41
|
var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/ReposManager") {
|
|
@@ -158,12 +161,12 @@ var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/Repo
|
|
|
158
161
|
yield* git.add(root, [".gitmodules", repoPathRel]).pipe(Effect.mapError(asSubmoduleError(`git add .gitmodules ${repoPathRel}`, root)));
|
|
159
162
|
registered.push(name);
|
|
160
163
|
} else if (!present) {
|
|
161
|
-
yield* git.configSet(root, updateKey, "checkout").pipe(Effect.mapError(asSubmoduleError(`git config ${updateKey} checkout`, root)));
|
|
162
164
|
yield* git.submoduleUpdate(root, {
|
|
163
165
|
init: true,
|
|
166
|
+
checkout: true,
|
|
164
167
|
depth: 1,
|
|
165
168
|
paths: [repoPathRel]
|
|
166
|
-
}).pipe(Effect.mapError(asSubmoduleError(`git submodule update --init --depth 1 -- ${repoPathRel}`, root))
|
|
169
|
+
}).pipe(Effect.mapError(asSubmoduleError(`git submodule update --init --checkout --depth 1 -- ${repoPathRel}`, root)));
|
|
167
170
|
initialized.push(name);
|
|
168
171
|
} else upToDate.push(name);
|
|
169
172
|
if (entry.sparse && entry.sparse.length > 0) {
|
|
@@ -647,6 +650,70 @@ var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/Repo
|
|
|
647
650
|
stillDirty
|
|
648
651
|
};
|
|
649
652
|
});
|
|
653
|
+
/**
|
|
654
|
+
* Resolve the file git's `--local` scope actually targets for `root`.
|
|
655
|
+
* A plain checkout is `<root>/.git/config`; a LINKED WORKTREE's
|
|
656
|
+
* `<root>/.git` is a pointer FILE whose `gitdir:` names
|
|
657
|
+
* `.git/worktrees/<name>`, and the local config git uses there is the
|
|
658
|
+
* SHARED one at the main gitdir named by that dir's `commondir` file.
|
|
659
|
+
* Every unreadable/malformed step degrades to the plain
|
|
660
|
+
* `<root>/.git/config` path rather than failing — the same
|
|
661
|
+
* never-fails posture as `resolveModuleDir`.
|
|
662
|
+
*/
|
|
663
|
+
const resolveLocalConfigPath = (root) => Effect.gen(function* () {
|
|
664
|
+
const dotGit = path.join(root, ".git");
|
|
665
|
+
const fallback = path.join(dotGit, "config");
|
|
666
|
+
const info = yield* fs.stat(dotGit).pipe(Effect.option);
|
|
667
|
+
if (Option.isNone(info) || info.value.type === "Directory") return fallback;
|
|
668
|
+
const content = yield* fs.readFileString(dotGit).pipe(Effect.option);
|
|
669
|
+
if (Option.isNone(content)) return fallback;
|
|
670
|
+
const match = /^gitdir:\s*(.+)$/m.exec(content.value);
|
|
671
|
+
if (!match?.[1]) return fallback;
|
|
672
|
+
const pointer = match[1].trim();
|
|
673
|
+
const gitdir = path.isAbsolute(pointer) ? pointer : path.resolve(root, pointer);
|
|
674
|
+
const commondir = yield* fs.readFileString(path.join(gitdir, "commondir")).pipe(Effect.option);
|
|
675
|
+
if (Option.isSome(commondir)) {
|
|
676
|
+
const common = commondir.value.trim();
|
|
677
|
+
return path.join(path.isAbsolute(common) ? common : path.resolve(gitdir, common), "config");
|
|
678
|
+
}
|
|
679
|
+
return path.join(gitdir, "config");
|
|
680
|
+
});
|
|
681
|
+
const deregister = (root, section) => Effect.gen(function* () {
|
|
682
|
+
if (!section.startsWith(`${".repos"}/`) || section === `${".repos"}/`) return yield* Effect.fail(new ReposConfigError({
|
|
683
|
+
path: MANIFEST_PATH,
|
|
684
|
+
reason: `"${section}" is not a ${REPOS_DIR}/ registration — deregister only clears stale vendored-repo sections; clear an unrelated submodule registration with git directly`,
|
|
685
|
+
kind: "invalid"
|
|
686
|
+
}));
|
|
687
|
+
const manifest = yield* configStore.read(root).pipe(Effect.catchTag("ReposConfigError", (error) => error.kind === "missing" ? Effect.succeed({ repos: {} }) : Effect.fail(error)));
|
|
688
|
+
for (const name of Object.keys(manifest.repos)) if (section === `${".repos"}/${name}`) return yield* Effect.fail(new ReposConfigError({
|
|
689
|
+
path: MANIFEST_PATH,
|
|
690
|
+
reason: `"${section}" is the canonical registration of manifest entry "${name}" — deregister clears STALE registrations only; use remove to unvendor, or sync to reconcile`,
|
|
691
|
+
kind: "invalid"
|
|
692
|
+
}));
|
|
693
|
+
const localConfigPath = yield* resolveLocalConfigPath(root);
|
|
694
|
+
const configEntries = yield* git.configList(root, { file: localConfigPath }).pipe(Effect.mapError(asSubmoduleError(`git config -f ${localConfigPath} --list`, root)));
|
|
695
|
+
const registeredNames = new Set(configEntries.map((configEntry) => submoduleNameFromKey(configEntry.key)).filter((registeredName) => registeredName !== void 0));
|
|
696
|
+
const removedKeys = configEntries.map((configEntry) => configEntry.key).filter((key) => submoduleNameFromKey(key) === section);
|
|
697
|
+
if (removedKeys.length === 0) return yield* Effect.fail(new ReposConfigError({
|
|
698
|
+
path: localConfigPath,
|
|
699
|
+
reason: `no submodule.${section} section registered in the local git config — nothing to deregister`,
|
|
700
|
+
kind: "invalid"
|
|
701
|
+
}));
|
|
702
|
+
const modulesRoot = path.join(root, ".git", "modules");
|
|
703
|
+
for (const name of Object.keys(manifest.repos)) {
|
|
704
|
+
const moduleDir = yield* resolveModuleDir(fs, path, root, name);
|
|
705
|
+
if (path.relative(modulesRoot, moduleDir) === section && !registeredNames.has(`${".repos"}/${name}`)) return yield* Effect.fail(new ReposConfigError({
|
|
706
|
+
path: MANIFEST_PATH,
|
|
707
|
+
reason: `"${section}" is the registration backing manifest entry "${name}" (its module gitdir lives there under a diverged name, and no canonical submodule.${REPOS_DIR}/${name} registration exists) — deregister clears STALE registrations only; re-vendor the entry instead (remove, then add with the orientation the remove result hands back)`,
|
|
708
|
+
kind: "invalid"
|
|
709
|
+
}));
|
|
710
|
+
}
|
|
711
|
+
yield* git.configRemoveSection(root, `submodule.${section}`).pipe(Effect.mapError(asSubmoduleError(`git config --remove-section submodule.${section}`, root)));
|
|
712
|
+
return {
|
|
713
|
+
section,
|
|
714
|
+
removedKeys
|
|
715
|
+
};
|
|
716
|
+
});
|
|
650
717
|
return {
|
|
651
718
|
status,
|
|
652
719
|
sync,
|
|
@@ -655,7 +722,8 @@ var ReposManager = class extends Context.Service()("@savvy-web/silk-effects/Repo
|
|
|
655
722
|
note,
|
|
656
723
|
remove,
|
|
657
724
|
rename,
|
|
658
|
-
restore
|
|
725
|
+
restore,
|
|
726
|
+
deregister
|
|
659
727
|
};
|
|
660
728
|
}));
|
|
661
729
|
};
|