@ozyman42/ozy-cli 0.4.6 → 0.4.7-darwin-x64.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.
Files changed (207) hide show
  1. package/multi-call-binary +0 -0
  2. package/package.json +11 -52
  3. package/bin/ozy +0 -191
  4. package/bin/ozy-signing-agent +0 -191
  5. package/bin/ozy-ssh-keygen +0 -191
  6. package/bin/ozy-virtual-security-key +0 -191
  7. package/dist/commands/git/hosts.js +0 -18
  8. package/dist/commands/git/hosts.js.map +0 -1
  9. package/dist/commands/git/index.js +0 -8
  10. package/dist/commands/git/index.js.map +0 -1
  11. package/dist/commands/git/setup.js +0 -145
  12. package/dist/commands/git/setup.js.map +0 -1
  13. package/dist/commands/index.js +0 -13
  14. package/dist/commands/index.js.map +0 -1
  15. package/dist/commands/npm/index.js +0 -7
  16. package/dist/commands/npm/index.js.map +0 -1
  17. package/dist/commands/npm/setup/git-remote.js +0 -66
  18. package/dist/commands/npm/setup/git-remote.js.map +0 -1
  19. package/dist/commands/npm/setup/index.js +0 -168
  20. package/dist/commands/npm/setup/index.js.map +0 -1
  21. package/dist/commands/npm/setup/npm-auth.js +0 -62
  22. package/dist/commands/npm/setup/npm-auth.js.map +0 -1
  23. package/dist/commands/npm/setup/package-json-validate.js +0 -47
  24. package/dist/commands/npm/setup/package-json-validate.js.map +0 -1
  25. package/dist/commands/npm/setup/package-registry.js +0 -20
  26. package/dist/commands/npm/setup/package-registry.js.map +0 -1
  27. package/dist/commands/npm/setup/setup.test.js +0 -95
  28. package/dist/commands/npm/setup/setup.test.js.map +0 -1
  29. package/dist/commands/npm/setup/trusted-publishing.js +0 -84
  30. package/dist/commands/npm/setup/trusted-publishing.js.map +0 -1
  31. package/dist/commands/npm/setup/workflow.js +0 -19
  32. package/dist/commands/npm/setup/workflow.js.map +0 -1
  33. package/dist/commands/ssh/get-sk-credential.js +0 -104
  34. package/dist/commands/ssh/get-sk-credential.js.map +0 -1
  35. package/dist/commands/ssh/index.js +0 -7
  36. package/dist/commands/ssh/index.js.map +0 -1
  37. package/dist/commands/upgrade.js +0 -81
  38. package/dist/commands/upgrade.js.map +0 -1
  39. package/dist/common/command.js +0 -15
  40. package/dist/common/command.js.map +0 -1
  41. package/dist/common/constants.js +0 -25
  42. package/dist/common/constants.js.map +0 -1
  43. package/dist/common/effective-modules-extensions.js +0 -13
  44. package/dist/common/effective-modules-extensions.js.map +0 -1
  45. package/dist/common/log.js +0 -4
  46. package/dist/common/log.js.map +0 -1
  47. package/dist/common/render-caller-tree.js +0 -20
  48. package/dist/common/render-caller-tree.js.map +0 -1
  49. package/dist/entrypoints/ozy-signing-agent.js +0 -48
  50. package/dist/entrypoints/ozy-signing-agent.js.map +0 -1
  51. package/dist/entrypoints/ozy-ssh-keygen.js +0 -9
  52. package/dist/entrypoints/ozy-ssh-keygen.js.map +0 -1
  53. package/dist/entrypoints/ozy-virtual-security-key.js +0 -18
  54. package/dist/entrypoints/ozy-virtual-security-key.js.map +0 -1
  55. package/dist/entrypoints/ozy.js +0 -3
  56. package/dist/entrypoints/ozy.js.map +0 -1
  57. package/dist/index.js +0 -2
  58. package/dist/index.js.map +0 -1
  59. package/dist/internal/thing.js +0 -4
  60. package/dist/internal/thing.js.map +0 -1
  61. package/dist/modules/cli/agent-client/impl.js +0 -109
  62. package/dist/modules/cli/agent-client/impl.js.map +0 -1
  63. package/dist/modules/cli/agent-client/interface.js +0 -11
  64. package/dist/modules/cli/agent-client/interface.js.map +0 -1
  65. package/dist/modules/cli/git/impl.js +0 -124
  66. package/dist/modules/cli/git/impl.js.map +0 -1
  67. package/dist/modules/cli/git/interface.js +0 -3
  68. package/dist/modules/cli/git/interface.js.map +0 -1
  69. package/dist/modules/cli/git/submodule/fix.js +0 -257
  70. package/dist/modules/cli/git/submodule/fix.js.map +0 -1
  71. package/dist/modules/cli/git/submodule/git-state.js +0 -154
  72. package/dist/modules/cli/git/submodule/git-state.js.map +0 -1
  73. package/dist/modules/cli/git/submodule/gitmodules.js +0 -113
  74. package/dist/modules/cli/git/submodule/gitmodules.js.map +0 -1
  75. package/dist/modules/cli/git/submodule/printer.js +0 -232
  76. package/dist/modules/cli/git/submodule/printer.js.map +0 -1
  77. package/dist/modules/cli/git/submodule/resolve-git-dir.js +0 -16
  78. package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +0 -1
  79. package/dist/modules/cli/github/impl.js +0 -137
  80. package/dist/modules/cli/github/impl.js.map +0 -1
  81. package/dist/modules/cli/github/interface.js +0 -2
  82. package/dist/modules/cli/github/interface.js.map +0 -1
  83. package/dist/modules/cli/index.js +0 -9
  84. package/dist/modules/cli/index.js.map +0 -1
  85. package/dist/modules/common/crypto/impl.js +0 -133
  86. package/dist/modules/common/crypto/impl.js.map +0 -1
  87. package/dist/modules/common/crypto/interface.js +0 -2
  88. package/dist/modules/common/crypto/interface.js.map +0 -1
  89. package/dist/modules/common/index.js +0 -10
  90. package/dist/modules/common/index.js.map +0 -1
  91. package/dist/modules/common/kep-map-store/impl.js +0 -42
  92. package/dist/modules/common/kep-map-store/impl.js.map +0 -1
  93. package/dist/modules/common/kep-map-store/interface.js +0 -2
  94. package/dist/modules/common/kep-map-store/interface.js.map +0 -1
  95. package/dist/modules/common/os-platform/caller-info.js +0 -132
  96. package/dist/modules/common/os-platform/caller-info.js.map +0 -1
  97. package/dist/modules/common/os-platform/impl.js +0 -87
  98. package/dist/modules/common/os-platform/impl.js.map +0 -1
  99. package/dist/modules/common/os-platform/interface.js +0 -2
  100. package/dist/modules/common/os-platform/interface.js.map +0 -1
  101. package/dist/modules/common/os-platform/virtual-hid/index.js +0 -12
  102. package/dist/modules/common/os-platform/virtual-hid/index.js.map +0 -1
  103. package/dist/modules/common/os-platform/virtual-hid/linux.js +0 -5
  104. package/dist/modules/common/os-platform/virtual-hid/linux.js.map +0 -1
  105. package/dist/modules/common/os-platform/virtual-hid/mac.js +0 -119
  106. package/dist/modules/common/os-platform/virtual-hid/mac.js.map +0 -1
  107. package/dist/modules/common/os-platform/virtual-hid/windows.js +0 -5
  108. package/dist/modules/common/os-platform/virtual-hid/windows.js.map +0 -1
  109. package/dist/modules/common/ssh-config/impl.js +0 -116
  110. package/dist/modules/common/ssh-config/impl.js.map +0 -1
  111. package/dist/modules/common/ssh-config/interface.js +0 -10
  112. package/dist/modules/common/ssh-config/interface.js.map +0 -1
  113. package/dist/modules/ssh-agent/index.js +0 -8
  114. package/dist/modules/ssh-agent/index.js.map +0 -1
  115. package/dist/modules/ssh-agent/session/impl.js +0 -265
  116. package/dist/modules/ssh-agent/session/impl.js.map +0 -1
  117. package/dist/modules/ssh-agent/session/interface-rpc.js +0 -20
  118. package/dist/modules/ssh-agent/session/interface-rpc.js.map +0 -1
  119. package/dist/modules/ssh-agent/session/interface.js +0 -32
  120. package/dist/modules/ssh-agent/session/interface.js.map +0 -1
  121. package/dist/modules/ssh-agent/session/passkey-prf-page.js +0 -204
  122. package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +0 -1
  123. package/dist/modules/ssh-agent/session/prf-flow.js +0 -103
  124. package/dist/modules/ssh-agent/session/prf-flow.js.map +0 -1
  125. package/dist/modules/ssh-agent/ssh-agent/impl.js +0 -117
  126. package/dist/modules/ssh-agent/ssh-agent/impl.js.map +0 -1
  127. package/dist/modules/ssh-agent/ssh-agent/interface.js +0 -2
  128. package/dist/modules/ssh-agent/ssh-agent/interface.js.map +0 -1
  129. package/dist/scripts/build.js +0 -412
  130. package/dist/scripts/build.js.map +0 -1
  131. package/dist/scripts/check-npm-version.js +0 -8
  132. package/dist/scripts/check-npm-version.js.map +0 -1
  133. package/dist/scripts/kill-existing-agent.js +0 -21
  134. package/dist/scripts/kill-existing-agent.js.map +0 -1
  135. package/dist/scripts/publish.js +0 -46
  136. package/dist/scripts/publish.js.map +0 -1
  137. package/src/commands/git/hosts.ts +0 -20
  138. package/src/commands/git/index.ts +0 -9
  139. package/src/commands/git/setup.ts +0 -182
  140. package/src/commands/index.ts +0 -15
  141. package/src/commands/npm/index.ts +0 -8
  142. package/src/commands/npm/setup/git-remote.ts +0 -78
  143. package/src/commands/npm/setup/index.ts +0 -189
  144. package/src/commands/npm/setup/npm-auth.ts +0 -58
  145. package/src/commands/npm/setup/package-json-validate.ts +0 -68
  146. package/src/commands/npm/setup/package-registry.ts +0 -24
  147. package/src/commands/npm/setup/publish-workflow.yml +0 -35
  148. package/src/commands/npm/setup/setup.test.ts +0 -117
  149. package/src/commands/npm/setup/trusted-publishing.ts +0 -106
  150. package/src/commands/npm/setup/workflow.ts +0 -20
  151. package/src/commands/ssh/get-sk-credential.ts +0 -110
  152. package/src/commands/ssh/index.ts +0 -8
  153. package/src/commands/upgrade.ts +0 -90
  154. package/src/common/command.ts +0 -16
  155. package/src/common/constants.ts +0 -26
  156. package/src/common/effective-modules-extensions.ts +0 -24
  157. package/src/common/log.ts +0 -3
  158. package/src/common/render-caller-tree.ts +0 -22
  159. package/src/entrypoints/ozy-signing-agent.ts +0 -48
  160. package/src/entrypoints/ozy-ssh-keygen.ts +0 -10
  161. package/src/entrypoints/ozy-virtual-security-key.ts +0 -21
  162. package/src/entrypoints/ozy.ts +0 -2
  163. package/src/index.ts +0 -1
  164. package/src/internal/thing.ts +0 -3
  165. package/src/modules/cli/agent-client/impl.ts +0 -152
  166. package/src/modules/cli/agent-client/interface.ts +0 -17
  167. package/src/modules/cli/git/impl.ts +0 -133
  168. package/src/modules/cli/git/interface.ts +0 -23
  169. package/src/modules/cli/git/submodule/fix.ts +0 -298
  170. package/src/modules/cli/git/submodule/git-state.ts +0 -217
  171. package/src/modules/cli/git/submodule/gitmodules.ts +0 -126
  172. package/src/modules/cli/git/submodule/printer.ts +0 -256
  173. package/src/modules/cli/git/submodule/resolve-git-dir.ts +0 -14
  174. package/src/modules/cli/github/impl.ts +0 -169
  175. package/src/modules/cli/github/interface.ts +0 -29
  176. package/src/modules/cli/index.ts +0 -16
  177. package/src/modules/common/crypto/impl.ts +0 -173
  178. package/src/modules/common/crypto/interface.ts +0 -20
  179. package/src/modules/common/index.ts +0 -19
  180. package/src/modules/common/kep-map-store/impl.ts +0 -47
  181. package/src/modules/common/kep-map-store/interface.ts +0 -9
  182. package/src/modules/common/os-platform/caller-info.ts +0 -152
  183. package/src/modules/common/os-platform/impl.ts +0 -86
  184. package/src/modules/common/os-platform/interface.ts +0 -16
  185. package/src/modules/common/os-platform/virtual-hid/index.ts +0 -12
  186. package/src/modules/common/os-platform/virtual-hid/linux.ts +0 -7
  187. package/src/modules/common/os-platform/virtual-hid/mac.ts +0 -150
  188. package/src/modules/common/os-platform/virtual-hid/windows.ts +0 -7
  189. package/src/modules/common/ssh-config/impl.ts +0 -130
  190. package/src/modules/common/ssh-config/interface.ts +0 -33
  191. package/src/modules/ssh-agent/index.ts +0 -13
  192. package/src/modules/ssh-agent/session/agent-notes.md +0 -210
  193. package/src/modules/ssh-agent/session/design.md +0 -2
  194. package/src/modules/ssh-agent/session/html-refactor.md +0 -23
  195. package/src/modules/ssh-agent/session/impl.ts +0 -328
  196. package/src/modules/ssh-agent/session/interface-rpc.ts +0 -24
  197. package/src/modules/ssh-agent/session/interface.ts +0 -62
  198. package/src/modules/ssh-agent/session/passkey-prf-page.ts +0 -224
  199. package/src/modules/ssh-agent/session/prf-flow.ts +0 -152
  200. package/src/modules/ssh-agent/ssh-agent/impl.ts +0 -136
  201. package/src/modules/ssh-agent/ssh-agent/interface.ts +0 -9
  202. package/src/scripts/build.ts +0 -506
  203. package/src/scripts/check-npm-version.ts +0 -7
  204. package/src/scripts/command-launcher.js.ejs +0 -168
  205. package/src/scripts/kill-existing-agent.ts +0 -22
  206. package/src/scripts/publish.ts +0 -47
  207. package/src/scripts/windows-bin-shims.md +0 -1104
