@pablozaiden/devbox 0.1.3 → 0.1.5
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/README.md +2 -0
- package/dist/devbox.js +91 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ It does not modify the original `devcontainer.json`. Instead, it generates a der
|
|
|
12
12
|
- Publishes the same TCP port on host and container.
|
|
13
13
|
- Mounts the current directory into the container as the workspace.
|
|
14
14
|
- Shares a usable SSH agent socket with the container and copies `known_hosts` into the container.
|
|
15
|
+
- Seeds the container user's global Git `user.name` and `user.email` from the host when available.
|
|
15
16
|
- Runs the [`ssh-server-runner`](https://github.com/PabloZaiden/ssh-server-runner) one-liner inside the devcontainer.
|
|
16
17
|
- Persists the runner credentials on the mounted workspace as a local `.sshcred` file, and keeps SSH host keys in `.devbox-ssh-host-keys/`, so they survive `down` / `rebuild`.
|
|
17
18
|
|
|
@@ -108,6 +109,7 @@ cd examples/smoke-workspace
|
|
|
108
109
|
- When Docker Desktop host services are available, `devbox` can share the SSH agent without relying on a host-shell `SSH_AUTH_SOCK`.
|
|
109
110
|
- On Docker Desktop, `devbox` prefers the Docker-provided SSH agent socket over the host `SSH_AUTH_SOCK`, which avoids macOS launchd socket mount issues.
|
|
110
111
|
- `--allow-missing-ssh` starts the workspace without mounting an SSH agent and prints a warning instead of failing.
|
|
112
|
+
- When the host already has Git author identity configured, `devbox` copies it into the container user's global Git config if the container does not already define those values.
|
|
111
113
|
|
|
112
114
|
## Releasing to npm
|
|
113
115
|
|
package/dist/devbox.js
CHANGED
|
@@ -1305,12 +1305,22 @@ function formatDevcontainerProgressLine(line) {
|
|
|
1305
1305
|
return null;
|
|
1306
1306
|
}
|
|
1307
1307
|
if (type === "raw" && text === "Container started") {
|
|
1308
|
-
return "Container started.";
|
|
1308
|
+
return "Container started. Finishing devcontainer setup...";
|
|
1309
1309
|
}
|
|
1310
1310
|
if (text.startsWith("workspace root: ")) {
|
|
1311
1311
|
return `Workspace: ${text.slice("workspace root: ".length)}`;
|
|
1312
1312
|
}
|
|
1313
|
-
if (text === "
|
|
1313
|
+
if (text === "Inspecting container") {
|
|
1314
|
+
return "Inspecting container...";
|
|
1315
|
+
}
|
|
1316
|
+
if (text.startsWith("userEnvProbe")) {
|
|
1317
|
+
return "Checking container environment...";
|
|
1318
|
+
}
|
|
1319
|
+
const lifecycleProgress = formatDevcontainerLifecycleProgress(text);
|
|
1320
|
+
if (lifecycleProgress) {
|
|
1321
|
+
return lifecycleProgress;
|
|
1322
|
+
}
|
|
1323
|
+
if (text === "No user features to update" || text.startsWith("Run: ") || text.startsWith("Run in container: ") || text.startsWith("Exit code ")) {
|
|
1314
1324
|
return null;
|
|
1315
1325
|
}
|
|
1316
1326
|
if (level !== undefined && level >= 2) {
|
|
@@ -1340,6 +1350,25 @@ function buildAssertConfiguredSshAuthSockScript() {
|
|
|
1340
1350
|
].join(`
|
|
1341
1351
|
`);
|
|
1342
1352
|
}
|
|
1353
|
+
function buildConfigureGitIdentityScript(input) {
|
|
1354
|
+
const commands = [];
|
|
1355
|
+
if (input.gitUserName) {
|
|
1356
|
+
commands.push('current_git_user_name="$(git config --global --get user.name 2>/dev/null || true)"', 'if [ -z "$current_git_user_name" ]; then', ` git config --global user.name ${quoteShell(input.gitUserName)}`, "fi");
|
|
1357
|
+
}
|
|
1358
|
+
if (input.gitUserEmail) {
|
|
1359
|
+
commands.push('current_git_user_email="$(git config --global --get user.email 2>/dev/null || true)"', 'if [ -z "$current_git_user_email" ]; then', ` git config --global user.email ${quoteShell(input.gitUserEmail)}`, "fi");
|
|
1360
|
+
}
|
|
1361
|
+
if (commands.length === 0) {
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
return [
|
|
1365
|
+
"if ! command -v git >/dev/null 2>&1; then",
|
|
1366
|
+
" exit 0",
|
|
1367
|
+
"fi",
|
|
1368
|
+
...commands
|
|
1369
|
+
].join(`
|
|
1370
|
+
`);
|
|
1371
|
+
}
|
|
1343
1372
|
function buildInteractiveShellScript() {
|
|
1344
1373
|
return [
|
|
1345
1374
|
"if command -v bash >/dev/null 2>&1; then",
|
|
@@ -1405,13 +1434,21 @@ async function ensureHostEnvironment(options) {
|
|
|
1405
1434
|
}
|
|
1406
1435
|
const hostEnvSshAuthSock = process.env.SSH_AUTH_SOCK?.trim() || undefined;
|
|
1407
1436
|
const hostEnvSockExists = hostEnvSshAuthSock ? await pathExists(hostEnvSshAuthSock) : false;
|
|
1408
|
-
const dockerDesktopHostServiceAvailable = await
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1437
|
+
const [dockerDesktopHostServiceAvailable, gitUserName, gitUserEmail] = await Promise.all([
|
|
1438
|
+
hasDockerDesktopHostService(),
|
|
1439
|
+
tryGetGitConfig(options.workspacePath, "user.name"),
|
|
1440
|
+
tryGetGitConfig(options.workspacePath, "user.email")
|
|
1441
|
+
]);
|
|
1442
|
+
return {
|
|
1443
|
+
...resolveSshAuthSockSource({
|
|
1444
|
+
hostEnvSshAuthSock,
|
|
1445
|
+
hostEnvSockExists,
|
|
1446
|
+
dockerDesktopHostServiceAvailable,
|
|
1447
|
+
allowMissingSsh: options.allowMissingSsh
|
|
1448
|
+
}),
|
|
1449
|
+
gitUserName,
|
|
1450
|
+
gitUserEmail
|
|
1451
|
+
};
|
|
1415
1452
|
}
|
|
1416
1453
|
async function ensureGeneratedConfigIgnored(workspacePath, generatedConfigPath) {
|
|
1417
1454
|
await ensurePathIgnored(workspacePath, generatedConfigPath);
|
|
@@ -1517,6 +1554,13 @@ async function copyKnownHosts(containerId) {
|
|
|
1517
1554
|
const script = `if [ -f ${quoteShell(KNOWN_HOSTS_TARGET)} ]; then umask 077 && mkdir -p ~/.ssh && cp ${quoteShell(KNOWN_HOSTS_TARGET)} ~/.ssh/known_hosts && chmod 600 ~/.ssh/known_hosts; fi`;
|
|
1518
1555
|
await devcontainerExec(containerId, script, { quiet: true });
|
|
1519
1556
|
}
|
|
1557
|
+
async function configureGitIdentity(containerId, gitUserName, gitUserEmail) {
|
|
1558
|
+
const script = buildConfigureGitIdentityScript({ gitUserName, gitUserEmail });
|
|
1559
|
+
if (!script) {
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
await devcontainerExec(containerId, script, { quiet: true });
|
|
1563
|
+
}
|
|
1520
1564
|
async function stopManagedSshd(containerId) {
|
|
1521
1565
|
await devcontainerExec(containerId, buildStopManagedSshdScript(), { quiet: true });
|
|
1522
1566
|
}
|
|
@@ -1642,6 +1686,22 @@ async function tryGetGitTopLevel(workspacePath) {
|
|
|
1642
1686
|
return null;
|
|
1643
1687
|
}
|
|
1644
1688
|
}
|
|
1689
|
+
async function tryGetGitConfig(workspacePath, key) {
|
|
1690
|
+
if (!isExecutableAvailable("git")) {
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
const result = await execute(["git", "config", "--get", key], {
|
|
1694
|
+
cwd: workspacePath,
|
|
1695
|
+
stdoutMode: "capture",
|
|
1696
|
+
stderrMode: "capture",
|
|
1697
|
+
allowFailure: true
|
|
1698
|
+
});
|
|
1699
|
+
if (result.exitCode !== 0) {
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1702
|
+
const trimmed = result.stdout.trim();
|
|
1703
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1704
|
+
}
|
|
1645
1705
|
async function findListeningPids(port) {
|
|
1646
1706
|
if (isExecutableAvailable("lsof")) {
|
|
1647
1707
|
const result = await execute(["lsof", "-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
|
|
@@ -1713,6 +1773,7 @@ async function consumeStream(stream, mode, useStderr) {
|
|
|
1713
1773
|
stream.setEncoding("utf8");
|
|
1714
1774
|
let captured = "";
|
|
1715
1775
|
let buffered = "";
|
|
1776
|
+
let lastRenderedProgressLine = null;
|
|
1716
1777
|
for await (const chunk of stream) {
|
|
1717
1778
|
const text = typeof chunk === "string" ? chunk : String(chunk);
|
|
1718
1779
|
captured += text;
|
|
@@ -1729,13 +1790,13 @@ async function consumeStream(stream, mode, useStderr) {
|
|
|
1729
1790
|
while (newlineIndex >= 0) {
|
|
1730
1791
|
const line = buffered.slice(0, newlineIndex);
|
|
1731
1792
|
buffered = buffered.slice(newlineIndex + 1);
|
|
1732
|
-
renderDevcontainerJsonLine(line, writer);
|
|
1793
|
+
lastRenderedProgressLine = renderDevcontainerJsonLine(line, writer, lastRenderedProgressLine);
|
|
1733
1794
|
newlineIndex = buffered.indexOf(`
|
|
1734
1795
|
`);
|
|
1735
1796
|
}
|
|
1736
1797
|
}
|
|
1737
1798
|
if (mode === "devcontainer-json" && buffered.length > 0) {
|
|
1738
|
-
renderDevcontainerJsonLine(buffered, writer);
|
|
1799
|
+
renderDevcontainerJsonLine(buffered, writer, lastRenderedProgressLine);
|
|
1739
1800
|
}
|
|
1740
1801
|
return captured;
|
|
1741
1802
|
}
|
|
@@ -1771,12 +1832,14 @@ function isExecutablePath(candidate) {
|
|
|
1771
1832
|
return false;
|
|
1772
1833
|
}
|
|
1773
1834
|
}
|
|
1774
|
-
function renderDevcontainerJsonLine(line, writer) {
|
|
1835
|
+
function renderDevcontainerJsonLine(line, writer, previousLine) {
|
|
1775
1836
|
const formatted = formatDevcontainerProgressLine(line);
|
|
1776
|
-
if (formatted) {
|
|
1837
|
+
if (formatted && formatted !== previousLine) {
|
|
1777
1838
|
writer.write(`${formatted}
|
|
1778
1839
|
`);
|
|
1840
|
+
return formatted;
|
|
1779
1841
|
}
|
|
1842
|
+
return previousLine;
|
|
1780
1843
|
}
|
|
1781
1844
|
function parseDevcontainerOutcome(stdout) {
|
|
1782
1845
|
const lines = stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
@@ -1806,6 +1869,16 @@ ${details}`;
|
|
|
1806
1869
|
function stripAnsi(text) {
|
|
1807
1870
|
return text.replace(/\u001B\[[0-9;]*m/g, "");
|
|
1808
1871
|
}
|
|
1872
|
+
function formatDevcontainerLifecycleProgress(text) {
|
|
1873
|
+
if (!text.startsWith("LifecycleCommandExecutionMap:")) {
|
|
1874
|
+
return null;
|
|
1875
|
+
}
|
|
1876
|
+
const commandMatch = text.match(/\b(initializeCommand|onCreateCommand|updateContentCommand|postCreateCommand|postStartCommand|postAttachCommand)\b/);
|
|
1877
|
+
if (commandMatch) {
|
|
1878
|
+
return `Running ${commandMatch[1]}...`;
|
|
1879
|
+
}
|
|
1880
|
+
return "Running devcontainer lifecycle commands...";
|
|
1881
|
+
}
|
|
1809
1882
|
function looksLikeDevcontainerUserEnvProbeDump(text) {
|
|
1810
1883
|
const match = text.match(/^([0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12})([\s\S]*)\1$/i);
|
|
1811
1884
|
if (!match) {
|
|
@@ -1840,7 +1913,7 @@ async function main() {
|
|
|
1840
1913
|
await handleUpLike(parsed.command, workspacePath, state, parsed.port, parsed.allowMissingSsh, parsed.devcontainerSubpath);
|
|
1841
1914
|
}
|
|
1842
1915
|
async function handleUpLike(command, workspacePath, state, explicitPort, allowMissingSsh, devcontainerSubpath) {
|
|
1843
|
-
const environment = await ensureHostEnvironment({ allowMissingSsh });
|
|
1916
|
+
const environment = await ensureHostEnvironment({ allowMissingSsh, workspacePath });
|
|
1844
1917
|
const port = resolvePort(command, explicitPort, state);
|
|
1845
1918
|
const knownHostsPath = await getKnownHostsPath();
|
|
1846
1919
|
const discovered = await discoverDevcontainerConfig(workspacePath, devcontainerSubpath);
|
|
@@ -1899,6 +1972,10 @@ async function handleUpLike(command, workspacePath, state, explicitPort, allowMi
|
|
|
1899
1972
|
await assertConfiguredSshAuthSockAvailable(upResult.containerId);
|
|
1900
1973
|
}
|
|
1901
1974
|
await copyKnownHosts(upResult.containerId);
|
|
1975
|
+
if (environment.gitUserName || environment.gitUserEmail) {
|
|
1976
|
+
console.log("Syncing Git author identity from the host into the devcontainer...");
|
|
1977
|
+
await configureGitIdentity(upResult.containerId, environment.gitUserName, environment.gitUserEmail);
|
|
1978
|
+
}
|
|
1902
1979
|
await stopManagedSshd(upResult.containerId);
|
|
1903
1980
|
await restoreRunnerHostKeys(upResult.containerId, remoteWorkspaceFolder);
|
|
1904
1981
|
console.log("Installing and starting the SSH server inside the container (first run can take a bit)...");
|