@ozyman42/ozy-cli 0.4.8-linux-x64.0 → 0.4.8
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/bin/ozy +191 -0
- package/bin/ozy-signing-agent +191 -0
- package/bin/ozy-ssh-keygen +191 -0
- package/bin/ozy-virtual-security-key +191 -0
- package/dist/commands/git/hosts.js +18 -0
- package/dist/commands/git/hosts.js.map +1 -0
- package/dist/commands/git/index.js +8 -0
- package/dist/commands/git/index.js.map +1 -0
- package/dist/commands/git/setup.js +145 -0
- package/dist/commands/git/setup.js.map +1 -0
- package/dist/commands/index.js +13 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/npm/index.js +7 -0
- package/dist/commands/npm/index.js.map +1 -0
- package/dist/commands/npm/setup/git-remote.js +66 -0
- package/dist/commands/npm/setup/git-remote.js.map +1 -0
- package/dist/commands/npm/setup/index.js +168 -0
- package/dist/commands/npm/setup/index.js.map +1 -0
- package/dist/commands/npm/setup/npm-auth.js +62 -0
- package/dist/commands/npm/setup/npm-auth.js.map +1 -0
- package/dist/commands/npm/setup/package-json-validate.js +47 -0
- package/dist/commands/npm/setup/package-json-validate.js.map +1 -0
- package/dist/commands/npm/setup/package-registry.js +20 -0
- package/dist/commands/npm/setup/package-registry.js.map +1 -0
- package/dist/commands/npm/setup/setup.test.js +95 -0
- package/dist/commands/npm/setup/setup.test.js.map +1 -0
- package/dist/commands/npm/setup/trusted-publishing.js +84 -0
- package/dist/commands/npm/setup/trusted-publishing.js.map +1 -0
- package/dist/commands/npm/setup/workflow.js +19 -0
- package/dist/commands/npm/setup/workflow.js.map +1 -0
- package/dist/commands/ssh/get-sk-credential.js +104 -0
- package/dist/commands/ssh/get-sk-credential.js.map +1 -0
- package/dist/commands/ssh/index.js +7 -0
- package/dist/commands/ssh/index.js.map +1 -0
- package/dist/commands/upgrade.js +199 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/common/command.js +15 -0
- package/dist/common/command.js.map +1 -0
- package/dist/common/constants.js +25 -0
- package/dist/common/constants.js.map +1 -0
- package/dist/common/effective-modules-extensions.js +13 -0
- package/dist/common/effective-modules-extensions.js.map +1 -0
- package/dist/common/log.js +4 -0
- package/dist/common/log.js.map +1 -0
- package/dist/common/render-caller-tree.js +20 -0
- package/dist/common/render-caller-tree.js.map +1 -0
- package/dist/entrypoints/ozy-signing-agent.js +48 -0
- package/dist/entrypoints/ozy-signing-agent.js.map +1 -0
- package/dist/entrypoints/ozy-ssh-keygen.js +9 -0
- package/dist/entrypoints/ozy-ssh-keygen.js.map +1 -0
- package/dist/entrypoints/ozy-virtual-security-key.js +18 -0
- package/dist/entrypoints/ozy-virtual-security-key.js.map +1 -0
- package/dist/entrypoints/ozy.js +3 -0
- package/dist/entrypoints/ozy.js.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/thing.js +4 -0
- package/dist/internal/thing.js.map +1 -0
- package/dist/modules/cli/agent-client/impl.js +109 -0
- package/dist/modules/cli/agent-client/impl.js.map +1 -0
- package/dist/modules/cli/agent-client/interface.js +11 -0
- package/dist/modules/cli/agent-client/interface.js.map +1 -0
- package/dist/modules/cli/git/impl.js +124 -0
- package/dist/modules/cli/git/impl.js.map +1 -0
- package/dist/modules/cli/git/interface.js +3 -0
- package/dist/modules/cli/git/interface.js.map +1 -0
- package/dist/modules/cli/git/submodule/fix.js +257 -0
- package/dist/modules/cli/git/submodule/fix.js.map +1 -0
- package/dist/modules/cli/git/submodule/git-state.js +154 -0
- package/dist/modules/cli/git/submodule/git-state.js.map +1 -0
- package/dist/modules/cli/git/submodule/gitmodules.js +113 -0
- package/dist/modules/cli/git/submodule/gitmodules.js.map +1 -0
- package/dist/modules/cli/git/submodule/printer.js +232 -0
- package/dist/modules/cli/git/submodule/printer.js.map +1 -0
- package/dist/modules/cli/git/submodule/resolve-git-dir.js +16 -0
- package/dist/modules/cli/git/submodule/resolve-git-dir.js.map +1 -0
- package/dist/modules/cli/github/impl.js +137 -0
- package/dist/modules/cli/github/impl.js.map +1 -0
- package/dist/modules/cli/github/interface.js +2 -0
- package/dist/modules/cli/github/interface.js.map +1 -0
- package/dist/modules/cli/index.js +9 -0
- package/dist/modules/cli/index.js.map +1 -0
- package/dist/modules/common/crypto/impl.js +133 -0
- package/dist/modules/common/crypto/impl.js.map +1 -0
- package/dist/modules/common/crypto/interface.js +2 -0
- package/dist/modules/common/crypto/interface.js.map +1 -0
- package/dist/modules/common/index.js +10 -0
- package/dist/modules/common/index.js.map +1 -0
- package/dist/modules/common/kep-map-store/impl.js +42 -0
- package/dist/modules/common/kep-map-store/impl.js.map +1 -0
- package/dist/modules/common/kep-map-store/interface.js +2 -0
- package/dist/modules/common/kep-map-store/interface.js.map +1 -0
- package/dist/modules/common/os-platform/caller-info.js +132 -0
- package/dist/modules/common/os-platform/caller-info.js.map +1 -0
- package/dist/modules/common/os-platform/impl.js +95 -0
- package/dist/modules/common/os-platform/impl.js.map +1 -0
- package/dist/modules/common/os-platform/interface.js +2 -0
- package/dist/modules/common/os-platform/interface.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/index.js +12 -0
- package/dist/modules/common/os-platform/virtual-hid/index.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/linux.js +5 -0
- package/dist/modules/common/os-platform/virtual-hid/linux.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/mac.js +119 -0
- package/dist/modules/common/os-platform/virtual-hid/mac.js.map +1 -0
- package/dist/modules/common/os-platform/virtual-hid/windows.js +5 -0
- package/dist/modules/common/os-platform/virtual-hid/windows.js.map +1 -0
- package/dist/modules/common/ssh-config/impl.js +116 -0
- package/dist/modules/common/ssh-config/impl.js.map +1 -0
- package/dist/modules/common/ssh-config/interface.js +10 -0
- package/dist/modules/common/ssh-config/interface.js.map +1 -0
- package/dist/modules/ssh-agent/index.js +8 -0
- package/dist/modules/ssh-agent/index.js.map +1 -0
- package/dist/modules/ssh-agent/session/impl.js +265 -0
- package/dist/modules/ssh-agent/session/impl.js.map +1 -0
- package/dist/modules/ssh-agent/session/interface-rpc.js +20 -0
- package/dist/modules/ssh-agent/session/interface-rpc.js.map +1 -0
- package/dist/modules/ssh-agent/session/interface.js +32 -0
- package/dist/modules/ssh-agent/session/interface.js.map +1 -0
- package/dist/modules/ssh-agent/session/passkey-prf-page.js +204 -0
- package/dist/modules/ssh-agent/session/passkey-prf-page.js.map +1 -0
- package/dist/modules/ssh-agent/session/prf-flow.js +103 -0
- package/dist/modules/ssh-agent/session/prf-flow.js.map +1 -0
- package/dist/modules/ssh-agent/ssh-agent/impl.js +117 -0
- package/dist/modules/ssh-agent/ssh-agent/impl.js.map +1 -0
- package/dist/modules/ssh-agent/ssh-agent/interface.js +2 -0
- package/dist/modules/ssh-agent/ssh-agent/interface.js.map +1 -0
- package/dist/scripts/build.js +410 -0
- package/dist/scripts/build.js.map +1 -0
- package/dist/scripts/check-npm-version.js +8 -0
- package/dist/scripts/check-npm-version.js.map +1 -0
- package/dist/scripts/kill-existing-agent.js +21 -0
- package/dist/scripts/kill-existing-agent.js.map +1 -0
- package/dist/scripts/publish.js +46 -0
- package/dist/scripts/publish.js.map +1 -0
- package/package.json +52 -11
- package/src/commands/git/hosts.ts +20 -0
- package/src/commands/git/index.ts +9 -0
- package/src/commands/git/setup.ts +182 -0
- package/src/commands/index.ts +15 -0
- package/src/commands/npm/index.ts +8 -0
- package/src/commands/npm/setup/git-remote.ts +78 -0
- package/src/commands/npm/setup/index.ts +189 -0
- package/src/commands/npm/setup/npm-auth.ts +58 -0
- package/src/commands/npm/setup/package-json-validate.ts +68 -0
- package/src/commands/npm/setup/package-registry.ts +24 -0
- package/src/commands/npm/setup/publish-workflow.yml +35 -0
- package/src/commands/npm/setup/setup.test.ts +117 -0
- package/src/commands/npm/setup/trusted-publishing.ts +106 -0
- package/src/commands/npm/setup/workflow.ts +20 -0
- package/src/commands/ssh/get-sk-credential.ts +110 -0
- package/src/commands/ssh/index.ts +8 -0
- package/src/commands/upgrade.ts +220 -0
- package/src/common/command.ts +16 -0
- package/src/common/constants.ts +26 -0
- package/src/common/effective-modules-extensions.ts +24 -0
- package/src/common/log.ts +3 -0
- package/src/common/render-caller-tree.ts +22 -0
- package/src/entrypoints/ozy-signing-agent.ts +48 -0
- package/src/entrypoints/ozy-ssh-keygen.ts +10 -0
- package/src/entrypoints/ozy-virtual-security-key.ts +21 -0
- package/src/entrypoints/ozy.ts +2 -0
- package/src/index.ts +1 -0
- package/src/internal/thing.ts +3 -0
- package/src/modules/cli/agent-client/impl.ts +152 -0
- package/src/modules/cli/agent-client/interface.ts +17 -0
- package/src/modules/cli/git/impl.ts +133 -0
- package/src/modules/cli/git/interface.ts +23 -0
- package/src/modules/cli/git/submodule/fix.ts +298 -0
- package/src/modules/cli/git/submodule/git-state.ts +217 -0
- package/src/modules/cli/git/submodule/gitmodules.ts +126 -0
- package/src/modules/cli/git/submodule/printer.ts +256 -0
- package/src/modules/cli/git/submodule/resolve-git-dir.ts +14 -0
- package/src/modules/cli/github/impl.ts +169 -0
- package/src/modules/cli/github/interface.ts +29 -0
- package/src/modules/cli/index.ts +16 -0
- package/src/modules/common/crypto/impl.ts +173 -0
- package/src/modules/common/crypto/interface.ts +20 -0
- package/src/modules/common/index.ts +19 -0
- package/src/modules/common/kep-map-store/impl.ts +47 -0
- package/src/modules/common/kep-map-store/interface.ts +9 -0
- package/src/modules/common/os-platform/caller-info.ts +152 -0
- package/src/modules/common/os-platform/impl.ts +91 -0
- package/src/modules/common/os-platform/interface.ts +16 -0
- package/src/modules/common/os-platform/virtual-hid/index.ts +12 -0
- package/src/modules/common/os-platform/virtual-hid/linux.ts +7 -0
- package/src/modules/common/os-platform/virtual-hid/mac.ts +150 -0
- package/src/modules/common/os-platform/virtual-hid/windows.ts +7 -0
- package/src/modules/common/ssh-config/impl.ts +130 -0
- package/src/modules/common/ssh-config/interface.ts +33 -0
- package/src/modules/ssh-agent/index.ts +13 -0
- package/src/modules/ssh-agent/session/agent-notes.md +210 -0
- package/src/modules/ssh-agent/session/design.md +2 -0
- package/src/modules/ssh-agent/session/html-refactor.md +23 -0
- package/src/modules/ssh-agent/session/impl.ts +328 -0
- package/src/modules/ssh-agent/session/interface-rpc.ts +24 -0
- package/src/modules/ssh-agent/session/interface.ts +62 -0
- package/src/modules/ssh-agent/session/passkey-prf-page.ts +224 -0
- package/src/modules/ssh-agent/session/prf-flow.ts +152 -0
- package/src/modules/ssh-agent/ssh-agent/impl.ts +136 -0
- package/src/modules/ssh-agent/ssh-agent/interface.ts +9 -0
- package/src/scripts/build.ts +504 -0
- package/src/scripts/check-npm-version.ts +7 -0
- package/src/scripts/command-launcher.js.ejs +168 -0
- package/src/scripts/kill-existing-agent.ts +22 -0
- package/src/scripts/publish.ts +47 -0
- package/src/scripts/windows-bin-shims.md +1104 -0
- package/multi-call-binary +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import { Effect, Layer, pipe } from "effect";
|
|
3
|
+
import { OSPlatformImpl } from "@/modules/common/os-platform/impl";
|
|
4
|
+
import { CryptoImpl } from "@/modules/common/crypto/impl";
|
|
5
|
+
import { KeyMapStoreImpl } from "@/modules/common/kep-map-store/impl";
|
|
6
|
+
import { SSHConfigImpl } from "@/modules/common/ssh-config/impl";
|
|
7
|
+
import { SSHAgentImpl } from "@/modules/ssh-agent/ssh-agent/impl";
|
|
8
|
+
import { SessionImpl } from "@/modules/ssh-agent/session/impl";
|
|
9
|
+
import { AGENT_LOG_FILE_PATH, AGENT_PID_FILE_PATH, AGENT_PORT_FILE_PATH, AGENT_SOCK_FILE_PATH } from "@/common/constants";
|
|
10
|
+
import { rmSync } from "node:fs";
|
|
11
|
+
|
|
12
|
+
const origLog = console.log.bind(console);
|
|
13
|
+
const origError = console.error.bind(console);
|
|
14
|
+
const origWarn = console.warn.bind(console);
|
|
15
|
+
|
|
16
|
+
function logToFile(...args: any[]) {
|
|
17
|
+
try {
|
|
18
|
+
appendFileSync(AGENT_LOG_FILE_PATH, args.map(arg => {
|
|
19
|
+
try { return JSON.stringify(arg) }
|
|
20
|
+
catch(e) { return String(e); }
|
|
21
|
+
}).join(' ') + '\n');
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log = (...args: any[]) => { logToFile(...args); origLog(...args); };
|
|
26
|
+
console.error = (...args: any[]) => { logToFile(...args); origError(...args); };
|
|
27
|
+
console.warn = (...args: any[]) => { logToFile(...args); origWarn(...args); };
|
|
28
|
+
|
|
29
|
+
function cleanup() {
|
|
30
|
+
try { rmSync(AGENT_PID_FILE_PATH); } catch {}
|
|
31
|
+
try { rmSync(AGENT_PORT_FILE_PATH); } catch {}
|
|
32
|
+
try { rmSync(AGENT_SOCK_FILE_PATH); } catch {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
process.on("exit", cleanup);
|
|
36
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
37
|
+
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
38
|
+
|
|
39
|
+
pipe(
|
|
40
|
+
SSHAgentImpl.ServiceLayer,
|
|
41
|
+
Layer.provideMerge(SessionImpl.makeRpcLayer()),
|
|
42
|
+
Layer.provideMerge(KeyMapStoreImpl.Layer),
|
|
43
|
+
Layer.provideMerge(SSHConfigImpl.Layer),
|
|
44
|
+
Layer.provideMerge(CryptoImpl.Layer),
|
|
45
|
+
Layer.provideMerge(OSPlatformImpl.Layer),
|
|
46
|
+
Layer.launch,
|
|
47
|
+
Effect.runPromise
|
|
48
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AGENT_SOCK_FILE_PATH } from "@/common/constants";
|
|
2
|
+
|
|
3
|
+
const result = Bun.spawnSync(["ssh-keygen", ...Bun.argv.slice(2)], {
|
|
4
|
+
stdin: "inherit",
|
|
5
|
+
stdout: "inherit",
|
|
6
|
+
stderr: "inherit",
|
|
7
|
+
env: { ...process.env, SSH_AUTH_SOCK: AGENT_SOCK_FILE_PATH },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
process.exit(result.exitCode ?? 1);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
3
|
+
import { commonModules } from "@/modules/common";
|
|
4
|
+
import { OSPlatformImpl } from "@/modules/common/os-platform/impl";
|
|
5
|
+
import { VIRTUAL_KEY_LOG_FILE_PATH } from "@/common/constants";
|
|
6
|
+
|
|
7
|
+
const program = Effect.gen(function* () {
|
|
8
|
+
const platform = yield* commonModules.OSPlatform;
|
|
9
|
+
yield* platform.registerVirtualHID((data) =>
|
|
10
|
+
Effect.sync(() => {
|
|
11
|
+
appendFileSync(VIRTUAL_KEY_LOG_FILE_PATH, `[vhid] received ${data.length} bytes: ${data.toString('hex')}\n`);
|
|
12
|
+
return Buffer.alloc(64);
|
|
13
|
+
})
|
|
14
|
+
);
|
|
15
|
+
yield* Effect.never;
|
|
16
|
+
}).pipe(Effect.provide(OSPlatformImpl.Layer));
|
|
17
|
+
|
|
18
|
+
Effect.runPromise(program).catch(e => {
|
|
19
|
+
console.error('Virtual security key failed:', e);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Effect, Layer, Match, Option, pipe, Data, FileSystem, Schedule, Result } from "effect";
|
|
2
|
+
import { effunct, implementing, type EffectGen } from "effective-modules";
|
|
3
|
+
import { FetchHttpClient } from "effect/unstable/http";
|
|
4
|
+
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
5
|
+
import { AgentRpcGroup } from "@/modules/ssh-agent/session/interface-rpc";
|
|
6
|
+
import { cliModules } from "..";
|
|
7
|
+
import { type AgentRpcClient, type IAgentClient, AgentClientError } from "./interface";
|
|
8
|
+
import { AGENT_CMD_PATH, AGENT_PID_FILE_PATH, AGENT_PORT_FILE_PATH, AGENT_SOCK_FILE_PATH, CURRENT_VERSION } from "@/common/constants";
|
|
9
|
+
import { readFileSync, rmSync } from "node:fs";
|
|
10
|
+
import { log } from "@/common/log";
|
|
11
|
+
|
|
12
|
+
function makeAgentClientLayer(port: number) {
|
|
13
|
+
return pipe(
|
|
14
|
+
RpcClient.layerProtocolHttp({ url: `http://localhost:${port}/rpc` }),
|
|
15
|
+
Layer.provideMerge(RpcSerialization.layerJson),
|
|
16
|
+
Layer.provideMerge(FetchHttpClient.layer)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class NoResponseError extends Data.TaggedError("NoResponseError")<{}> {
|
|
21
|
+
public toString() { return "No response from agent."; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class NonMatchingVersionError extends Data.TaggedError("NonMatchingVersionError")<{
|
|
25
|
+
cliVersion: string;
|
|
26
|
+
agentVersion: string;
|
|
27
|
+
}> {
|
|
28
|
+
public toString() {
|
|
29
|
+
return `Agent running but with mismatched version ${this.agentVersion} compared to CLI version ${this.cliVersion}`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type PingError = NoResponseError | NonMatchingVersionError;
|
|
34
|
+
|
|
35
|
+
export class AgentClientImpl extends implementing(cliModules.AgentClient).uses(FileSystem.FileSystem) implements IAgentClient {
|
|
36
|
+
private *readPortFromFile(): EffectGen<Option.Option<number>, never> {
|
|
37
|
+
const result = yield* pipe(
|
|
38
|
+
this.getDependency(FileSystem.FileSystem).readFileString(AGENT_PORT_FILE_PATH),
|
|
39
|
+
Effect.result
|
|
40
|
+
);
|
|
41
|
+
if (Result.isFailure(result)) return Option.none();
|
|
42
|
+
const port = parseInt(result.success.trim());
|
|
43
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
44
|
+
log(`Read out of bounds port ${port}`);
|
|
45
|
+
return Option.none();
|
|
46
|
+
}
|
|
47
|
+
return Option.some(port);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private *pingAtPort(port: number): EffectGen<void, PingError> {
|
|
51
|
+
yield* Effect.scoped(
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
const client = yield* RpcClient.make(AgentRpcGroup);
|
|
54
|
+
const { version } = yield* pipe(
|
|
55
|
+
client.GetVersion({}),
|
|
56
|
+
Effect.catchTag("RpcClientError", () => new NoResponseError())
|
|
57
|
+
);
|
|
58
|
+
if (version !== CURRENT_VERSION)
|
|
59
|
+
yield* new NonMatchingVersionError({ cliVersion: CURRENT_VERSION, agentVersion: version });
|
|
60
|
+
})
|
|
61
|
+
).pipe(Effect.provide(makeAgentClientLayer(port)));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private *ping(): EffectGen<{ port: number }, PingError> {
|
|
65
|
+
const portOption = yield* effunct(this.readPortFromFile)();
|
|
66
|
+
if (Option.isNone(portOption)) return yield* new NoResponseError();
|
|
67
|
+
const port = portOption.value;
|
|
68
|
+
yield* effunct(this.pingAtPort)(port);
|
|
69
|
+
return { port };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private *waitForPidFile(expectedPid: number): EffectGen<void, string> {
|
|
73
|
+
const fs = this.getDependency(FileSystem.FileSystem);
|
|
74
|
+
yield* pipe(
|
|
75
|
+
Effect.retry(
|
|
76
|
+
pipe(
|
|
77
|
+
fs.readFileString(AGENT_PID_FILE_PATH),
|
|
78
|
+
Effect.catchTag("PlatformError", () => Effect.fail("pid file not yet written")),
|
|
79
|
+
Effect.flatMap(content => {
|
|
80
|
+
const pid = parseInt(content.trim());
|
|
81
|
+
return pid === expectedPid
|
|
82
|
+
? Effect.void
|
|
83
|
+
: Effect.fail(`PID mismatch: expected ${expectedPid}, got ${pid}`);
|
|
84
|
+
})
|
|
85
|
+
),
|
|
86
|
+
Schedule.spaced("50 millis")
|
|
87
|
+
),
|
|
88
|
+
Effect.timeout("5 seconds"),
|
|
89
|
+
Effect.mapError(() => "Agent did not start within 5 seconds")
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private *startAgent(): EffectGen<{ pid: number; port: number }, string | PingError> {
|
|
94
|
+
const proc = Bun.spawn([AGENT_CMD_PATH], { stdout: "ignore", stderr: "ignore" });
|
|
95
|
+
yield* effunct(this.waitForPidFile)(proc.pid);
|
|
96
|
+
const { port } = yield* effunct(this.ping)();
|
|
97
|
+
log(`Started agent at PID ${proc.pid}`);
|
|
98
|
+
return { pid: proc.pid, port };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
*killIfRunning(): EffectGen<void, string> {
|
|
102
|
+
try {
|
|
103
|
+
// Throws if file missing
|
|
104
|
+
const pidFileContent = readFileSync(AGENT_PID_FILE_PATH, "utf-8");
|
|
105
|
+
const PID = parseInt(pidFileContent.trim());
|
|
106
|
+
// If doesn't exist, below line throws
|
|
107
|
+
process.kill(PID, 0);
|
|
108
|
+
log(`Killing agent on PID ${PID}`);
|
|
109
|
+
process.kill(PID, "SIGTERM");
|
|
110
|
+
yield* Effect.sleep(500);
|
|
111
|
+
} catch { }
|
|
112
|
+
try { rmSync(AGENT_PID_FILE_PATH); } catch {}
|
|
113
|
+
try { rmSync(AGENT_PORT_FILE_PATH); } catch {}
|
|
114
|
+
try { rmSync(AGENT_SOCK_FILE_PATH); } catch {}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private *ensureRunning(): EffectGen<{ port: number }, string> {
|
|
118
|
+
const pingResult = yield* pipe(effunct(this.ping)(), Effect.result);
|
|
119
|
+
if (Result.isSuccess(pingResult)) return pingResult.success;
|
|
120
|
+
log(pingResult.failure.toString());
|
|
121
|
+
yield* Match.value(pingResult.failure).pipe(
|
|
122
|
+
Match.tag("NonMatchingVersionError", () => effunct(this.killIfRunning)()),
|
|
123
|
+
Match.tag("NoResponseError", () => effunct(this.killIfRunning)()),
|
|
124
|
+
Match.exhaustive
|
|
125
|
+
);
|
|
126
|
+
return yield* pipe(
|
|
127
|
+
effunct(this.startAgent)(),
|
|
128
|
+
Effect.mapError(err =>
|
|
129
|
+
typeof err === "string" ?
|
|
130
|
+
err :
|
|
131
|
+
`Failed to start agent due to ${err.name}: ${err.toString()}`
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
*usingClient<A, E>(use: (client: AgentRpcClient) => EffectGen<A, E>): EffectGen<A, AgentClientError<E>> {
|
|
137
|
+
const { port } = yield* pipe(
|
|
138
|
+
effunct(this.ensureRunning)(),
|
|
139
|
+
Effect.mapError(reason => new AgentClientError.ClientError({ reason }))
|
|
140
|
+
);
|
|
141
|
+
return yield* pipe(
|
|
142
|
+
Effect.scoped(
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const client = yield* RpcClient.make(AgentRpcGroup);
|
|
145
|
+
return yield* use(client);
|
|
146
|
+
})
|
|
147
|
+
),
|
|
148
|
+
Effect.provide(makeAgentClientLayer(port)),
|
|
149
|
+
Effect.mapError(e => new AgentClientError.UsageError({ cause: e }))
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EffectGen } from "effective-modules";
|
|
2
|
+
import type { AgentRpcGroup } from "@/modules/ssh-agent/session/interface-rpc";
|
|
3
|
+
import type { RpcClient, RpcClientError as RpcClientErrorModule, RpcGroup } from "effect/unstable/rpc";
|
|
4
|
+
import { Data } from "effect";
|
|
5
|
+
|
|
6
|
+
export type AgentRpcClient = RpcClient.RpcClient<RpcGroup.Rpcs<AgentRpcGroup>, RpcClientErrorModule.RpcClientError>;
|
|
7
|
+
|
|
8
|
+
export namespace AgentClientError {
|
|
9
|
+
export class UsageError<E> extends Data.TaggedError("AgentClientUsageError")<{ readonly cause: E }> {}
|
|
10
|
+
export class ClientError extends Data.TaggedError("AgentClientError")<{ readonly reason: string }> {}
|
|
11
|
+
}
|
|
12
|
+
export type AgentClientError<E> = AgentClientError.UsageError<E> | AgentClientError.ClientError;
|
|
13
|
+
|
|
14
|
+
export interface IAgentClient {
|
|
15
|
+
killIfRunning(): EffectGen<void, string>;
|
|
16
|
+
usingClient<A, E>(use: (client: AgentRpcClient) => EffectGen<A, E>): EffectGen<A, AgentClientError<E>>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { implementing, type EffectGen } from "effective-modules";
|
|
2
|
+
import { cliModules } from "..";
|
|
3
|
+
import { commonModules } from "@/modules/common";
|
|
4
|
+
import { $ } from 'bun';
|
|
5
|
+
import type { IGit, SetupRepoInput } from "./interface";
|
|
6
|
+
import { Effect, Option, pipe } from "effect";
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { SSH_KEYGEN_CMD_PATH, STANDARD_REMOTE_PREFIX } from "@/common/constants";
|
|
10
|
+
import { Gitmodules } from "./submodule/gitmodules";
|
|
11
|
+
import { readGitState } from "./submodule/git-state";
|
|
12
|
+
import { fixGitConfig, fixModuleDirs, fixIndexGitlinks } from "./submodule/fix";
|
|
13
|
+
import { printSyncState, printFixes } from "./submodule/printer";
|
|
14
|
+
|
|
15
|
+
export class GitImpl extends implementing(cliModules.Git).uses(commonModules.OSPlatform, commonModules.SSHConfig) implements IGit {
|
|
16
|
+
private parseOwnerRepo(remote: string): Effect.Effect<{ owner: string; repo: string }, string> {
|
|
17
|
+
const sshMatch = remote.match(/^git@[^:]+:([^/]+)\/(.+?)(?:\.git)?$/);
|
|
18
|
+
if (sshMatch) return Effect.succeed({ owner: sshMatch[1]!, repo: sshMatch[2]! });
|
|
19
|
+
const httpsMatch = remote.match(/^https?:\/\/[^/]+\/([^/]+)\/(.+?)(?:\.git)?$/);
|
|
20
|
+
if (httpsMatch) return Effect.succeed({ owner: httpsMatch[1]!, repo: httpsMatch[2]! });
|
|
21
|
+
return Effect.fail(`Cannot parse owner/repo from remote: ${remote}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
*resolveOwnerAndRepo(maybeOwnerAndRepo: Option.Option<string>): EffectGen<{ owner: string; repo: string; }, string> {
|
|
25
|
+
if (Option.isSome(maybeOwnerAndRepo)) {
|
|
26
|
+
const parts = maybeOwnerAndRepo.value.split('/');
|
|
27
|
+
const [owner, repo] = parts;
|
|
28
|
+
if (parts.length !== 2 || !owner || !repo)
|
|
29
|
+
return yield* Effect.fail(`Cannot parse remote error: Expected "owner/repo", got "${maybeOwnerAndRepo.value}"`);
|
|
30
|
+
return { owner, repo };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!(yield* this.isGitRepository())) {
|
|
34
|
+
yield* Effect.fail(`not in git repo: Current directory is not a git repository`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const remoteOption = yield* this.getRemoteOrigin();
|
|
38
|
+
if (Option.isNone(remoteOption)) {
|
|
39
|
+
return yield* Effect.fail(`no remote origin: No remote.origin.url — pass owner/repo as argument to clone instead`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return yield* this.parseOwnerRepo(remoteOption.value);
|
|
43
|
+
}
|
|
44
|
+
*isGitRepository(): EffectGen<boolean> {
|
|
45
|
+
return yield* pipe(
|
|
46
|
+
Effect.tryPromise(async () => {
|
|
47
|
+
await $`git rev-parse --is-inside-work-tree`.quiet();
|
|
48
|
+
return true;
|
|
49
|
+
}),
|
|
50
|
+
Effect.catchTag("UnknownError", () => Effect.succeed(false))
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
*getRemoteOrigin(): EffectGen<Option.Option<string>> {
|
|
54
|
+
return yield* pipe(
|
|
55
|
+
Effect.tryPromise(async() => {
|
|
56
|
+
return Option.some((await $`git config --get remote.origin.url`.quiet()).text().trim());
|
|
57
|
+
}),
|
|
58
|
+
Effect.catchTag("UnknownError", () => Effect.succeed(Option.none()))
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
*clone(owner: string, repo: string): EffectGen<void, string> {
|
|
62
|
+
const result = Bun.spawnSync(['git', 'clone', '--recurse-submodules', `${STANDARD_REMOTE_PREFIX}${owner}/${repo}.git`], { stdout: 'inherit', stderr: 'inherit' });
|
|
63
|
+
if (result.exitCode !== 0)
|
|
64
|
+
return yield* Effect.fail(`git clone failed with exit code ${result.exitCode}`);
|
|
65
|
+
process.chdir(`./${repo}`);
|
|
66
|
+
}
|
|
67
|
+
*setLocalConfig(key: string, value: string): EffectGen<void, string> {
|
|
68
|
+
yield* Effect.tryPromise({
|
|
69
|
+
try: async () => { await $`git config --local ${key} ${value}`.quiet(); },
|
|
70
|
+
catch: (e) => `git config ${key}: ${e instanceof Error ? e.message : String(e)}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
private *configureRepo({ dir, remoteURL, user, pubkeyPath }: SetupRepoInput): EffectGen<void, string> {
|
|
74
|
+
yield* Effect.log(`Configuring ${dir}`);
|
|
75
|
+
yield* Effect.tryPromise({
|
|
76
|
+
try: async () => {
|
|
77
|
+
const configs: [string, string][] = [
|
|
78
|
+
['remote.origin.url', remoteURL],
|
|
79
|
+
['user.name', user.name],
|
|
80
|
+
['user.email', user.email],
|
|
81
|
+
['user.signingkey', pubkeyPath],
|
|
82
|
+
['gpg.format', 'ssh'],
|
|
83
|
+
['gpg.ssh.program', SSH_KEYGEN_CMD_PATH],
|
|
84
|
+
['commit.gpgsign', 'true'],
|
|
85
|
+
['tag.gpgsign', 'true'],
|
|
86
|
+
];
|
|
87
|
+
for (const [k, v] of configs) {
|
|
88
|
+
const r = Bun.spawnSync(['git', 'config', '--local', k, v], { cwd: dir, stdout: 'pipe', stderr: 'pipe' });
|
|
89
|
+
if (r.exitCode !== 0) {
|
|
90
|
+
const err = new TextDecoder().decode(r.stderr);
|
|
91
|
+
throw new Error(`git config ${k} failed: ${err.trim()}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
catch: (e) => e instanceof Error ? e.message : String(e),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
private *syncSubmoduleState(repoRoot: string, topLevelRoot: string): EffectGen<Gitmodules, string> {
|
|
99
|
+
const gitmodules = yield* Gitmodules.fromRepoRoot(repoRoot);
|
|
100
|
+
if (gitmodules.list.length === 0) return gitmodules;
|
|
101
|
+
const submodulePaths = gitmodules.list.map(m => m.path);
|
|
102
|
+
const state = yield* readGitState(repoRoot, submodulePaths);
|
|
103
|
+
printSyncState(gitmodules, state);
|
|
104
|
+
const fixResults = yield* Effect.all(
|
|
105
|
+
[
|
|
106
|
+
fixGitConfig(repoRoot, gitmodules),
|
|
107
|
+
fixModuleDirs(repoRoot, gitmodules, state.moduleDirs, state.orphanModuleDirs, topLevelRoot),
|
|
108
|
+
fixIndexGitlinks(repoRoot, gitmodules, state.index),
|
|
109
|
+
],
|
|
110
|
+
{ concurrency: "unbounded" },
|
|
111
|
+
);
|
|
112
|
+
printFixes(fixResults.flat());
|
|
113
|
+
return gitmodules;
|
|
114
|
+
}
|
|
115
|
+
*setupRepo(input: SetupRepoInput): EffectGen<void, string> {
|
|
116
|
+
yield* this.setupRepoAt(input, input.dir);
|
|
117
|
+
}
|
|
118
|
+
private *setupRepoAt(input: SetupRepoInput, topLevelRoot: string): EffectGen<void, string> {
|
|
119
|
+
yield* this.configureRepo(input);
|
|
120
|
+
if (!existsSync(resolve(input.dir, '.gitmodules'))) return;
|
|
121
|
+
const gitmodules = yield* this.syncSubmoduleState(input.dir, topLevelRoot);
|
|
122
|
+
for (const m of gitmodules.list) {
|
|
123
|
+
const subDir = resolve(input.dir, m.path);
|
|
124
|
+
if (!existsSync(subDir)) continue;
|
|
125
|
+
yield* this.setupRepoAt({
|
|
126
|
+
dir: subDir,
|
|
127
|
+
remoteURL: m.url,
|
|
128
|
+
user: input.user,
|
|
129
|
+
pubkeyPath: input.pubkeyPath
|
|
130
|
+
}, topLevelRoot);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type EffectGen} from "effective-modules";
|
|
2
|
+
import { Option } from "effect";
|
|
3
|
+
|
|
4
|
+
export interface GitUser {
|
|
5
|
+
name: string;
|
|
6
|
+
email: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SetupRepoInput {
|
|
10
|
+
dir: string;
|
|
11
|
+
user: GitUser;
|
|
12
|
+
pubkeyPath: string;
|
|
13
|
+
remoteURL: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IGit {
|
|
17
|
+
isGitRepository(): EffectGen<boolean>;
|
|
18
|
+
getRemoteOrigin(): EffectGen<Option.Option<string>>;
|
|
19
|
+
resolveOwnerAndRepo(maybeOwnerAndRepo: Option.Option<string>): EffectGen<{owner: string; repo: string;}, string>;
|
|
20
|
+
clone(owner: string, repo: string): EffectGen<void, string>;
|
|
21
|
+
setLocalConfig(key: string, value: string): EffectGen<void, string>;
|
|
22
|
+
setupRepo(params: SetupRepoInput): EffectGen<void, string>;
|
|
23
|
+
}
|