@@ -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
- }
@@ -1,256 +0,0 @@
1
- import type { Gitmodules } from "./gitmodules";
2
- import type { GitState, ModuleDirInfo, IndexGitlink, WorkingTreeGitEntry } from "./git-state";
3
- import type { Fix } from "./fix";
4
-
5
- const RED = "\x1b[31m";
6
- const GREEN = "\x1b[32m";
7
- const CYAN = "\x1b[36m";
8
- const DIM = "\x1b[2m";
9
- const BOLD = "\x1b[1m";
10
- const RESET = "\x1b[0m";
11
-
12
- const ok = `${GREEN}✓${RESET}`;
13
- const fail = (msg: string) => `${RED}✗ ${msg}${RESET}`;
14
- const fsName = (s: string) => `${CYAN}${s}${RESET}`;
15
- const key = (s: string) => `${DIM}${s}${RESET}`;
16
-
17
- interface Line {
18
- label: string;
19
- isKey?: boolean; // dim key style instead of cyan fs style
20
- value?: string;
21
- status?: string;
22
- children?: Line[];
23
- }
24
-
25
- function render(lines: Line[], prefix = ""): void {
26
- for (let i = 0; i < lines.length; i++) {
27
- const last = i === lines.length - 1;
28
- const branch = last ? "└── " : "├── ";
29
- const child = last ? " " : "│ ";
30
- const l = lines[i];
31
- const label = l.isKey ? key(l.label) : fsName(l.label);
32
- let out = `${prefix}${branch}${label}`;
33
- if (l.value !== undefined) out += `: ${l.value}`;
34
- if (l.status !== undefined) out += ` ${l.status}`;
35
- console.log(out);
36
- if (l.children?.length) render(l.children, prefix + child);
37
- }
38
- }
39
-
40
- function buildPathTree(parts: string[], children: Line[]): Line {
41
- if (parts.length === 1) return { label: parts[0], children };
42
- return { label: parts[0], children: [buildPathTree(parts.slice(1), children)] };
43
- }
44
-
45
- function resultLine(label: string, result: string | { error: string } | undefined): Line {
46
- if (result === undefined) return { label, isKey: true, status: fail("unknown") };
47
- if (typeof result === "string") return { label, isKey: true, value: result };
48
- return { label, isKey: true, status: fail(result.error) };
49
- }
50
-
51
- function moduleHasIssues(
52
- m: { path: string; url: string },
53
- cfg: { path?: string; url?: string } | undefined,
54
- dir: ModuleDirInfo | undefined,
55
- idx: IndexGitlink | undefined,
56
- headSha: string | undefined,
57
- wt: WorkingTreeGitEntry | undefined,
58
- ): boolean {
59
- if (!cfg) return true;
60
- if (cfg.path !== m.path || cfg.url !== m.url) return true;
61
- if (!dir) return true;
62
- if (!idx) return true;
63
- if (headSha && idx.commit !== headSha) return true;
64
- if (!wt || wt.type === "missing" || wt.type === "dir") return true;
65
- if (typeof wt.headCommit !== "string") return true;
66
- if (typeof wt.remote !== "string" || wt.remote !== m.url) return true;
67
- return false;
68
- }
69
-
70
- export function printSyncState(modules: Gitmodules, state: GitState): void {
71
- const byName = new Map(state.config.map((c) => [c.name, c]));
72
- const byPath = new Map(state.moduleDirs.map((d) => [d.relativePath, d]));
73
- const byIndexPath = new Map(state.index.map((i) => [i.path, i]));
74
- const byTreePath = new Map(state.workingTree.map((w) => [w.path, w]));
75
- const modulePaths = new Set(modules.list.map((m) => m.path));
76
- const moduleNames = new Set(modules.list.map((m) => m.name));
77
-
78
- console.log("=== syncing submodules ===");
79
-
80
- for (const m of modules.list) {
81
- const cfg = byName.get(m.name);
82
- const dir = byPath.get(m.path);
83
- const idx = byIndexPath.get(m.path);
84
- const wt = byTreePath.get(m.path);
85
- const headSha = typeof wt?.headCommit === "string" ? wt.headCommit : undefined;
86
-
87
- if (!moduleHasIssues(m, cfg, dir, idx, headSha, wt)) {
88
- console.log(`${BOLD}[${m.name}]${RESET} ${ok}`);
89
- continue;
90
- }
91
-
92
- console.log(`${BOLD}[${m.name}]${RESET}`);
93
-
94
- // .gitmodules
95
- const gitmodulesNode: Line = {
96
- label: ".gitmodules",
97
- children: [
98
- { label: "path", isKey: true, value: m.path },
99
- { label: "url", isKey: true, value: m.url },
100
- ],
101
- };
102
-
103
- // .git/config
104
- let configNode: Line;
105
- if (!cfg) {
106
- configNode = { label: "config", status: fail("missing") };
107
- } else {
108
- configNode = {
109
- label: "config",
110
- children: [
111
- {
112
- label: "path",
113
- isKey: true,
114
- value: cfg.path ?? "(missing)",
115
- status: cfg.path === m.path ? ok : fail(`expected ${m.path}`),
116
- },
117
- {
118
- label: "url",
119
- isKey: true,
120
- value: cfg.url ?? "(missing)",
121
- status: cfg.url === m.url ? ok : fail(`expected ${m.url}`),
122
- },
123
- ],
124
- };
125
- }
126
-
127
- // .git/modules
128
- const modulesNode: Line = {
129
- label: "modules",
130
- children: [{ label: m.path, status: dir ? ok : fail("missing") }],
131
- };
132
-
133
- // .git/index
134
- let indexNode: Line;
135
- if (!idx) {
136
- indexNode = { label: "index", status: fail("missing") };
137
- } else {
138
- const idxSha8 = idx.commit.slice(0, 8);
139
- const matches = headSha && idx.commit === headSha;
140
- const mismatches = headSha && idx.commit !== headSha;
141
- indexNode = {
142
- label: "index",
143
- value: idxSha8,
144
- status: matches ? ok : mismatches ? fail(`head is ${headSha.slice(0, 8)}`) : undefined,
145
- };
146
- }
147
-
148
- const dotGitNode: Line = {
149
- label: ".git",
150
- children: [configNode, modulesNode, indexNode],
151
- };
152
-
153
- // working tree leaves
154
- const wtLeaves: Line[] = [];
155
-
156
- if (!wt || wt.type === "missing") {
157
- wtLeaves.push({ label: ".git", status: fail("missing") });
158
- } else if (wt.type === "dir") {
159
- wtLeaves.push({ label: ".git", status: fail("directory (standalone clone)") });
160
- } else {
161
- wtLeaves.push({ label: ".git", value: `gitdir → ${wt.gitdirTarget}` });
162
- }
163
-
164
- if (wt) {
165
- const head = headSha ?? wt.headCommit;
166
- wtLeaves.push(resultLine("head", typeof head === "string" ? head.slice(0, 8) : head));
167
-
168
- if (typeof wt.remote === "string") {
169
- wtLeaves.push({
170
- label: "remote",
171
- isKey: true,
172
- value: wt.remote,
173
- status: wt.remote === m.url ? ok : fail(`expected ${m.url}`),
174
- });
175
- } else {
176
- wtLeaves.push(resultLine("remote", wt.remote));
177
- }
178
- }
179
-
180
- const workingTreeNode = buildPathTree(m.path.split("/"), wtLeaves);
181
-
182
- render([gitmodulesNode, dotGitNode, workingTreeNode]);
183
- }
184
-
185
- // Stale entries
186
- const staleConfig = state.config.filter((c) => !moduleNames.has(c.name));
187
- const staleDirs = state.moduleDirs.filter((d) => !modulePaths.has(d.relativePath));
188
- const staleIndex = state.index.filter((i) => !modulePaths.has(i.path));
189
-
190
- if (staleConfig.length || staleDirs.length || state.orphanModuleDirs.length || staleIndex.length) {
191
- console.log(`\n${RED}${BOLD}=== stale (not in .gitmodules) ===${RESET}`);
192
- const staleNodes: Line[] = [];
193
-
194
- if (staleConfig.length) {
195
- staleNodes.push({
196
- label: ".git/config",
197
- children: staleConfig.map((c) => ({ label: c.name })),
198
- });
199
- }
200
- if (staleDirs.length || state.orphanModuleDirs.length) {
201
- staleNodes.push({
202
- label: ".git/modules",
203
- children: [
204
- ...staleDirs.map((d) => ({ label: d.relativePath })),
205
- ...state.orphanModuleDirs.map((d) => ({ label: d })),
206
- ],
207
- });
208
- }
209
- if (staleIndex.length) {
210
- staleNodes.push({
211
- label: ".git/index",
212
- children: staleIndex.map((i) => ({ label: i.path, isKey: true, value: i.commit.slice(0, 8) })),
213
- });
214
- }
215
-
216
- render(staleNodes);
217
- }
218
- }
219
-
220
- type FixTree = Map<string, { subtree: FixTree; fixes: Fix[] }>;
221
-
222
- function addToFixTree(tree: FixTree, fix: Fix, depth = 0): void {
223
- const seg = fix.location[depth];
224
- if (!tree.has(seg)) tree.set(seg, { subtree: new Map(), fixes: [] });
225
- const node = tree.get(seg)!;
226
- if (depth === fix.location.length - 1) {
227
- node.fixes.push(fix);
228
- } else {
229
- addToFixTree(node.subtree, fix, depth + 1);
230
- }
231
- }
232
-
233
- function fixTreeToLines(tree: FixTree): Line[] {
234
- const lines: Line[] = [];
235
- for (const [label, { subtree, fixes }] of tree) {
236
- const children = fixTreeToLines(subtree);
237
- if (fixes.length > 0) {
238
- for (const fix of fixes) {
239
- const actionStr = fix.action === "deleted" ? `${RED}deleted${RESET}` : `${GREEN}synced${RESET}`;
240
- const status = fix.detail ? `${actionStr} ${fix.detail}` : actionStr;
241
- lines.push({ label, status, children: children.length > 0 ? children : undefined });
242
- }
243
- } else {
244
- lines.push({ label, children });
245
- }
246
- }
247
- return lines;
248
- }
249
-
250
- export function printFixes(fixes: Fix[]): void {
251
- if (fixes.length === 0) return;
252
- const tree: FixTree = new Map();
253
- for (const fix of fixes) addToFixTree(tree, fix);
254
- console.log("\napplied fixes:");
255
- render(fixTreeToLines(tree));
256
- }
@@ -1,14 +0,0 @@
1
- import { statSync } from "node:fs";
2
- import { readFile } from "node:fs/promises";
3
- import { resolve } from "node:path";
4
-
5
- // A submodule's .git is a redirect file ("gitdir: <relative path>"), not a directory —
6
- // resolve it to the real git dir so callers don't try to treat the file as a directory.
7
- export async function resolveGitDir(repoRoot: string): Promise<string> {
8
- const dotGit = resolve(repoRoot, ".git");
9
- if (statSync(dotGit).isDirectory()) return dotGit;
10
- const content = await readFile(dotGit, "utf8");
11
- const match = content.match(/^gitdir:\s*(.+)$/m);
12
- if (!match) throw new Error(`Malformed .git file at ${dotGit}`);
13
- return resolve(repoRoot, match[1]!.trim());
14
- }