@prover-coder-ai/docker-git 1.0.10 → 1.0.12
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/.package.json.release.bak +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +5 -1
- package/dist/main.js +165 -165
- package/dist/main.js.map +1 -1
- package/dist/src/docker-git/cli/parser-clone.js +2 -1
- package/dist/src/docker-git/cli/parser-mcp-playwright.js +18 -0
- package/dist/src/docker-git/cli/parser-options.js +2 -0
- package/dist/src/docker-git/cli/parser.js +3 -2
- package/dist/src/docker-git/cli/usage.js +3 -0
- package/dist/src/docker-git/program.js +2 -1
- package/package.json +1 -1
- package/src/docker-git/cli/parser-clone.ts +2 -1
- package/src/docker-git/cli/parser-mcp-playwright.ts +25 -0
- package/src/docker-git/cli/parser-options.ts +2 -0
- package/src/docker-git/cli/parser.ts +7 -3
- package/src/docker-git/cli/usage.ts +3 -0
- package/src/docker-git/program.ts +5 -1
- package/tests/docker-git/parser.test.ts +44 -0
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -93,6 +93,9 @@ Disable sharing (per-project auth):
|
|
|
93
93
|
Enable during create/clone:
|
|
94
94
|
- Add `--mcp-playwright`
|
|
95
95
|
|
|
96
|
+
Enable for an existing project directory (preserves `.orch/env/project.env` and volumes):
|
|
97
|
+
- `docker-git mcp-playwright [<url>] [--project-dir <path>]`
|
|
98
|
+
|
|
96
99
|
This will:
|
|
97
100
|
- Create a Chromium sidecar container: `dg-<repo>-browser`
|
|
98
101
|
- Configure Codex MCP server `playwright` inside the dev container
|
|
@@ -119,7 +122,8 @@ Common toggles:
|
|
|
119
122
|
MCP errors in `codex` UI:
|
|
120
123
|
- `No such file or directory (os error 2)` for `playwright`:
|
|
121
124
|
- `~/.codex/config.toml` contains `[mcp_servers.playwright]`, but the container was created without `--mcp-playwright`.
|
|
122
|
-
- Fix:
|
|
125
|
+
- Fix (recommended): run `docker-git mcp-playwright [<url>]` to enable it for the existing project.
|
|
126
|
+
- Fix (recreate): recreate with `--force-env --mcp-playwright` (keeps volumes) or `--force --mcp-playwright` (wipes volumes).
|
|
123
127
|
- `handshaking ... initialize response`:
|
|
124
128
|
- The configured MCP command is not a real MCP server (example: `command="echo"`).
|
|
125
129
|
|
package/dist/main.js
CHANGED
|
@@ -273,6 +273,51 @@ const runDockerPsNames = (cwd) => pipe(runCommandCapture({
|
|
|
273
273
|
command: "docker",
|
|
274
274
|
args: ["ps", "--format", "{{.Names}}"]
|
|
275
275
|
}, [Number(ExitCode(0))], (exitCode) => new CommandFailedError({ command: "docker ps", exitCode })), Effect.map((output) => output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0)));
|
|
276
|
+
const isParseError = (error) => error._tag === "UnknownCommand" || error._tag === "UnknownOption" || error._tag === "MissingOptionValue" || error._tag === "MissingRequiredOption" || error._tag === "InvalidOption" || error._tag === "UnexpectedArgument";
|
|
277
|
+
const renderDockerAccessHeadline = (issue) => issue === "PermissionDenied" ? "Cannot access Docker daemon socket: permission denied." : "Cannot connect to Docker daemon.";
|
|
278
|
+
const renderPrimaryError = (error) => Match.value(error).pipe(Match.when({ _tag: "FileExistsError" }, ({ path }) => `File already exists: ${path} (use --force to overwrite)`), Match.when({ _tag: "DockerCommandError" }, ({ exitCode }) => [
|
|
279
|
+
`docker compose failed with exit code ${exitCode}`,
|
|
280
|
+
"Hint: ensure Docker daemon is running and current user can access /var/run/docker.sock (for example via the docker group)."
|
|
281
|
+
].join("\n")), Match.when({ _tag: "DockerAccessError" }, ({ details, issue }) => [
|
|
282
|
+
renderDockerAccessHeadline(issue),
|
|
283
|
+
"Hint: ensure Docker daemon is running and current user can access the docker socket.",
|
|
284
|
+
"Hint: if you use rootless Docker, set DOCKER_HOST to your user socket (for example unix:///run/user/$UID/docker.sock).",
|
|
285
|
+
`Details: ${details}`
|
|
286
|
+
].join("\n")), Match.when({ _tag: "CloneFailedError" }, ({ repoRef, repoUrl, targetDir }) => `Clone failed for ${repoUrl} (${repoRef}) into ${targetDir}`), Match.when({ _tag: "PortProbeError" }, ({ message, port }) => `SSH port check failed for ${port}: ${message}`), Match.when({ _tag: "CommandFailedError" }, ({ command, exitCode }) => `${command} failed with exit code ${exitCode}`), Match.when({ _tag: "ScrapArchiveNotFoundError" }, ({ path }) => `Scrap archive not found: ${path} (run docker-git scrap export first)`), Match.when({ _tag: "ScrapArchiveInvalidError" }, ({ message, path }) => `Invalid scrap archive: ${path}
|
|
287
|
+
Details: ${message}`), Match.when({ _tag: "ScrapTargetDirUnsupportedError" }, ({ reason, sshUser, targetDir }) => [
|
|
288
|
+
`Cannot use scrap with targetDir ${targetDir}.`,
|
|
289
|
+
`Reason: ${reason}`,
|
|
290
|
+
`Hint: scrap currently supports workspaces under /home/${sshUser}/... only.`
|
|
291
|
+
].join("\n")), Match.when({ _tag: "ScrapWipeRefusedError" }, ({ reason, targetDir }) => [
|
|
292
|
+
`Refusing to wipe workspace for scrap import (targetDir ${targetDir}).`,
|
|
293
|
+
`Reason: ${reason}`,
|
|
294
|
+
"Hint: re-run with --no-wipe, or set a narrower --target-dir when creating the project."
|
|
295
|
+
].join("\n")), Match.when({ _tag: "AuthError" }, ({ message }) => message), Match.orElse(() => null));
|
|
296
|
+
const renderConfigError = (error) => {
|
|
297
|
+
if (error._tag === "ConfigNotFoundError") {
|
|
298
|
+
return `docker-git.json not found: ${error.path} (run docker-git create in that directory)`;
|
|
299
|
+
}
|
|
300
|
+
if (error._tag === "ConfigDecodeError") {
|
|
301
|
+
return `Invalid docker-git.json at ${error.path}: ${error.message}`;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
};
|
|
305
|
+
const renderInputError = (error) => {
|
|
306
|
+
if (error._tag === "InputCancelledError") {
|
|
307
|
+
return "Input cancelled.";
|
|
308
|
+
}
|
|
309
|
+
if (error._tag === "InputReadError") {
|
|
310
|
+
return `Input error: ${error.message}`;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
};
|
|
314
|
+
const renderNonParseError = (error) => renderPrimaryError(error) ?? renderConfigError(error) ?? renderInputError(error) ?? error.message;
|
|
315
|
+
const renderError = (error) => {
|
|
316
|
+
if (isParseError(error)) {
|
|
317
|
+
return formatParseError(error);
|
|
318
|
+
}
|
|
319
|
+
return renderNonParseError(error);
|
|
320
|
+
};
|
|
276
321
|
const splitLines = (input) => input.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
|
|
277
322
|
const isAlpha = (char) => {
|
|
278
323
|
const code = char.codePointAt(0) ?? 0;
|
|
@@ -456,85 +501,6 @@ Authorized keys: ${authorizedKeysPath}${authorizedKeysExists ? "" : " (missing)"
|
|
|
456
501
|
Env global: ${config.template.envGlobalPath}
|
|
457
502
|
Env project: ${config.template.envProjectPath}
|
|
458
503
|
Codex auth: ${config.template.codexAuthPath} -> ${config.template.codexHome}`;
|
|
459
|
-
const successExitCode = Number(ExitCode(0));
|
|
460
|
-
const gitBaseEnv = {
|
|
461
|
-
// Avoid blocking on interactive credential prompts in CI / TUI contexts.
|
|
462
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
463
|
-
};
|
|
464
|
-
const git = (cwd, args, env = gitBaseEnv) => runCommandWithExitCodes({ cwd, command: "git", args, env }, [successExitCode], (exitCode) => new CommandFailedError({ command: `git ${args[0] ?? ""}`, exitCode }));
|
|
465
|
-
const gitExitCode = (cwd, args, env = gitBaseEnv) => runCommandExitCode({ cwd, command: "git", args, env });
|
|
466
|
-
const gitCapture = (cwd, args, env = gitBaseEnv) => runCommandCapture({ cwd, command: "git", args, env }, [successExitCode], (exitCode) => new CommandFailedError({ command: `git ${args[0] ?? ""}`, exitCode }));
|
|
467
|
-
const githubTokenKey = "GITHUB_TOKEN";
|
|
468
|
-
const isGithubHttpsRemote = (url) => /^https:\/\/github\.com\//.test(url.trim());
|
|
469
|
-
const resolveTokenFromProcessEnv = () => {
|
|
470
|
-
const github = process.env["GITHUB_TOKEN"];
|
|
471
|
-
if (github !== void 0) {
|
|
472
|
-
const trimmed = github.trim();
|
|
473
|
-
if (trimmed.length > 0) {
|
|
474
|
-
return trimmed;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
const gh = process.env["GH_TOKEN"];
|
|
478
|
-
if (gh !== void 0) {
|
|
479
|
-
const trimmed = gh.trim();
|
|
480
|
-
if (trimmed.length > 0) {
|
|
481
|
-
return trimmed;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
return null;
|
|
485
|
-
};
|
|
486
|
-
const findTokenInEnvEntries = (entries) => {
|
|
487
|
-
const directEntry = entries.find((e) => e.key === githubTokenKey);
|
|
488
|
-
if (directEntry !== void 0) {
|
|
489
|
-
const direct = directEntry.value.trim();
|
|
490
|
-
if (direct.length > 0) {
|
|
491
|
-
return direct;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
const labeledEntry = entries.find((e) => e.key.startsWith("GITHUB_TOKEN__"));
|
|
495
|
-
if (labeledEntry !== void 0) {
|
|
496
|
-
const labeled = labeledEntry.value.trim();
|
|
497
|
-
if (labeled.length > 0) {
|
|
498
|
-
return labeled;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return null;
|
|
502
|
-
};
|
|
503
|
-
const resolveGithubToken = (fs, path, root) => Effect.gen(function* (_) {
|
|
504
|
-
const fromEnv = resolveTokenFromProcessEnv();
|
|
505
|
-
if (fromEnv !== null) {
|
|
506
|
-
return fromEnv;
|
|
507
|
-
}
|
|
508
|
-
const envPath = path.join(root, ".orch", "env", "global.env");
|
|
509
|
-
const exists = yield* _(fs.exists(envPath));
|
|
510
|
-
if (!exists) {
|
|
511
|
-
return null;
|
|
512
|
-
}
|
|
513
|
-
const text = yield* _(fs.readFileString(envPath));
|
|
514
|
-
return findTokenInEnvEntries(parseEnvEntries(text));
|
|
515
|
-
});
|
|
516
|
-
const withGithubAskpassEnv = (token, use) => Effect.scoped(Effect.gen(function* (_) {
|
|
517
|
-
const fs = yield* _(FileSystem.FileSystem);
|
|
518
|
-
const askpassPath = yield* _(fs.makeTempFileScoped({ prefix: "docker-git-askpass-" }));
|
|
519
|
-
const contents = [
|
|
520
|
-
"#!/bin/sh",
|
|
521
|
-
'case "$1" in',
|
|
522
|
-
' *Username*) echo "x-access-token" ;;',
|
|
523
|
-
' *Password*) echo "${DOCKER_GIT_GITHUB_TOKEN}" ;;',
|
|
524
|
-
' *) echo "${DOCKER_GIT_GITHUB_TOKEN}" ;;',
|
|
525
|
-
"esac",
|
|
526
|
-
""
|
|
527
|
-
].join("\n");
|
|
528
|
-
yield* _(fs.writeFileString(askpassPath, contents));
|
|
529
|
-
yield* _(fs.chmod(askpassPath, 448));
|
|
530
|
-
const env = {
|
|
531
|
-
...gitBaseEnv,
|
|
532
|
-
DOCKER_GIT_GITHUB_TOKEN: token,
|
|
533
|
-
GIT_ASKPASS: askpassPath,
|
|
534
|
-
GIT_ASKPASS_REQUIRE: "force"
|
|
535
|
-
};
|
|
536
|
-
return yield* _(use(env));
|
|
537
|
-
}));
|
|
538
504
|
const isDockerGitConfig = (entry) => entry.endsWith("docker-git.json");
|
|
539
505
|
const shouldSkipDir = (entry) => entry === ".git" || entry === ".orch";
|
|
540
506
|
const processDockerGitEntry = (fs, path, dir, entry, state) => Effect.gen(function* (_) {
|
|
@@ -571,92 +537,6 @@ const findDockerGitConfigPaths = (fs, path, rootDir) => Effect.gen(function* (_)
|
|
|
571
537
|
}
|
|
572
538
|
return results;
|
|
573
539
|
});
|
|
574
|
-
const resolveStateRoot = (path, cwd) => path.resolve(defaultProjectsRoot(cwd));
|
|
575
|
-
Effect.gen(function* (_) {
|
|
576
|
-
const path = yield* _(Path.Path);
|
|
577
|
-
const cwd = process.cwd();
|
|
578
|
-
const root = resolveStateRoot(path, cwd);
|
|
579
|
-
yield* _(Effect.log(root));
|
|
580
|
-
}).pipe(Effect.asVoid);
|
|
581
|
-
Effect.gen(function* (_) {
|
|
582
|
-
const path = yield* _(Path.Path);
|
|
583
|
-
const root = resolveStateRoot(path, process.cwd());
|
|
584
|
-
const output = yield* _(gitCapture(root, ["status", "-sb", "--porcelain=v1"], gitBaseEnv));
|
|
585
|
-
yield* _(Effect.log(output.trim().length > 0 ? output.trimEnd() : "(clean)"));
|
|
586
|
-
}).pipe(Effect.asVoid);
|
|
587
|
-
Effect.gen(function* (_) {
|
|
588
|
-
const fs = yield* _(FileSystem.FileSystem);
|
|
589
|
-
const path = yield* _(Path.Path);
|
|
590
|
-
const root = resolveStateRoot(path, process.cwd());
|
|
591
|
-
const originUrlExit = yield* _(gitExitCode(root, ["remote", "get-url", "origin"], gitBaseEnv));
|
|
592
|
-
if (originUrlExit !== successExitCode) {
|
|
593
|
-
yield* _(git(root, ["pull", "--rebase"], gitBaseEnv));
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
const originUrl = yield* _(gitCapture(root, ["remote", "get-url", "origin"], gitBaseEnv).pipe(Effect.map((value) => value.trim())));
|
|
597
|
-
const token = yield* _(resolveGithubToken(fs, path, root));
|
|
598
|
-
const effect = token && token.length > 0 && isGithubHttpsRemote(originUrl) ? withGithubAskpassEnv(token, (env) => git(root, ["pull", "--rebase"], env)) : git(root, ["pull", "--rebase"], gitBaseEnv);
|
|
599
|
-
yield* _(effect);
|
|
600
|
-
}).pipe(Effect.asVoid);
|
|
601
|
-
Effect.gen(function* (_) {
|
|
602
|
-
const fs = yield* _(FileSystem.FileSystem);
|
|
603
|
-
const path = yield* _(Path.Path);
|
|
604
|
-
const root = resolveStateRoot(path, process.cwd());
|
|
605
|
-
const originUrlExit = yield* _(gitExitCode(root, ["remote", "get-url", "origin"], gitBaseEnv));
|
|
606
|
-
if (originUrlExit !== successExitCode) {
|
|
607
|
-
yield* _(git(root, ["push", "-u", "origin", "HEAD"], gitBaseEnv));
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
const originUrl = yield* _(gitCapture(root, ["remote", "get-url", "origin"], gitBaseEnv).pipe(Effect.map((value) => value.trim())));
|
|
611
|
-
const token = yield* _(resolveGithubToken(fs, path, root));
|
|
612
|
-
const effect = token && token.length > 0 && isGithubHttpsRemote(originUrl) ? withGithubAskpassEnv(token, (env) => git(root, ["push", "-u", "origin", "HEAD"], env)) : git(root, ["push", "-u", "origin", "HEAD"], gitBaseEnv);
|
|
613
|
-
yield* _(effect);
|
|
614
|
-
}).pipe(Effect.asVoid);
|
|
615
|
-
const isParseError = (error) => error._tag === "UnknownCommand" || error._tag === "UnknownOption" || error._tag === "MissingOptionValue" || error._tag === "MissingRequiredOption" || error._tag === "InvalidOption" || error._tag === "UnexpectedArgument";
|
|
616
|
-
const renderDockerAccessHeadline = (issue) => issue === "PermissionDenied" ? "Cannot access Docker daemon socket: permission denied." : "Cannot connect to Docker daemon.";
|
|
617
|
-
const renderPrimaryError = (error) => Match.value(error).pipe(Match.when({ _tag: "FileExistsError" }, ({ path }) => `File already exists: ${path} (use --force to overwrite)`), Match.when({ _tag: "DockerCommandError" }, ({ exitCode }) => [
|
|
618
|
-
`docker compose failed with exit code ${exitCode}`,
|
|
619
|
-
"Hint: ensure Docker daemon is running and current user can access /var/run/docker.sock (for example via the docker group)."
|
|
620
|
-
].join("\n")), Match.when({ _tag: "DockerAccessError" }, ({ details, issue }) => [
|
|
621
|
-
renderDockerAccessHeadline(issue),
|
|
622
|
-
"Hint: ensure Docker daemon is running and current user can access the docker socket.",
|
|
623
|
-
"Hint: if you use rootless Docker, set DOCKER_HOST to your user socket (for example unix:///run/user/$UID/docker.sock).",
|
|
624
|
-
`Details: ${details}`
|
|
625
|
-
].join("\n")), Match.when({ _tag: "CloneFailedError" }, ({ repoRef, repoUrl, targetDir }) => `Clone failed for ${repoUrl} (${repoRef}) into ${targetDir}`), Match.when({ _tag: "PortProbeError" }, ({ message, port }) => `SSH port check failed for ${port}: ${message}`), Match.when({ _tag: "CommandFailedError" }, ({ command, exitCode }) => `${command} failed with exit code ${exitCode}`), Match.when({ _tag: "ScrapArchiveNotFoundError" }, ({ path }) => `Scrap archive not found: ${path} (run docker-git scrap export first)`), Match.when({ _tag: "ScrapArchiveInvalidError" }, ({ message, path }) => `Invalid scrap archive: ${path}
|
|
626
|
-
Details: ${message}`), Match.when({ _tag: "ScrapTargetDirUnsupportedError" }, ({ reason, sshUser, targetDir }) => [
|
|
627
|
-
`Cannot use scrap with targetDir ${targetDir}.`,
|
|
628
|
-
`Reason: ${reason}`,
|
|
629
|
-
`Hint: scrap currently supports workspaces under /home/${sshUser}/... only.`
|
|
630
|
-
].join("\n")), Match.when({ _tag: "ScrapWipeRefusedError" }, ({ reason, targetDir }) => [
|
|
631
|
-
`Refusing to wipe workspace for scrap import (targetDir ${targetDir}).`,
|
|
632
|
-
`Reason: ${reason}`,
|
|
633
|
-
"Hint: re-run with --no-wipe, or set a narrower --target-dir when creating the project."
|
|
634
|
-
].join("\n")), Match.when({ _tag: "AuthError" }, ({ message }) => message), Match.orElse(() => null));
|
|
635
|
-
const renderConfigError = (error) => {
|
|
636
|
-
if (error._tag === "ConfigNotFoundError") {
|
|
637
|
-
return `docker-git.json not found: ${error.path} (run docker-git create in that directory)`;
|
|
638
|
-
}
|
|
639
|
-
if (error._tag === "ConfigDecodeError") {
|
|
640
|
-
return `Invalid docker-git.json at ${error.path}: ${error.message}`;
|
|
641
|
-
}
|
|
642
|
-
return null;
|
|
643
|
-
};
|
|
644
|
-
const renderInputError = (error) => {
|
|
645
|
-
if (error._tag === "InputCancelledError") {
|
|
646
|
-
return "Input cancelled.";
|
|
647
|
-
}
|
|
648
|
-
if (error._tag === "InputReadError") {
|
|
649
|
-
return `Input error: ${error.message}`;
|
|
650
|
-
}
|
|
651
|
-
return null;
|
|
652
|
-
};
|
|
653
|
-
const renderNonParseError = (error) => renderPrimaryError(error) ?? renderConfigError(error) ?? renderInputError(error) ?? error.message;
|
|
654
|
-
const renderError = (error) => {
|
|
655
|
-
if (isParseError(error)) {
|
|
656
|
-
return formatParseError(error);
|
|
657
|
-
}
|
|
658
|
-
return renderNonParseError(error);
|
|
659
|
-
};
|
|
660
540
|
const sshOptions = "-tt -Y -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
|
661
541
|
const buildSshCommand = (config, sshKey) => sshKey === null ? `ssh ${sshOptions} -p ${config.sshPort} ${config.sshUser}@localhost` : `ssh -i ${sshKey} ${sshOptions} -p ${config.sshPort} ${config.sshUser}@localhost`;
|
|
662
542
|
const loadProjectBase = (configPath) => Effect.gen(function* (_) {
|
|
@@ -773,6 +653,126 @@ const withProjectIndexAndSsh = (run) => pipe(loadProjectIndex(), Effect.flatMap(
|
|
|
773
653
|
const sshKey = yield* _(findSshPrivateKey(fs, path, process.cwd()));
|
|
774
654
|
return yield* _(run(index, sshKey));
|
|
775
655
|
})));
|
|
656
|
+
const successExitCode = Number(ExitCode(0));
|
|
657
|
+
const gitBaseEnv = {
|
|
658
|
+
// Avoid blocking on interactive credential prompts in CI / TUI contexts.
|
|
659
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
660
|
+
};
|
|
661
|
+
const git = (cwd, args, env = gitBaseEnv) => runCommandWithExitCodes({ cwd, command: "git", args, env }, [successExitCode], (exitCode) => new CommandFailedError({ command: `git ${args[0] ?? ""}`, exitCode }));
|
|
662
|
+
const gitExitCode = (cwd, args, env = gitBaseEnv) => runCommandExitCode({ cwd, command: "git", args, env });
|
|
663
|
+
const gitCapture = (cwd, args, env = gitBaseEnv) => runCommandCapture({ cwd, command: "git", args, env }, [successExitCode], (exitCode) => new CommandFailedError({ command: `git ${args[0] ?? ""}`, exitCode }));
|
|
664
|
+
const githubTokenKey = "GITHUB_TOKEN";
|
|
665
|
+
const isGithubHttpsRemote = (url) => /^https:\/\/github\.com\//.test(url.trim());
|
|
666
|
+
const resolveTokenFromProcessEnv = () => {
|
|
667
|
+
const github = process.env["GITHUB_TOKEN"];
|
|
668
|
+
if (github !== void 0) {
|
|
669
|
+
const trimmed = github.trim();
|
|
670
|
+
if (trimmed.length > 0) {
|
|
671
|
+
return trimmed;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const gh = process.env["GH_TOKEN"];
|
|
675
|
+
if (gh !== void 0) {
|
|
676
|
+
const trimmed = gh.trim();
|
|
677
|
+
if (trimmed.length > 0) {
|
|
678
|
+
return trimmed;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return null;
|
|
682
|
+
};
|
|
683
|
+
const findTokenInEnvEntries = (entries) => {
|
|
684
|
+
const directEntry = entries.find((e) => e.key === githubTokenKey);
|
|
685
|
+
if (directEntry !== void 0) {
|
|
686
|
+
const direct = directEntry.value.trim();
|
|
687
|
+
if (direct.length > 0) {
|
|
688
|
+
return direct;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const labeledEntry = entries.find((e) => e.key.startsWith("GITHUB_TOKEN__"));
|
|
692
|
+
if (labeledEntry !== void 0) {
|
|
693
|
+
const labeled = labeledEntry.value.trim();
|
|
694
|
+
if (labeled.length > 0) {
|
|
695
|
+
return labeled;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
};
|
|
700
|
+
const resolveGithubToken = (fs, path, root) => Effect.gen(function* (_) {
|
|
701
|
+
const fromEnv = resolveTokenFromProcessEnv();
|
|
702
|
+
if (fromEnv !== null) {
|
|
703
|
+
return fromEnv;
|
|
704
|
+
}
|
|
705
|
+
const envPath = path.join(root, ".orch", "env", "global.env");
|
|
706
|
+
const exists = yield* _(fs.exists(envPath));
|
|
707
|
+
if (!exists) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
const text = yield* _(fs.readFileString(envPath));
|
|
711
|
+
return findTokenInEnvEntries(parseEnvEntries(text));
|
|
712
|
+
});
|
|
713
|
+
const withGithubAskpassEnv = (token, use) => Effect.scoped(Effect.gen(function* (_) {
|
|
714
|
+
const fs = yield* _(FileSystem.FileSystem);
|
|
715
|
+
const askpassPath = yield* _(fs.makeTempFileScoped({ prefix: "docker-git-askpass-" }));
|
|
716
|
+
const contents = [
|
|
717
|
+
"#!/bin/sh",
|
|
718
|
+
'case "$1" in',
|
|
719
|
+
' *Username*) echo "x-access-token" ;;',
|
|
720
|
+
' *Password*) echo "${DOCKER_GIT_GITHUB_TOKEN}" ;;',
|
|
721
|
+
' *) echo "${DOCKER_GIT_GITHUB_TOKEN}" ;;',
|
|
722
|
+
"esac",
|
|
723
|
+
""
|
|
724
|
+
].join("\n");
|
|
725
|
+
yield* _(fs.writeFileString(askpassPath, contents));
|
|
726
|
+
yield* _(fs.chmod(askpassPath, 448));
|
|
727
|
+
const env = {
|
|
728
|
+
...gitBaseEnv,
|
|
729
|
+
DOCKER_GIT_GITHUB_TOKEN: token,
|
|
730
|
+
GIT_ASKPASS: askpassPath,
|
|
731
|
+
GIT_ASKPASS_REQUIRE: "force"
|
|
732
|
+
};
|
|
733
|
+
return yield* _(use(env));
|
|
734
|
+
}));
|
|
735
|
+
const resolveStateRoot = (path, cwd) => path.resolve(defaultProjectsRoot(cwd));
|
|
736
|
+
Effect.gen(function* (_) {
|
|
737
|
+
const path = yield* _(Path.Path);
|
|
738
|
+
const cwd = process.cwd();
|
|
739
|
+
const root = resolveStateRoot(path, cwd);
|
|
740
|
+
yield* _(Effect.log(root));
|
|
741
|
+
}).pipe(Effect.asVoid);
|
|
742
|
+
Effect.gen(function* (_) {
|
|
743
|
+
const path = yield* _(Path.Path);
|
|
744
|
+
const root = resolveStateRoot(path, process.cwd());
|
|
745
|
+
const output = yield* _(gitCapture(root, ["status", "-sb", "--porcelain=v1"], gitBaseEnv));
|
|
746
|
+
yield* _(Effect.log(output.trim().length > 0 ? output.trimEnd() : "(clean)"));
|
|
747
|
+
}).pipe(Effect.asVoid);
|
|
748
|
+
Effect.gen(function* (_) {
|
|
749
|
+
const fs = yield* _(FileSystem.FileSystem);
|
|
750
|
+
const path = yield* _(Path.Path);
|
|
751
|
+
const root = resolveStateRoot(path, process.cwd());
|
|
752
|
+
const originUrlExit = yield* _(gitExitCode(root, ["remote", "get-url", "origin"], gitBaseEnv));
|
|
753
|
+
if (originUrlExit !== successExitCode) {
|
|
754
|
+
yield* _(git(root, ["pull", "--rebase"], gitBaseEnv));
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const originUrl = yield* _(gitCapture(root, ["remote", "get-url", "origin"], gitBaseEnv).pipe(Effect.map((value) => value.trim())));
|
|
758
|
+
const token = yield* _(resolveGithubToken(fs, path, root));
|
|
759
|
+
const effect = token && token.length > 0 && isGithubHttpsRemote(originUrl) ? withGithubAskpassEnv(token, (env) => git(root, ["pull", "--rebase"], env)) : git(root, ["pull", "--rebase"], gitBaseEnv);
|
|
760
|
+
yield* _(effect);
|
|
761
|
+
}).pipe(Effect.asVoid);
|
|
762
|
+
Effect.gen(function* (_) {
|
|
763
|
+
const fs = yield* _(FileSystem.FileSystem);
|
|
764
|
+
const path = yield* _(Path.Path);
|
|
765
|
+
const root = resolveStateRoot(path, process.cwd());
|
|
766
|
+
const originUrlExit = yield* _(gitExitCode(root, ["remote", "get-url", "origin"], gitBaseEnv));
|
|
767
|
+
if (originUrlExit !== successExitCode) {
|
|
768
|
+
yield* _(git(root, ["push", "-u", "origin", "HEAD"], gitBaseEnv));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const originUrl = yield* _(gitCapture(root, ["remote", "get-url", "origin"], gitBaseEnv).pipe(Effect.map((value) => value.trim())));
|
|
772
|
+
const token = yield* _(resolveGithubToken(fs, path, root));
|
|
773
|
+
const effect = token && token.length > 0 && isGithubHttpsRemote(originUrl) ? withGithubAskpassEnv(token, (env) => git(root, ["push", "-u", "origin", "HEAD"], env)) : git(root, ["push", "-u", "origin", "HEAD"], gitBaseEnv);
|
|
774
|
+
yield* _(effect);
|
|
775
|
+
}).pipe(Effect.asVoid);
|
|
776
776
|
pipe(loadProjectIndex(), Effect.flatMap((index) => index === null ? Effect.void : forEachProjectStatus(index.configPaths, (status) => pipe(Effect.log(renderProjectStatusHeader(status)), Effect.zipRight(runDockerComposeDown(status.projectDir).pipe(Effect.catchTag("DockerCommandError", (error) => Effect.logWarning(`docker compose down failed for ${status.projectDir}: ${renderError(error)}`))))))), Effect.asVoid);
|
|
777
777
|
const listProjects = pipe(withProjectIndexAndSsh((index, sshKey) => Effect.gen(function* (_) {
|
|
778
778
|
const available = [];
|