@ozyman42/ozy-cli 0.4.8 → 0.4.9-darwin-arm64.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/multi-call-binary +0 -0
- package/package.json +11 -52
- package/bin/ozy +0 -191
- package/bin/ozy-signing-agent +0 -191
- package/bin/ozy-ssh-keygen +0 -191
- package/bin/ozy-virtual-security-key +0 -191
- package/dist/commands/git/hosts.js +0 -18
- package/dist/commands/git/hosts.js.map +0 -1
- package/dist/commands/git/index.js +0 -8
- package/dist/commands/git/index.js.map +0 -1
- package/dist/commands/git/setup.js +0 -145
- package/dist/commands/git/setup.js.map +0 -1
- package/dist/commands/index.js +0 -13
- package/dist/commands/index.js.map +0 -1
- package/dist/commands/npm/index.js +0 -7
- package/dist/commands/npm/index.js.map +0 -1
- package/dist/commands/npm/setup/git-remote.js +0 -66
- package/dist/commands/npm/setup/git-remote.js.map +0 -1
- package/dist/commands/npm/setup/index.js +0 -168
- package/dist/commands/npm/setup/index.js.map +0 -1
- package/dist/commands/npm/setup/npm-auth.js +0 -62
- package/dist/commands/npm/setup/npm-auth.js.map +0 -1
- package/dist/commands/npm/setup/package-json-validate.js +0 -47
- package/dist/commands/npm/setup/package-json-validate.js.map +0 -1
- package/dist/commands/npm/setup/package-registry.js +0 -20
- package/dist/commands/npm/setup/package-registry.js.map +0 -1
- package/dist/commands/npm/setup/setup.test.js +0 -95
- package/dist/commands/npm/setup/setup.test.js.map +0 -1
- package/dist/commands/npm/setup/trusted-publishing.js +0 -84
- package/dist/commands/npm/setup/trusted-publishing.js.map +0 -1
- package/dist/commands/npm/setup/workflow.js +0 -19
- package/dist/commands/npm/setup/workflow.js.map +0 -1
- package/dist/commands/ssh/get-sk-credential.js +0 -104
- package/dist/commands/ssh/get-sk-credential.js.map +0 -1
- package/dist/commands/ssh/index.js +0 -7
- package/dist/commands/ssh/index.js.map +0 -1
- package/dist/commands/upgrade.js +0 -199
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/common/command.js +0 -15
- package/dist/common/command.js.map +0 -1
- package/dist/common/constants.js +0 -25
- package/dist/common/constants.js.map +0 -1
- package/dist/common/effective-modules-extensions.js +0 -13
- package/dist/common/effective-modules-extensions.js.map +0 -1
- package/dist/common/log.js +0 -4
- package/dist/common/log.js.map +0 -1
- package/dist/common/render-caller-tree.js +0 -20
- package/dist/common/render-caller-tree.js.map +0 -1
- package/dist/entrypoints/ozy-signing-agent.js +0 -48
- package/dist/entrypoints/ozy-signing-agent.js.map +0 -1
- package/dist/entrypoints/ozy-ssh-keygen.js +0 -9
- package/dist/entrypoints/ozy-ssh-keygen.js.map +0 -1
- package/dist/entrypoints/ozy-virtual-security-key.js +0 -18
- package/dist/entrypoints/ozy-virtual-security-key.js.map +0 -1
- package/dist/entrypoints/ozy.js +0 -3
- package/dist/entrypoints/ozy.js.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/dist/internal/thing.js +0 -4
- package/dist/internal/thing.js.map +0 -1
- package/dist/modules/cli/agent-client/impl.js +0 -109
- package/dist/modules/cli/agent-client/impl.js.map +0 -1
- package/dist/modules/cli/agent-client/interface.js +0 -11
- package/dist/modules/cli/agent-client/interface.js.map +0 -1
- package/dist/modules/cli/git/impl.js +0 -124
- package/dist/modules/cli/git/impl.js.map +0 -1
- package/dist/modules/cli/git/interface.js +0 -3
- package/dist/modules/cli/git/interface.js.map +0 -1
- package/dist/modules/cli/git/submodule/fix.js +0 -257
- package/dist/modules/cli/git/submodule/fix.js.map +0 -1
- package/dist/modules/cli/git/submodule/git-state.js +0 -154
- package/dist/modules/cli/git/submodule/git-state.js.map +0 -1
- package/dist/modules/cli/git/submodule/gitmodules.js +0 -113
- package/dist/modules/cli/git/submodule/gitmodules.js.map +0 -1
- package/dist/modules/cli/git/submodule/printer.js +0 -232
- package/dist/modules/cli/git/submodule/printer.js.map +0 -1
- package/dist/modules/cli/git/submodule/resolve-git-dir.js +0 -16
- package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +0 -1
- package/dist/modules/cli/github/impl.js +0 -137
- package/dist/modules/cli/github/impl.js.map +0 -1
- package/dist/modules/cli/github/interface.js +0 -2
- package/dist/modules/cli/github/interface.js.map +0 -1
- package/dist/modules/cli/index.js +0 -9
- package/dist/modules/cli/index.js.map +0 -1
- package/dist/modules/common/crypto/impl.js +0 -133
- package/dist/modules/common/crypto/impl.js.map +0 -1
- package/dist/modules/common/crypto/interface.js +0 -2
- package/dist/modules/common/crypto/interface.js.map +0 -1
- package/dist/modules/common/index.js +0 -10
- package/dist/modules/common/index.js.map +0 -1
- package/dist/modules/common/kep-map-store/impl.js +0 -42
- package/dist/modules/common/kep-map-store/impl.js.map +0 -1
- package/dist/modules/common/kep-map-store/interface.js +0 -2
- package/dist/modules/common/kep-map-store/interface.js.map +0 -1
- package/dist/modules/common/os-platform/caller-info.js +0 -132
- package/dist/modules/common/os-platform/caller-info.js.map +0 -1
- package/dist/modules/common/os-platform/impl.js +0 -95
- package/dist/modules/common/os-platform/impl.js.map +0 -1
- package/dist/modules/common/os-platform/interface.js +0 -2
- package/dist/modules/common/os-platform/interface.js.map +0 -1
- package/dist/modules/common/os-platform/virtual-hid/index.js +0 -12
- package/dist/modules/common/os-platform/virtual-hid/index.js.map +0 -1
- package/dist/modules/common/os-platform/virtual-hid/linux.js +0 -5
- package/dist/modules/common/os-platform/virtual-hid/linux.js.map +0 -1
- package/dist/modules/common/os-platform/virtual-hid/mac.js +0 -119
- package/dist/modules/common/os-platform/virtual-hid/mac.js.map +0 -1
- package/dist/modules/common/os-platform/virtual-hid/windows.js +0 -5
- package/dist/modules/common/os-platform/virtual-hid/windows.js.map +0 -1
- package/dist/modules/common/ssh-config/impl.js +0 -116
- package/dist/modules/common/ssh-config/impl.js.map +0 -1
- package/dist/modules/common/ssh-config/interface.js +0 -10
- package/dist/modules/common/ssh-config/interface.js.map +0 -1
- package/dist/modules/ssh-agent/index.js +0 -8
- package/dist/modules/ssh-agent/index.js.map +0 -1
- package/dist/modules/ssh-agent/session/impl.js +0 -265
- package/dist/modules/ssh-agent/session/impl.js.map +0 -1
- package/dist/modules/ssh-agent/session/interface-rpc.js +0 -20
- package/dist/modules/ssh-agent/session/interface-rpc.js.map +0 -1
- package/dist/modules/ssh-agent/session/interface.js +0 -32
- package/dist/modules/ssh-agent/session/interface.js.map +0 -1
- package/dist/modules/ssh-agent/session/passkey-prf-page.js +0 -204
- package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +0 -1
- package/dist/modules/ssh-agent/session/prf-flow.js +0 -103
- package/dist/modules/ssh-agent/session/prf-flow.js.map +0 -1
- package/dist/modules/ssh-agent/ssh-agent/impl.js +0 -117
- package/dist/modules/ssh-agent/ssh-agent/impl.js.map +0 -1
- package/dist/modules/ssh-agent/ssh-agent/interface.js +0 -2
- package/dist/modules/ssh-agent/ssh-agent/interface.js.map +0 -1
- package/dist/scripts/build.js +0 -410
- package/dist/scripts/build.js.map +0 -1
- package/dist/scripts/check-npm-version.js +0 -8
- package/dist/scripts/check-npm-version.js.map +0 -1
- package/dist/scripts/kill-existing-agent.js +0 -21
- package/dist/scripts/kill-existing-agent.js.map +0 -1
- package/dist/scripts/publish.js +0 -46
- package/dist/scripts/publish.js.map +0 -1
- package/src/commands/git/hosts.ts +0 -20
- package/src/commands/git/index.ts +0 -9
- package/src/commands/git/setup.ts +0 -182
- package/src/commands/index.ts +0 -15
- package/src/commands/npm/index.ts +0 -8
- package/src/commands/npm/setup/git-remote.ts +0 -78
- package/src/commands/npm/setup/index.ts +0 -189
- package/src/commands/npm/setup/npm-auth.ts +0 -58
- package/src/commands/npm/setup/package-json-validate.ts +0 -68
- package/src/commands/npm/setup/package-registry.ts +0 -24
- package/src/commands/npm/setup/publish-workflow.yml +0 -35
- package/src/commands/npm/setup/setup.test.ts +0 -117
- package/src/commands/npm/setup/trusted-publishing.ts +0 -106
- package/src/commands/npm/setup/workflow.ts +0 -20
- package/src/commands/ssh/get-sk-credential.ts +0 -110
- package/src/commands/ssh/index.ts +0 -8
- package/src/commands/upgrade.ts +0 -220
- package/src/common/command.ts +0 -16
- package/src/common/constants.ts +0 -26
- package/src/common/effective-modules-extensions.ts +0 -24
- package/src/common/log.ts +0 -3
- package/src/common/render-caller-tree.ts +0 -22
- package/src/entrypoints/ozy-signing-agent.ts +0 -48
- package/src/entrypoints/ozy-ssh-keygen.ts +0 -10
- package/src/entrypoints/ozy-virtual-security-key.ts +0 -21
- package/src/entrypoints/ozy.ts +0 -2
- package/src/index.ts +0 -1
- package/src/internal/thing.ts +0 -3
- package/src/modules/cli/agent-client/impl.ts +0 -152
- package/src/modules/cli/agent-client/interface.ts +0 -17
- package/src/modules/cli/git/impl.ts +0 -133
- package/src/modules/cli/git/interface.ts +0 -23
- package/src/modules/cli/git/submodule/fix.ts +0 -298
- package/src/modules/cli/git/submodule/git-state.ts +0 -217
- package/src/modules/cli/git/submodule/gitmodules.ts +0 -126
- package/src/modules/cli/git/submodule/printer.ts +0 -256
- package/src/modules/cli/git/submodule/resolve-git-dir.ts +0 -14
- package/src/modules/cli/github/impl.ts +0 -169
- package/src/modules/cli/github/interface.ts +0 -29
- package/src/modules/cli/index.ts +0 -16
- package/src/modules/common/crypto/impl.ts +0 -173
- package/src/modules/common/crypto/interface.ts +0 -20
- package/src/modules/common/index.ts +0 -19
- package/src/modules/common/kep-map-store/impl.ts +0 -47
- package/src/modules/common/kep-map-store/interface.ts +0 -9
- package/src/modules/common/os-platform/caller-info.ts +0 -152
- package/src/modules/common/os-platform/impl.ts +0 -91
- package/src/modules/common/os-platform/interface.ts +0 -16
- package/src/modules/common/os-platform/virtual-hid/index.ts +0 -12
- package/src/modules/common/os-platform/virtual-hid/linux.ts +0 -7
- package/src/modules/common/os-platform/virtual-hid/mac.ts +0 -150
- package/src/modules/common/os-platform/virtual-hid/windows.ts +0 -7
- package/src/modules/common/ssh-config/impl.ts +0 -130
- package/src/modules/common/ssh-config/interface.ts +0 -33
- package/src/modules/ssh-agent/index.ts +0 -13
- package/src/modules/ssh-agent/session/agent-notes.md +0 -210
- package/src/modules/ssh-agent/session/design.md +0 -2
- package/src/modules/ssh-agent/session/html-refactor.md +0 -23
- package/src/modules/ssh-agent/session/impl.ts +0 -328
- package/src/modules/ssh-agent/session/interface-rpc.ts +0 -24
- package/src/modules/ssh-agent/session/interface.ts +0 -62
- package/src/modules/ssh-agent/session/passkey-prf-page.ts +0 -224
- package/src/modules/ssh-agent/session/prf-flow.ts +0 -152
- package/src/modules/ssh-agent/ssh-agent/impl.ts +0 -136
- package/src/modules/ssh-agent/ssh-agent/interface.ts +0 -9
- package/src/scripts/build.ts +0 -504
- package/src/scripts/check-npm-version.ts +0 -7
- package/src/scripts/command-launcher.js.ejs +0 -168
- package/src/scripts/kill-existing-agent.ts +0 -22
- package/src/scripts/publish.ts +0 -47
- package/src/scripts/windows-bin-shims.md +0 -1104
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import { existsSync, statSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile, rename, mkdir, rm } from "node:fs/promises";
|
|
3
|
-
import { resolve, join, dirname, relative } from "node:path";
|
|
4
|
-
import { Effect } from "effect";
|
|
5
|
-
import type { Gitmodules } from "./gitmodules";
|
|
6
|
-
import type { ModuleDirInfo, IndexGitlink } from "./git-state";
|
|
7
|
-
import { resolveGitDir } from "./resolve-git-dir";
|
|
8
|
-
|
|
9
|
-
export interface Fix {
|
|
10
|
-
location: string[];
|
|
11
|
-
action: "synced" | "deleted";
|
|
12
|
-
detail?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function parseConfigSubmoduleValues(content: string): Map<string, { url?: string; path?: string }> {
|
|
16
|
-
const entries = new Map<string, { url?: string; path?: string }>();
|
|
17
|
-
let current: { url?: string; path?: string } | null = null;
|
|
18
|
-
|
|
19
|
-
for (const line of content.split("\n")) {
|
|
20
|
-
const headerMatch = line.match(/^\[submodule "(.+)"\]$/);
|
|
21
|
-
if (headerMatch) {
|
|
22
|
-
current = {};
|
|
23
|
-
entries.set(headerMatch[1]!, current);
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
if (!current) continue;
|
|
27
|
-
const kvMatch = line.match(/^\s+(\w+)\s*=\s*(.+)$/);
|
|
28
|
-
if (!kvMatch) continue;
|
|
29
|
-
const [, key, value] = kvMatch;
|
|
30
|
-
if (key === "url" || key === "path") current[key] = value!.trim();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return entries;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Drops any stanza whose name isn't a valid submodule, is a stale duplicate, or
|
|
37
|
-
// has url/path values that no longer match .gitmodules — so it can be re-added fresh.
|
|
38
|
-
function removeExtraConfigSubmodules(content: string, modules: Gitmodules): { result: string; removed: string[] } {
|
|
39
|
-
const byName = new Map(modules.list.map((m) => [m.name, m]));
|
|
40
|
-
const existingValues = parseConfigSubmoduleValues(content);
|
|
41
|
-
|
|
42
|
-
const staleNames = new Set<string>();
|
|
43
|
-
for (const [name, values] of existingValues) {
|
|
44
|
-
const m = byName.get(name);
|
|
45
|
-
if (!m || values.url !== m.url || values.path !== m.path) staleNames.add(name);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const removed: string[] = [];
|
|
49
|
-
const seen = new Set<string>();
|
|
50
|
-
const output: string[] = [];
|
|
51
|
-
let dropping = false;
|
|
52
|
-
|
|
53
|
-
for (const line of content.split("\n")) {
|
|
54
|
-
const submoduleMatch = line.match(/^\[submodule "(.+)"\]$/);
|
|
55
|
-
if (submoduleMatch) {
|
|
56
|
-
const name = submoduleMatch[1]!;
|
|
57
|
-
if (!staleNames.has(name) && !seen.has(name)) {
|
|
58
|
-
seen.add(name);
|
|
59
|
-
dropping = false;
|
|
60
|
-
} else {
|
|
61
|
-
removed.push(name);
|
|
62
|
-
dropping = true;
|
|
63
|
-
}
|
|
64
|
-
} else if (/^\[/.test(line)) {
|
|
65
|
-
dropping = false;
|
|
66
|
-
}
|
|
67
|
-
if (!dropping) output.push(line);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return { result: output.join("\n"), removed };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function spawnGit(args: string[], cwd: string): Promise<void> {
|
|
74
|
-
const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
75
|
-
const code = await proc.exited;
|
|
76
|
-
if (code !== 0) {
|
|
77
|
-
const err = await new Response(proc.stderr).text();
|
|
78
|
-
throw new Error(`git ${args.join(" ")} exited with code ${code}\n${err.trim()}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function fixGitConfig(repoRoot: string, modules: Gitmodules): Effect.Effect<Fix[], string> {
|
|
83
|
-
return Effect.tryPromise({
|
|
84
|
-
try: async () => {
|
|
85
|
-
const gitDir = await resolveGitDir(repoRoot);
|
|
86
|
-
const configPath = resolve(gitDir, "config");
|
|
87
|
-
const content = await readFile(configPath, "utf8");
|
|
88
|
-
const { result: afterRemoval, removed } = removeExtraConfigSubmodules(content, modules);
|
|
89
|
-
|
|
90
|
-
const existingNames = new Set<string>(
|
|
91
|
-
[...afterRemoval.matchAll(/^\[submodule "(.+)"\]$/gm)].map((m) => m[1]!)
|
|
92
|
-
);
|
|
93
|
-
const missing = modules.list.filter((m) => !existingNames.has(m.name));
|
|
94
|
-
|
|
95
|
-
let result = afterRemoval;
|
|
96
|
-
for (const m of missing) {
|
|
97
|
-
if (!result.endsWith("\n")) result += "\n";
|
|
98
|
-
result += `[submodule "${m.name}"]\n\tactive = true\n\turl = ${m.url}\n\tpath = ${m.path}\n`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (removed.length === 0 && missing.length === 0) return [];
|
|
102
|
-
await writeFile(configPath, result, "utf8");
|
|
103
|
-
return [
|
|
104
|
-
...removed.map((name) => ({ location: [".git", "config", name], action: "deleted" as const })),
|
|
105
|
-
...missing.map((m) => ({ location: [".git", "config", m.name], action: "synced" as const })),
|
|
106
|
-
];
|
|
107
|
-
},
|
|
108
|
-
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const GIT_MODULES_MARKER = ".git/modules/";
|
|
113
|
-
|
|
114
|
-
// A stale redirect's leading "../" prefix can be wrong after a path rename, but the
|
|
115
|
-
// suffix starting at ".git/modules/" always describes a location under the single
|
|
116
|
-
// top-level .git, at any nesting depth — so anchor on that marker instead of trusting
|
|
117
|
-
// the relative resolution.
|
|
118
|
-
function recoverStaleGitDirTarget(redirectContent: string, topLevelRoot: string): string | undefined {
|
|
119
|
-
const match = redirectContent.match(/^gitdir:\s*(.+)$/m);
|
|
120
|
-
if (!match) return undefined;
|
|
121
|
-
const idx = match[1]!.indexOf(GIT_MODULES_MARKER);
|
|
122
|
-
if (idx === -1) return undefined;
|
|
123
|
-
return resolve(topLevelRoot, match[1]!.slice(idx).trim());
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Moves existing git data into moduleGitDir instead of abandoning it, whether it's a
|
|
127
|
-
// standalone clone's literal .git directory, or a redirect file (even a stale one
|
|
128
|
-
// left over from a path rename) pointing at git data that still exists.
|
|
129
|
-
async function migrateExistingGitDir(workingTreeDir: string, moduleGitDir: string, topLevelRoot: string): Promise<boolean> {
|
|
130
|
-
const dotGit = join(workingTreeDir, ".git");
|
|
131
|
-
if (!existsSync(dotGit)) return false;
|
|
132
|
-
|
|
133
|
-
if (statSync(dotGit).isDirectory()) {
|
|
134
|
-
await mkdir(dirname(moduleGitDir), { recursive: true });
|
|
135
|
-
await rename(dotGit, moduleGitDir);
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const content = await readFile(dotGit, "utf8");
|
|
140
|
-
const target = recoverStaleGitDirTarget(content, topLevelRoot);
|
|
141
|
-
if (!target || target === moduleGitDir || !existsSync(target)) return false;
|
|
142
|
-
await mkdir(dirname(moduleGitDir), { recursive: true });
|
|
143
|
-
await rename(target, moduleGitDir);
|
|
144
|
-
return true;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function fixModuleDirs(
|
|
148
|
-
repoRoot: string,
|
|
149
|
-
modules: Gitmodules,
|
|
150
|
-
moduleDirs: ModuleDirInfo[],
|
|
151
|
-
orphanModuleDirs: string[],
|
|
152
|
-
topLevelRoot: string,
|
|
153
|
-
): Effect.Effect<Fix[], string> {
|
|
154
|
-
return Effect.tryPromise({
|
|
155
|
-
try: async () => {
|
|
156
|
-
const fixes: Fix[] = [];
|
|
157
|
-
const modulesRoot = resolve(await resolveGitDir(repoRoot), "modules");
|
|
158
|
-
|
|
159
|
-
// Establish each module's git dir (migrating existing data if any exists
|
|
160
|
-
// anywhere) before the orphan cleanup below runs, so nothing real gets deleted
|
|
161
|
-
// out from under a path rename.
|
|
162
|
-
for (const m of modules.list) {
|
|
163
|
-
const workingTreeDir = resolve(repoRoot, m.path);
|
|
164
|
-
const moduleGitDir = join(modulesRoot, m.path);
|
|
165
|
-
if (!existsSync(workingTreeDir)) continue;
|
|
166
|
-
|
|
167
|
-
const alreadyEstablished = existsSync(moduleGitDir);
|
|
168
|
-
let freshlyCloned = false;
|
|
169
|
-
|
|
170
|
-
if (!alreadyEstablished) {
|
|
171
|
-
const migrated = await migrateExistingGitDir(workingTreeDir, moduleGitDir, topLevelRoot);
|
|
172
|
-
if (migrated) {
|
|
173
|
-
fixes.push({ location: [".git", "modules", ...m.path.split("/")], action: "synced", detail: "migrated" });
|
|
174
|
-
} else {
|
|
175
|
-
await mkdir(dirname(moduleGitDir), { recursive: true });
|
|
176
|
-
await spawnGit(["clone", "--bare", m.url, moduleGitDir], repoRoot);
|
|
177
|
-
fixes.push({ location: [".git", "modules", ...m.path.split("/")], action: "synced", detail: "initialized" });
|
|
178
|
-
freshlyCloned = true;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (freshlyCloned) {
|
|
183
|
-
const configFile = join(moduleGitDir, "config");
|
|
184
|
-
const worktree = relative(moduleGitDir, workingTreeDir);
|
|
185
|
-
await spawnGit(["config", "-f", configFile, "core.bare", "false"], repoRoot);
|
|
186
|
-
await spawnGit(["config", "-f", configFile, "core.worktree", worktree], repoRoot);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// A bare clone (whether just made above, or left over from a prior buggy
|
|
190
|
-
// run) never gets an index — populate it from HEAD whenever it's missing,
|
|
191
|
-
// not only right after we ourselves created it.
|
|
192
|
-
if (!existsSync(join(moduleGitDir, "index"))) {
|
|
193
|
-
const headCheck = Bun.spawn(["git", "--git-dir", moduleGitDir, "rev-parse", "--verify", "HEAD"], { stdout: "ignore", stderr: "ignore" });
|
|
194
|
-
if ((await headCheck.exited) !== 0) await spawnGit(["--git-dir", moduleGitDir, "fetch", "origin"], repoRoot);
|
|
195
|
-
await spawnGit(["--git-dir", moduleGitDir, "read-tree", "HEAD"], repoRoot);
|
|
196
|
-
fixes.push({ location: [".git", "modules", ...m.path.split("/")], action: "synced", detail: "populated index" });
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Always re-point the working tree's .git redirect — never assume an
|
|
200
|
-
// existing file is already correct.
|
|
201
|
-
const dotGit = join(workingTreeDir, ".git");
|
|
202
|
-
const rel = relative(workingTreeDir, moduleGitDir);
|
|
203
|
-
const desired = `gitdir: ${rel}\n`;
|
|
204
|
-
const current = existsSync(dotGit) && !statSync(dotGit).isDirectory()
|
|
205
|
-
? await readFile(dotGit, "utf8")
|
|
206
|
-
: undefined;
|
|
207
|
-
if (current !== desired) {
|
|
208
|
-
await writeFile(dotGit, desired, "utf8");
|
|
209
|
-
fixes.push({ location: [...m.path.split("/"), ".git"], action: "synced" });
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const gmByPath = new Map(modules.list.map((m) => [m.path, m]));
|
|
214
|
-
|
|
215
|
-
for (const d of moduleDirs) {
|
|
216
|
-
const gm = gmByPath.get(d.relativePath);
|
|
217
|
-
if (!gm && existsSync(join(modulesRoot, d.relativePath))) {
|
|
218
|
-
await rm(join(modulesRoot, d.relativePath), { recursive: true, force: true });
|
|
219
|
-
fixes.push({ location: [".git", "modules", ...d.relativePath.split("/")], action: "deleted" });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
for (const rel of orphanModuleDirs) {
|
|
224
|
-
if (existsSync(join(modulesRoot, rel))) {
|
|
225
|
-
await rm(join(modulesRoot, rel), { recursive: true, force: true });
|
|
226
|
-
fixes.push({ location: [".git", "modules", ...rel.split("/")], action: "deleted" });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
for (const m of modules.list) {
|
|
231
|
-
const moduleGitDir = join(modulesRoot, m.path);
|
|
232
|
-
if (!existsSync(moduleGitDir)) continue;
|
|
233
|
-
const configFile = join(moduleGitDir, "config");
|
|
234
|
-
|
|
235
|
-
const readConfig = (key: string) =>
|
|
236
|
-
Bun.spawn(["git", "config", "-f", configFile, key], { cwd: repoRoot, stdout: "pipe", stderr: "pipe" });
|
|
237
|
-
|
|
238
|
-
const [currentUrl, currentWorktree] = await Promise.all([
|
|
239
|
-
new Response(readConfig("remote.origin.url").stdout).text().then(t => t.trim()),
|
|
240
|
-
new Response(readConfig("core.worktree").stdout).text().then(t => t.trim()),
|
|
241
|
-
]);
|
|
242
|
-
|
|
243
|
-
const worktree = relative(moduleGitDir, resolve(repoRoot, m.path));
|
|
244
|
-
|
|
245
|
-
if (currentUrl !== m.url) {
|
|
246
|
-
await spawnGit(["config", "-f", configFile, "remote.origin.url", m.url], repoRoot);
|
|
247
|
-
fixes.push({ location: [".git", "modules", ...m.path.split("/"), "config"], action: "synced", detail: "remote.origin.url" });
|
|
248
|
-
}
|
|
249
|
-
if (currentWorktree !== worktree) {
|
|
250
|
-
await spawnGit(["config", "-f", configFile, "core.worktree", worktree], repoRoot);
|
|
251
|
-
fixes.push({ location: [".git", "modules", ...m.path.split("/"), "config"], action: "synced", detail: "core.worktree" });
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return fixes;
|
|
256
|
-
},
|
|
257
|
-
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export function fixIndexGitlinks(
|
|
262
|
-
repoRoot: string,
|
|
263
|
-
modules: Gitmodules,
|
|
264
|
-
currentIndex: IndexGitlink[],
|
|
265
|
-
): Effect.Effect<Fix[], string> {
|
|
266
|
-
return Effect.tryPromise({
|
|
267
|
-
try: async () => {
|
|
268
|
-
const currentShas = new Map(currentIndex.map((e) => [e.path, e.commit]));
|
|
269
|
-
const validPaths = new Set(modules.list.map((m) => m.path));
|
|
270
|
-
const fixes: Fix[] = [];
|
|
271
|
-
|
|
272
|
-
for (const entry of currentIndex) {
|
|
273
|
-
if (!validPaths.has(entry.path)) {
|
|
274
|
-
await spawnGit(["update-index", "--force-remove", entry.path], repoRoot);
|
|
275
|
-
fixes.push({ location: [".git", "index"], action: "deleted", detail: entry.path });
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
for (const m of modules.list) {
|
|
280
|
-
const workingTree = resolve(repoRoot, m.path);
|
|
281
|
-
if (!existsSync(workingTree)) continue;
|
|
282
|
-
|
|
283
|
-
const shaProc = Bun.spawn(["git", "rev-parse", "HEAD"], { cwd: workingTree, stdout: "pipe", stderr: "pipe" });
|
|
284
|
-
const sha = (await new Response(shaProc.stdout).text()).trim();
|
|
285
|
-
if (!/^[0-9a-f]{40}$/.test(sha)) continue;
|
|
286
|
-
|
|
287
|
-
const current = currentShas.get(m.path);
|
|
288
|
-
await spawnGit(["update-index", "--add", "--cacheinfo", `160000,${sha},${m.path}`], repoRoot);
|
|
289
|
-
if (current !== sha) {
|
|
290
|
-
fixes.push({ location: [".git", "index"], action: "synced", detail: `${m.path}: ${sha.slice(0, 8)}` });
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return fixes;
|
|
295
|
-
},
|
|
296
|
-
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
297
|
-
});
|
|
298
|
-
}
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { resolve, join } from "node:path";
|
|
4
|
-
import { Effect, Option } from "effect";
|
|
5
|
-
import { resolveGitDir } from "./resolve-git-dir";
|
|
6
|
-
|
|
7
|
-
// --- types ---
|
|
8
|
-
|
|
9
|
-
export interface GitConfigSubmodule {
|
|
10
|
-
name: string;
|
|
11
|
-
url?: string;
|
|
12
|
-
path?: string;
|
|
13
|
-
active?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ModuleDirInfo {
|
|
17
|
-
/** Path relative to .git/modules/ */
|
|
18
|
-
relativePath: string;
|
|
19
|
-
/** URL from this module dir's own git config [remote "origin"] */
|
|
20
|
-
remoteUrl?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface WorkingTreeGitEntry {
|
|
24
|
-
/** Submodule path relative to repo root */
|
|
25
|
-
path: string;
|
|
26
|
-
/** Whether .git at this path is a gitdir-redirect file, a full directory, or absent */
|
|
27
|
-
type: "file" | "dir" | "missing";
|
|
28
|
-
/** Content of the gitdir: line when type === "file" */
|
|
29
|
-
gitdirTarget?: string;
|
|
30
|
-
/** Result of `git remote get-url origin` run inside the working tree dir */
|
|
31
|
-
remote?: string | { error: string };
|
|
32
|
-
/** Result of `git rev-parse HEAD` run inside the working tree dir */
|
|
33
|
-
headCommit?: string | { error: string };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface IndexGitlink {
|
|
37
|
-
path: string;
|
|
38
|
-
commit: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface GitState {
|
|
42
|
-
/** Submodule stanzas found in .git/config */
|
|
43
|
-
config: GitConfigSubmodule[];
|
|
44
|
-
/** Git dirs found under .git/modules/, each with their remote URL */
|
|
45
|
-
moduleDirs: ModuleDirInfo[];
|
|
46
|
-
/** Dirs under .git/modules/ that contain no HEAD file and aren't prefixes of any known module path */
|
|
47
|
-
orphanModuleDirs: string[];
|
|
48
|
-
/** Gitlink entries recorded in the index (mode 160000) */
|
|
49
|
-
index: IndexGitlink[];
|
|
50
|
-
/** State of .git file/dir in each known submodule working tree path */
|
|
51
|
-
workingTree: WorkingTreeGitEntry[];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// --- parsers ---
|
|
55
|
-
|
|
56
|
-
function parseGitConfig(content: string): GitConfigSubmodule[] {
|
|
57
|
-
const entries: GitConfigSubmodule[] = [];
|
|
58
|
-
let current: Partial<GitConfigSubmodule> | null = null;
|
|
59
|
-
|
|
60
|
-
for (const line of content.split("\n")) {
|
|
61
|
-
const headerMatch = line.match(/^\[submodule "(.+)"\]$/);
|
|
62
|
-
if (headerMatch) {
|
|
63
|
-
if (current?.name) entries.push(current as GitConfigSubmodule);
|
|
64
|
-
current = { name: headerMatch[1] };
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (!current) continue;
|
|
68
|
-
const kvMatch = line.match(/^\s+(\w+)\s*=\s*(.+)$/);
|
|
69
|
-
if (!kvMatch) continue;
|
|
70
|
-
const [, key, value] = kvMatch;
|
|
71
|
-
if (key === "url") current.url = value!.trim();
|
|
72
|
-
else if (key === "path") current.path = value!.trim();
|
|
73
|
-
else if (key === "active") current.active = value!.trim() === "true";
|
|
74
|
-
}
|
|
75
|
-
if (current?.name) entries.push(current as GitConfigSubmodule);
|
|
76
|
-
return entries;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function parseIndexGitlinks(output: string): IndexGitlink[] {
|
|
80
|
-
return output
|
|
81
|
-
.split("\n")
|
|
82
|
-
.filter((l) => l.startsWith("160000 "))
|
|
83
|
-
.map((line) => {
|
|
84
|
-
const match = line.match(/^160000 ([0-9a-f]{40}) \d\t(.+)$/);
|
|
85
|
-
if (!match) throw new Error(`Unexpected ls-files line: ${line}`);
|
|
86
|
-
return { commit: match[1]!, path: match[2]! };
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Recursively scans .git/modules/, returning:
|
|
92
|
-
* - gitDirs: paths of real git dirs (contain a HEAD file)
|
|
93
|
-
* - orphanDirs: paths that are neither a git dir nor a prefix of any known module path
|
|
94
|
-
*/
|
|
95
|
-
function scanModuleDirs(
|
|
96
|
-
root: string,
|
|
97
|
-
modulePaths: Set<string>,
|
|
98
|
-
rel = "",
|
|
99
|
-
): { gitDirs: string[]; orphanDirs: string[] } {
|
|
100
|
-
const gitDirs: string[] = [];
|
|
101
|
-
const orphanDirs: string[] = [];
|
|
102
|
-
if (!existsSync(root)) return { gitDirs, orphanDirs };
|
|
103
|
-
|
|
104
|
-
for (const entry of readdirSync(root)) {
|
|
105
|
-
const abs = join(root, entry);
|
|
106
|
-
if (!statSync(abs).isDirectory()) continue;
|
|
107
|
-
const relPath = rel ? `${rel}/${entry}` : entry;
|
|
108
|
-
|
|
109
|
-
if (existsSync(join(abs, "HEAD"))) {
|
|
110
|
-
gitDirs.push(relPath);
|
|
111
|
-
} else {
|
|
112
|
-
const isPrefix = [...modulePaths].some((p) => p.startsWith(relPath + "/"));
|
|
113
|
-
if (isPrefix) {
|
|
114
|
-
const sub = scanModuleDirs(abs, modulePaths, relPath);
|
|
115
|
-
gitDirs.push(...sub.gitDirs);
|
|
116
|
-
orphanDirs.push(...sub.orphanDirs);
|
|
117
|
-
} else {
|
|
118
|
-
orphanDirs.push(relPath);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return { gitDirs, orphanDirs };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function readModuleDirRemoteUrl(abs: string): Promise<Option.Option<string>> {
|
|
127
|
-
const configPath = join(abs, "config");
|
|
128
|
-
if (!existsSync(configPath)) return Option.none();
|
|
129
|
-
const content = await readFile(configPath, "utf8");
|
|
130
|
-
const match = content.match(/\[remote "origin"\][^\[]*\n\s+url\s*=\s*(.+)/);
|
|
131
|
-
return match?.[1] ? Option.some(match[1].trim()) : Option.none();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function spawnGitInDir(args: string[], cwd: string): Promise<string | { error: string }> {
|
|
135
|
-
const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
136
|
-
const [code, out, err] = await Promise.all([
|
|
137
|
-
proc.exited,
|
|
138
|
-
new Response(proc.stdout).text(),
|
|
139
|
-
new Response(proc.stderr).text(),
|
|
140
|
-
]);
|
|
141
|
-
return code === 0 ? out.trim() : { error: err.trim().split("\n")[0]! };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function readWorkingTreeEntry(repoRoot: string, subPath: string): Promise<WorkingTreeGitEntry> {
|
|
145
|
-
const workingTreeAbs = join(repoRoot, subPath);
|
|
146
|
-
const dotGit = join(workingTreeAbs, ".git");
|
|
147
|
-
|
|
148
|
-
let type: "file" | "dir" | "missing";
|
|
149
|
-
let gitdirTarget: string | undefined;
|
|
150
|
-
|
|
151
|
-
if (!existsSync(dotGit)) {
|
|
152
|
-
type = "missing";
|
|
153
|
-
} else if (statSync(dotGit).isDirectory()) {
|
|
154
|
-
type = "dir";
|
|
155
|
-
} else {
|
|
156
|
-
type = "file";
|
|
157
|
-
const content = await readFile(dotGit, "utf8");
|
|
158
|
-
const match = content.match(/^gitdir:\s*(.+)$/m);
|
|
159
|
-
gitdirTarget = match?.[1].trim();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (!existsSync(workingTreeAbs)) return { path: subPath, type, gitdirTarget };
|
|
163
|
-
|
|
164
|
-
const [remote, headCommit] = await Promise.all([
|
|
165
|
-
spawnGitInDir(["remote", "get-url", "origin"], workingTreeAbs),
|
|
166
|
-
spawnGitInDir(["rev-parse", "HEAD"], workingTreeAbs),
|
|
167
|
-
]);
|
|
168
|
-
|
|
169
|
-
return { path: subPath, type, gitdirTarget, remote, headCommit };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// --- main reader ---
|
|
173
|
-
|
|
174
|
-
export function readGitState(repoRoot: string, extraPaths: string[] = []): Effect.Effect<GitState, string> {
|
|
175
|
-
return Effect.tryPromise({
|
|
176
|
-
try: async () => {
|
|
177
|
-
const gitDir = await resolveGitDir(repoRoot);
|
|
178
|
-
|
|
179
|
-
const [configContent, lsOutput] = await Promise.all([
|
|
180
|
-
readFile(resolve(gitDir, "config"), "utf8"),
|
|
181
|
-
new Response(
|
|
182
|
-
Bun.spawn(["git", "ls-files", "--stage"], { cwd: repoRoot, stdout: "pipe" }).stdout
|
|
183
|
-
).text(),
|
|
184
|
-
]);
|
|
185
|
-
|
|
186
|
-
const config = parseGitConfig(configContent);
|
|
187
|
-
const index = parseIndexGitlinks(lsOutput);
|
|
188
|
-
|
|
189
|
-
const modulePaths = new Set(extraPaths);
|
|
190
|
-
const modulesRoot = resolve(gitDir, "modules");
|
|
191
|
-
const { gitDirs: moduleDirPaths, orphanDirs: orphanModuleDirs } = scanModuleDirs(modulesRoot, modulePaths);
|
|
192
|
-
const moduleDirs = await Promise.all(
|
|
193
|
-
moduleDirPaths.map(async (rel) => {
|
|
194
|
-
const remoteUrlOption = await readModuleDirRemoteUrl(join(modulesRoot, rel));
|
|
195
|
-
return {
|
|
196
|
-
relativePath: rel,
|
|
197
|
-
remoteUrl: Option.isSome(remoteUrlOption) ? remoteUrlOption.value : undefined,
|
|
198
|
-
};
|
|
199
|
-
})
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const allPaths = [
|
|
203
|
-
...new Set([
|
|
204
|
-
...index.map((e) => e.path),
|
|
205
|
-
...config.flatMap((e) => (e.path ? [e.path] : [])),
|
|
206
|
-
...extraPaths,
|
|
207
|
-
]),
|
|
208
|
-
];
|
|
209
|
-
const workingTree = await Promise.all(
|
|
210
|
-
allPaths.map((p) => readWorkingTreeEntry(repoRoot, p))
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
return { config, moduleDirs, orphanModuleDirs, index, workingTree };
|
|
214
|
-
},
|
|
215
|
-
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
216
|
-
});
|
|
217
|
-
}
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { resolve } from "node:path";
|
|
4
|
-
import { Effect } from "effect";
|
|
5
|
-
import type { EffectGen } from "effective-modules";
|
|
6
|
-
import { STANDARD_REMOTE_PREFIX } from "@/common/constants";
|
|
7
|
-
|
|
8
|
-
export interface Submodule {
|
|
9
|
-
name: string;
|
|
10
|
-
path: string;
|
|
11
|
-
url: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// git@host:org/repo.git or git@host:repo.git
|
|
15
|
-
const SSH_URL_RE = /^git@[^:]+:.+\.git$/;
|
|
16
|
-
const SSH_URL_HOST_RE = /^git@[^:]+:(.+)$/;
|
|
17
|
-
|
|
18
|
-
export class Gitmodules {
|
|
19
|
-
private constructor(
|
|
20
|
-
private readonly path: string,
|
|
21
|
-
private readonly repoRoot: string,
|
|
22
|
-
private readonly modules: Submodule[],
|
|
23
|
-
private readonly rawContent: string,
|
|
24
|
-
) {
|
|
25
|
-
this.path;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public static *fromRepoRoot(repoRoot: string): EffectGen<Gitmodules, string> {
|
|
29
|
-
const path = resolve(repoRoot, ".gitmodules");
|
|
30
|
-
|
|
31
|
-
const content = yield* Effect.tryPromise({
|
|
32
|
-
try: () => readFile(path, "utf8"),
|
|
33
|
-
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const modules = Gitmodules
|
|
37
|
-
.parse(content)
|
|
38
|
-
.map(Gitmodules.normalizeRemote)
|
|
39
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
-
|
|
41
|
-
const serialized = modules
|
|
42
|
-
.map((m) => `[submodule "${m.name}"]\n\tpath = ${m.path}\n\turl = ${m.url}`)
|
|
43
|
-
.join("\n") + "\n";
|
|
44
|
-
|
|
45
|
-
const result = new Gitmodules(path, repoRoot, modules, serialized);
|
|
46
|
-
const errors = result.validate();
|
|
47
|
-
if (errors.length) {
|
|
48
|
-
return yield* Effect.fail(`Validation errors in .gitmodules:\n${errors.map(e => ` - ${e}`).join('\n')}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (serialized !== content) {
|
|
52
|
-
yield* Effect.tryPromise({
|
|
53
|
-
try: () => writeFile(resolve(repoRoot, '.gitmodules'), serialized, 'utf8'),
|
|
54
|
-
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
55
|
-
});
|
|
56
|
-
yield* Effect.log('Updated .gitmodules.');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
private static parse(content: string): Submodule[] {
|
|
63
|
-
const modules: Submodule[] = [];
|
|
64
|
-
let current: Partial<Submodule> | null = null;
|
|
65
|
-
|
|
66
|
-
for (const line of content.split("\n")) {
|
|
67
|
-
const headerMatch = line.match(/^\[submodule "(.+)"\]$/);
|
|
68
|
-
if (headerMatch) {
|
|
69
|
-
if (current) modules.push(current as Submodule);
|
|
70
|
-
current = { name: headerMatch[1] };
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const kvMatch = line.match(/^\s+(\w+)\s*=\s*(.+)$/);
|
|
74
|
-
if (kvMatch && current) {
|
|
75
|
-
const [, key, value] = kvMatch;
|
|
76
|
-
if (key === "path" || key === "url") current[key] = value.trim();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (current) modules.push(current as Submodule);
|
|
80
|
-
return modules;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private static normalizeRemote(m: Submodule): Submodule {
|
|
84
|
-
if (m.url.startsWith(STANDARD_REMOTE_PREFIX)) return m;
|
|
85
|
-
const match = m.url.match(SSH_URL_HOST_RE);
|
|
86
|
-
// TODO: we should probably move this logic to be alongside validate
|
|
87
|
-
if (!match) return m; // malformed URL — left as-is, validate() will report it
|
|
88
|
-
const normalizedUrl = `${STANDARD_REMOTE_PREFIX}${match[1]}`;
|
|
89
|
-
console.warn(`- updating submodule "${m.name}" remote: ${m.url} -> ${normalizedUrl}`);
|
|
90
|
-
return { ...m, url: normalizedUrl };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
get list(): readonly Submodule[] {
|
|
94
|
-
return this.modules;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
get content(): string {
|
|
98
|
-
return this.rawContent;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private validate(): string[] {
|
|
102
|
-
const errors: string[] = [];
|
|
103
|
-
const names = new Set<string>();
|
|
104
|
-
const paths = new Set<string>();
|
|
105
|
-
const urls = new Set<string>();
|
|
106
|
-
|
|
107
|
-
for (const m of this.modules) {
|
|
108
|
-
if (names.has(m.name)) errors.push(`Duplicate name: "${m.name}"`);
|
|
109
|
-
else names.add(m.name);
|
|
110
|
-
|
|
111
|
-
if (paths.has(m.path)) errors.push(`Duplicate path: "${m.path}" (submodule "${m.name}")`);
|
|
112
|
-
else paths.add(m.path);
|
|
113
|
-
|
|
114
|
-
if (urls.has(m.url)) errors.push(`Duplicate url: "${m.url}" (submodule "${m.name}")`);
|
|
115
|
-
else urls.add(m.url);
|
|
116
|
-
|
|
117
|
-
if (!SSH_URL_RE.test(m.url))
|
|
118
|
-
errors.push(`Invalid url for "${m.name}": ${m.url}`);
|
|
119
|
-
|
|
120
|
-
if (!existsSync(resolve(this.repoRoot, m.path)))
|
|
121
|
-
errors.push(`Path does not exist for "${m.name}": ${m.path}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return errors;
|
|
125
|
-
}
|
|
126
|
-
}
|