openship 0.6.9 → 0.7.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 (58) hide show
  1. package/dist/bare-3POKLHTW.js +29 -0
  2. package/dist/{chunk-UAWY6X7V.js → chunk-3KUW2UC6.js} +470 -59
  3. package/dist/{chunk-GNOMEMNI.js → chunk-3LWGDMJU.js} +1 -1
  4. package/dist/{chunk-ENKJ7XIF.js → chunk-5CFUYMPE.js} +11 -11
  5. package/dist/{chunk-NUT6HKK6.js → chunk-5D27WZBW.js} +5 -5
  6. package/dist/{chunk-NIYD7MD2.js → chunk-7MKZP4TW.js} +89 -45
  7. package/dist/{chunk-IAMOUDHY.js → chunk-AUBB5DLY.js} +24 -15
  8. package/dist/{chunk-7PEXM2UI.js → chunk-BOD2IQ3P.js} +6 -6
  9. package/dist/{chunk-D6HU4VUZ.js → chunk-CMGRMPRQ.js} +1 -1
  10. package/dist/{chunk-MUWVC5R4.js → chunk-DZBXOF5Q.js} +11 -8
  11. package/dist/{chunk-BO6CY4V2.js → chunk-GEHV7JBS.js} +47 -4
  12. package/dist/{chunk-R5DC5PEF.js → chunk-GYDROM4W.js} +39 -10
  13. package/dist/{chunk-TAZZZKYO.js → chunk-HBQWOJRP.js} +1 -1
  14. package/dist/{chunk-WEZFTL4V.js → chunk-IVRTIKNZ.js} +183 -0
  15. package/dist/{chunk-P7RCYELG.js → chunk-J2NDOB7D.js} +1 -1
  16. package/dist/{chunk-T6DVUX4F.js → chunk-J4SYG2LC.js} +14 -3
  17. package/dist/{chunk-VAI5VOOI.js → chunk-JAKVPLHT.js} +1 -1
  18. package/dist/{chunk-NPSRGIHV.js → chunk-JWW5YAFL.js} +5 -4
  19. package/dist/{chunk-FCGJN5Q5.js → chunk-KYTTPWRP.js} +9 -9
  20. package/dist/{chunk-7YBWXEM6.js → chunk-LL7M7GL4.js} +4 -4
  21. package/dist/{chunk-SK4FFCFR.js → chunk-MZYD3LFP.js} +1 -1
  22. package/dist/{chunk-6GU6JSK6.js → chunk-QNQFS37A.js} +3 -3
  23. package/dist/{chunk-T46BQOLY.js → chunk-RHHCAWW3.js} +3 -3
  24. package/dist/{chunk-5UXHT6ED.js → chunk-SPR7N2EK.js} +113 -20
  25. package/dist/{chunk-AHJYP7DG.js → chunk-SYQ3FOLE.js} +74 -17
  26. package/dist/{chunk-3MP6ACKM.js → chunk-TPGBT5UR.js} +185 -78
  27. package/dist/{chunk-634DDFT5.js → chunk-TT7P5ZZ3.js} +8 -8
  28. package/dist/{chunk-D3OEMXYP.js → chunk-XQC6KYZD.js} +12 -7
  29. package/dist/{chunk-X53JXRS4.js → chunk-YGA52DHX.js} +3 -3
  30. package/dist/{chunk-5DJOB7IX.js → chunk-ZFA3MZ5N.js} +3 -3
  31. package/dist/{cloud-REV3DPJH.js → cloud-RFZSO6P5.js} +19 -19
  32. package/dist/{detect-BBFT5GEU.js → detect-NXD3PYBM.js} +4 -4
  33. package/dist/{docker-EG3QSE6I.js → docker-U5APUZBI.js} +10 -10
  34. package/dist/{docker-edge-executor-E5TOTLMR.js → docker-edge-executor-HUQOWFQU.js} +7 -7
  35. package/dist/{edge-import-YJVJUGYC.js → edge-import-UXD52WQR.js} +26 -26
  36. package/dist/{edge-preflight-QUW4BM7N.js → edge-preflight-3PMJ5BWM.js} +13 -13
  37. package/dist/{ensure-container-edge-FOEFQ3PI.js → ensure-container-edge-GDPY7UJD.js} +11 -11
  38. package/dist/{executor-NTWDJ3DB.js → executor-XOVSO6QZ.js} +6 -6
  39. package/dist/{host-channel-preflight-OI2TRHSG.js → host-channel-preflight-T2QUDQXU.js} +26 -26
  40. package/dist/index.js +71 -49
  41. package/dist/{local-executor-OQ2GRN7K.js → local-executor-KR7BFCJP.js} +3 -3
  42. package/dist/{nginx-4U4C2XUS.js → nginx-JMLAS7YS.js} +9 -9
  43. package/dist/{openresty-lua-LLWT5XQW.js → openresty-lua-FJH3KEC4.js} +7 -7
  44. package/dist/{privilege-MNDVMDST.js → privilege-H2QTJ5TE.js} +5 -5
  45. package/dist/server/index.js +9627 -4439
  46. package/dist/server/lua/site_logger.lua +18 -1
  47. package/dist/server/migrations/0117_github_installation_workspace_scope.sql +24 -0
  48. package/dist/server/migrations/0118_git_sources.sql +66 -0
  49. package/dist/server/migrations/0119_backup_run_batch.sql +8 -0
  50. package/dist/server/migrations/0120_domain_primary_unique.sql +24 -0
  51. package/dist/server/migrations/0121_host_port_claim_overlap.sql +14 -0
  52. package/dist/server/migrations/meta/_journal.json +35 -0
  53. package/dist/{setup-IFIMPQZJ.js → setup-ERGE5XNW.js} +12 -12
  54. package/dist/{ssh-client-2XWZCMGI.js → ssh-client-WVLLOAYK.js} +2 -2
  55. package/dist/up-EFCE6KA6.js +61 -0
  56. package/package.json +1 -1
  57. package/dist/bare-FA66N7GC.js +0 -29
  58. package/dist/up-RC5ZQIAD.js +0 -61
@@ -5,7 +5,7 @@ const require = __ospCreateRequire(import.meta.url);
5
5
  import {
6
6
  getTarCreateEnv,
7
7
  prepareSourceTarArgs
8
- } from "./chunk-VAI5VOOI.js";
8
+ } from "./chunk-JAKVPLHT.js";
9
9
  import {
10
10
  TRANSFER_EXCLUDES,
11
11
  nodeBinDirs,
@@ -13,7 +13,7 @@ import {
13
13
  packageManagerEnsureCommand,
14
14
  safeErrorMessage,
15
15
  shellQuote
16
- } from "./chunk-WEZFTL4V.js";
16
+ } from "./chunk-IVRTIKNZ.js";
17
17
 
18
18
  // ../../packages/adapters/src/runtime/git-clone.ts
19
19
  var sq = shellQuote;
@@ -75,18 +75,48 @@ function assembleGitClone(auth) {
75
75
  );
76
76
  }
77
77
  if (auth.gitCredentialHelperPath) {
78
- return invocation(
78
+ const base = invocation(
79
79
  auth.repoUrl,
80
80
  [
81
81
  TERMINAL_PROMPT,
82
- ["GIT_CONFIG_COUNT", "2", false],
82
+ // An empty helper value resets helpers inherited from the host before
83
+ // adding the deployment-scoped relay. Without the reset, a server's
84
+ // stale/global helper can answer first and silently override the
85
+ // forwarded identity named in the deployment log.
86
+ ["GIT_CONFIG_COUNT", "3", false],
83
87
  ["GIT_CONFIG_KEY_0", "credential.helper", false],
84
- ["GIT_CONFIG_VALUE_0", auth.gitCredentialHelperPath, true],
85
- ["GIT_CONFIG_KEY_1", "credential.useHttpPath", false],
86
- ["GIT_CONFIG_VALUE_1", "true", false]
88
+ ["GIT_CONFIG_VALUE_0", "", true],
89
+ ["GIT_CONFIG_KEY_1", "credential.helper", false],
90
+ ["GIT_CONFIG_VALUE_1", auth.gitCredentialHelperPath, true],
91
+ ["GIT_CONFIG_KEY_2", "credential.useHttpPath", false],
92
+ ["GIT_CONFIG_VALUE_2", "true", false]
87
93
  ],
88
94
  false
89
95
  );
96
+ let url;
97
+ try {
98
+ url = new URL(auth.repoUrl);
99
+ } catch {
100
+ throw new Error("Git credential forwarding requires an absolute HTTPS repository URL");
101
+ }
102
+ if (url.protocol !== "https:") {
103
+ throw new Error("Git credential forwarding requires an HTTPS repository URL");
104
+ }
105
+ const protocol = url.protocol.slice(0, -1);
106
+ const path = url.pathname.replace(/^\/+/, "");
107
+ const headerVar = "OPENSHIP_GIT_AUTH_HEADER";
108
+ const headerCommand = `${sq(auth.gitCredentialHelperPath)} auth-header ${sq(protocol)} ${sq(url.hostname)} ${sq(path)}`;
109
+ return {
110
+ ...base,
111
+ // `auth-header` returns only the Basic header, never prints it to the
112
+ // build log, and keeps the token out of the command text and filesystem.
113
+ // The value exists only in the remote shell/git process for this command.
114
+ shellPrelude: `${headerVar}="$(${headerCommand})" && test -n "$${headerVar}"`,
115
+ // The argv representation above remains a valid challenge-based helper
116
+ // invocation for local spawn users. Remote shell users call
117
+ // gitShellCommand(), which adds the preemptive header as config slot 3.
118
+ gitEnv: `${base.gitEnv.replace("GIT_CONFIG_COUNT=3", "GIT_CONFIG_COUNT=4")} GIT_CONFIG_KEY_3=http.extraHeader GIT_CONFIG_VALUE_3="$${headerVar}"`
119
+ };
90
120
  }
91
121
  if (auth.ambient) {
92
122
  if (auth.ambient.via === "ssh") {
@@ -112,6 +142,12 @@ function assembleGitClone(auth) {
112
142
  true
113
143
  );
114
144
  }
145
+ function gitShellCommand(invocation2, args) {
146
+ const env = invocation2.gitEnv ? `${invocation2.gitEnv} ` : "";
147
+ const flags = invocation2.credFlag ? `${invocation2.credFlag} ` : "";
148
+ const command = `${env}git ${flags}${args}`;
149
+ return invocation2.shellPrelude ? `${invocation2.shellPrelude} && ${command}` : command;
150
+ }
115
151
 
116
152
  // ../../packages/adapters/src/runtime/git-ssh-material.ts
117
153
  import { mkdir, rm, writeFile } from "fs/promises";
@@ -253,31 +289,49 @@ async function runBuildPipeline(env, config, logger) {
253
289
  config.gitSsh
254
290
  );
255
291
  }
256
- const { cloneUrl, gitEnv: GIT_ENV, credFlag: CRED } = assembleGitClone({
292
+ const gitInvocation = assembleGitClone({
257
293
  repoUrl: config.repoUrl,
258
294
  gitToken: config.gitToken,
259
295
  gitCredentialHelperPath: config.gitCredentialHelperPath,
260
296
  ssh: sshMaterial,
261
297
  ambient: config.gitAmbient
262
298
  });
299
+ const { cloneUrl, credFlag: CRED } = gitInvocation;
263
300
  try {
264
301
  if (config.commitSha) {
265
- try {
266
- await exec(
267
- `${GIT_ENV} git ${CRED} clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)} && cd ${sq(env.projectDir)} && git ${CRED} -c advice.detachedHead=false checkout ${sq(config.commitSha)}`
268
- );
269
- } catch {
302
+ await exec(
303
+ gitShellCommand(
304
+ gitInvocation,
305
+ `clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)}`
306
+ )
307
+ );
308
+ const commitPresent = await exec(
309
+ `cd ${sq(env.projectDir)} && git ${CRED} cat-file -e ${sq(`${config.commitSha}^{commit}`)}`
310
+ ).then(
311
+ () => true,
312
+ () => false
313
+ );
314
+ if (!commitPresent) {
270
315
  logger.log(
271
- `Checkout of ${config.commitSha} failed inside the depth-50 clone; running git fetch --unshallow and retrying.`,
316
+ `Commit ${config.commitSha} is not in the depth-50 clone; fetching full history before checkout.`,
272
317
  "warn"
273
318
  );
274
319
  await exec(
275
- `cd ${sq(env.projectDir)} && ${GIT_ENV} git ${CRED} fetch --progress --unshallow && git ${CRED} -c advice.detachedHead=false checkout ${sq(config.commitSha)}`
320
+ `cd ${sq(env.projectDir)} && ${gitShellCommand(gitInvocation, "fetch --progress --unshallow")}`
276
321
  );
277
322
  }
323
+ await exec(
324
+ `cd ${sq(env.projectDir)} && ${gitShellCommand(
325
+ gitInvocation,
326
+ `-c advice.detachedHead=false checkout ${sq(config.commitSha)}`
327
+ )}`
328
+ );
278
329
  } else {
279
330
  await exec(
280
- `${GIT_ENV} git ${CRED} clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)}`
331
+ gitShellCommand(
332
+ gitInvocation,
333
+ `clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${sq(env.projectDir)}`
334
+ )
281
335
  );
282
336
  }
283
337
  } finally {
@@ -342,7 +396,9 @@ function parseLogLevel(message) {
342
396
  function detectBuildKillHint(output) {
343
397
  if (!output) return null;
344
398
  const tail = output.slice(-4096);
345
- if (/\bsigkill\b|\bKilled\b|out of memory|JavaScript heap out of memory|Allocation failed/i.test(tail)) {
399
+ if (/\bsigkill\b|\bKilled\b|out of memory|JavaScript heap out of memory|Allocation failed/i.test(
400
+ tail
401
+ )) {
346
402
  return "Build process was killed - typically because the target ran out of memory during the build. Increase RAM on the target, add swap, or build locally and ship the dist.";
347
403
  }
348
404
  return null;
@@ -913,6 +969,7 @@ export {
913
969
  sq,
914
970
  injectGitToken,
915
971
  assembleGitClone,
972
+ gitShellCommand,
916
973
  materializeGitSsh,
917
974
  shellGitSshWriter,
918
975
  localGitSshWriter,
@@ -9,11 +9,11 @@ import {
9
9
  prepareSourceTree,
10
10
  resolveDockerBuildArgs,
11
11
  resolveServiceDockerfile
12
- } from "./chunk-6GU6JSK6.js";
12
+ } from "./chunk-QNQFS37A.js";
13
13
  import {
14
14
  isArtifactPathRef,
15
15
  removeManagedArtifact
16
- } from "./chunk-SK4FFCFR.js";
16
+ } from "./chunk-MZYD3LFP.js";
17
17
  import {
18
18
  BuildCancelledError,
19
19
  BuildLogger,
@@ -22,6 +22,7 @@ import {
22
22
  dockerBuildContextDirectory,
23
23
  droppedRuntimeEnvMessage,
24
24
  generateDockerfile,
25
+ gitShellCommand,
25
26
  isExcludedDocRootEntry,
26
27
  killProcessesUnderDir,
27
28
  materializeGitSsh,
@@ -34,7 +35,7 @@ import {
34
35
  sq,
35
36
  staticBuilderOutputPath,
36
37
  transferLocalDirectory
37
- } from "./chunk-AHJYP7DG.js";
38
+ } from "./chunk-SYQ3FOLE.js";
38
39
  import {
39
40
  DOCKER_DIAL_STDIO_COMMAND,
40
41
  attachDialStdioDiagnostics,
@@ -43,14 +44,14 @@ import {
43
44
  execSshCommand,
44
45
  openDockerDialStdioChannel,
45
46
  openSshUnixSocket
46
- } from "./chunk-TAZZZKYO.js";
47
+ } from "./chunk-HBQWOJRP.js";
47
48
  import {
48
49
  dirOf,
49
50
  ensureOwnedDir
50
- } from "./chunk-NPSRGIHV.js";
51
+ } from "./chunk-JWW5YAFL.js";
51
52
  import {
52
53
  OPENSHIP_LABEL
53
- } from "./chunk-D6HU4VUZ.js";
54
+ } from "./chunk-CMGRMPRQ.js";
54
55
  import {
55
56
  isRuntimeNotFoundError
56
57
  } from "./chunk-N5YZMO33.js";
@@ -64,7 +65,7 @@ import {
64
65
  registryForImage,
65
66
  safeErrorMessage,
66
67
  withTimeout
67
- } from "./chunk-WEZFTL4V.js";
68
+ } from "./chunk-IVRTIKNZ.js";
68
69
  import {
69
70
  __commonJS,
70
71
  __require,
@@ -4630,6 +4631,41 @@ import { createGzip } from "zlib";
4630
4631
  import { PassThrough, Writable } from "stream";
4631
4632
  import { relative, sep } from "path";
4632
4633
 
4634
+ // ../../packages/adapters/src/runtime/docker-container-info.ts
4635
+ function bindingPriority(ip) {
4636
+ const normalized = ip?.trim().replace(/^\[|\]$/g, "") ?? "";
4637
+ if (normalized === "127.0.0.1" || normalized === "::1") return 3;
4638
+ if (/^127(?:\.\d{1,3}){3}$/.test(normalized)) return 2;
4639
+ if (normalized && normalized !== "0.0.0.0" && normalized !== "::") return 1;
4640
+ return 0;
4641
+ }
4642
+ function dockerPublishedPortInfo(ports) {
4643
+ const selected = /* @__PURE__ */ new Map();
4644
+ for (const binding of ports) {
4645
+ if ((binding.type || "tcp").toLowerCase() !== "tcp") continue;
4646
+ if (!Number.isSafeInteger(binding.privatePort) || binding.privatePort < 1 || binding.privatePort > 65535) {
4647
+ continue;
4648
+ }
4649
+ const hostPort2 = binding.publicPort;
4650
+ if (typeof hostPort2 !== "number" || !Number.isSafeInteger(hostPort2) || hostPort2 < 1 || hostPort2 > 65535) {
4651
+ continue;
4652
+ }
4653
+ const priority = bindingPriority(binding.ip);
4654
+ const previous = selected.get(binding.privatePort);
4655
+ if (!previous || priority > previous.priority) {
4656
+ selected.set(binding.privatePort, { hostPort: hostPort2, priority });
4657
+ }
4658
+ }
4659
+ const hostPortByContainerPort = Object.fromEntries(
4660
+ [...selected].map(([containerPort, binding]) => [containerPort, binding.hostPort])
4661
+ );
4662
+ const hostPort = selected.values().next().value?.hostPort;
4663
+ return {
4664
+ ...hostPort !== void 0 ? { hostPort } : {},
4665
+ ...selected.size > 0 ? { hostPortByContainerPort } : {}
4666
+ };
4667
+ }
4668
+
4633
4669
  // ../../packages/adapters/src/runtime/docker-auth.ts
4634
4670
  import { readFile } from "fs/promises";
4635
4671
  import { homedir } from "os";
@@ -4805,11 +4841,13 @@ function bridgeSocket(socket, leftover) {
4805
4841
  });
4806
4842
  return out;
4807
4843
  }
4808
- function upgradeRequest(conn, path, body) {
4844
+ function upgradeRequest(conn, path, body, opts = {}) {
4809
4845
  return new Promise((resolve, reject) => {
4810
4846
  const socket = connect(conn);
4811
4847
  let settled = false;
4812
4848
  let head = Buffer.alloc(0);
4849
+ const protocol = opts.protocol ?? "tcp";
4850
+ const timeoutLabel = opts.timeoutLabel ?? "docker exec";
4813
4851
  const fail = (err) => {
4814
4852
  if (settled) return;
4815
4853
  settled = true;
@@ -4818,7 +4856,7 @@ function upgradeRequest(conn, path, body) {
4818
4856
  reject(err);
4819
4857
  };
4820
4858
  const timer = setTimeout(
4821
- () => fail(new Error(`docker exec upgrade timed out after ${UPGRADE_TIMEOUT_MS}ms`)),
4859
+ () => fail(new Error(`${timeoutLabel} upgrade timed out after ${UPGRADE_TIMEOUT_MS}ms`)),
4822
4860
  UPGRADE_TIMEOUT_MS
4823
4861
  );
4824
4862
  const onData = (chunk) => {
@@ -4827,7 +4865,8 @@ function upgradeRequest(conn, path, body) {
4827
4865
  const text = head.toString("latin1");
4828
4866
  const end = text.indexOf(HEADER_END);
4829
4867
  if (end === -1) {
4830
- if (head.length > 64 * 1024) fail(new Error("docker exec upgrade: response head too large"));
4868
+ if (head.length > 64 * 1024)
4869
+ fail(new Error(`${timeoutLabel} upgrade: response head too large`));
4831
4870
  return;
4832
4871
  }
4833
4872
  const statusLine = text.slice(0, text.indexOf("\r\n"));
@@ -4848,14 +4887,17 @@ function upgradeRequest(conn, path, body) {
4848
4887
  socket.on("data", onData);
4849
4888
  socket.on("error", fail);
4850
4889
  socket.on("close", onEarlyClose);
4890
+ const extraHeaders = Object.entries(opts.headers ?? {}).map(([name, value]) => `${name}: ${value}\r
4891
+ `).join("");
4892
+ const contentType = opts.contentType ? `Content-Type: ${opts.contentType}\r
4893
+ ` : "";
4851
4894
  socket.write(
4852
4895
  `POST ${path} HTTP/1.1\r
4853
4896
  Host: ${conn.socketPath ? "localhost" : conn.host ?? "localhost"}\r
4854
- Content-Type: application/json\r
4855
- Content-Length: ${Buffer.byteLength(body)}\r
4897
+ ` + contentType + `Content-Length: ${Buffer.byteLength(body)}\r
4856
4898
  Connection: Upgrade\r
4857
- Upgrade: tcp\r
4858
- \r
4899
+ Upgrade: ${protocol}\r
4900
+ ` + extraHeaders + `\r
4859
4901
  ` + body
4860
4902
  );
4861
4903
  });
@@ -4864,7 +4906,8 @@ function startExecStream(conn, execId, opts) {
4864
4906
  return upgradeRequest(
4865
4907
  conn,
4866
4908
  `/exec/${execId}/start`,
4867
- JSON.stringify({ Detach: false, Tty: opts.tty })
4909
+ JSON.stringify({ Detach: false, Tty: opts.tty }),
4910
+ { contentType: "application/json" }
4868
4911
  );
4869
4912
  }
4870
4913
  function startAttachStream(conn, containerId, opts) {
@@ -4874,7 +4917,55 @@ function startAttachStream(conn, containerId, opts) {
4874
4917
  stdout: opts.stdout ? "1" : "0",
4875
4918
  stderr: opts.stderr ? "1" : "0"
4876
4919
  });
4877
- return upgradeRequest(conn, `/containers/${containerId}/attach?${q.toString()}`, "");
4920
+ return upgradeRequest(conn, `/containers/${containerId}/attach?${q.toString()}`, "", {
4921
+ contentType: "application/json"
4922
+ });
4923
+ }
4924
+ function startBuildKitSessionStream(conn, sessionId, sessionName) {
4925
+ if (!sessionId || /[\r\n]/.test(sessionId) || /[\r\n]/.test(sessionName)) {
4926
+ return Promise.reject(new Error("Invalid Docker BuildKit session identity"));
4927
+ }
4928
+ return upgradeRequest(conn, "/session", "", {
4929
+ protocol: "h2c",
4930
+ headers: {
4931
+ "X-Docker-Expose-Session-Uuid": sessionId,
4932
+ "X-Docker-Expose-Session-Name": sessionName
4933
+ },
4934
+ timeoutLabel: "Docker BuildKit session"
4935
+ });
4936
+ }
4937
+ var patchedBuildKitModems = /* @__PURE__ */ new WeakSet();
4938
+ function headerValue(headers, name) {
4939
+ if (!headers || typeof headers !== "object") return "";
4940
+ for (const [key, value] of Object.entries(headers)) {
4941
+ if (key.toLowerCase() !== name.toLowerCase()) continue;
4942
+ if (Array.isArray(value)) return value.length > 0 ? String(value[0]) : "";
4943
+ return value == null ? "" : String(value);
4944
+ }
4945
+ return "";
4946
+ }
4947
+ function installDockerodeBuildKitSessionWorkaround(docker) {
4948
+ if (!docker.modem || typeof docker.modem !== "object") return;
4949
+ const modem = docker.modem;
4950
+ if (typeof modem.dial !== "function" || patchedBuildKitModems.has(modem)) return;
4951
+ const originalDial = modem.dial.bind(modem);
4952
+ modem.dial = (options, callback) => {
4953
+ const upgrade = headerValue(options.headers, "upgrade");
4954
+ if (options.path !== "/session" || options.hijack !== true || upgrade.toLowerCase() !== "h2c" || !callback) {
4955
+ return originalDial(options, callback);
4956
+ }
4957
+ const sessionId = headerValue(options.headers, "x-docker-expose-session-uuid");
4958
+ const sessionName = headerValue(options.headers, "x-docker-expose-session-name");
4959
+ void startBuildKitSessionStream(
4960
+ daemonConnectionFrom(docker),
4961
+ sessionId,
4962
+ sessionName || "openship"
4963
+ ).then(
4964
+ (socket) => callback(null, socket),
4965
+ (error) => callback(error instanceof Error ? error : new Error(String(error)), null)
4966
+ );
4967
+ };
4968
+ patchedBuildKitModems.add(modem);
4878
4969
  }
4879
4970
  function daemonConnectionFrom(docker) {
4880
4971
  const m = docker.modem ?? {};
@@ -5113,7 +5204,10 @@ function awaitFirstByte(upstream, ms) {
5113
5204
  upstream.removeListener("close", onFail);
5114
5205
  resolve(outcome);
5115
5206
  };
5116
- const onData = (chunk) => finish({ kind: "data", chunk });
5207
+ const onData = (chunk) => {
5208
+ upstream.pause();
5209
+ finish({ kind: "data", chunk });
5210
+ };
5117
5211
  const onFail = () => finish({ kind: "closed" });
5118
5212
  const timer = setTimeout(() => finish({ kind: "silent" }), ms);
5119
5213
  upstream.on("data", onData);
@@ -5373,7 +5467,9 @@ function createDockerSshBridge(opts) {
5373
5467
  firstByte.kind === "closed" ? "channel closed before answering" : "channel opened but no data flowed"
5374
5468
  );
5375
5469
  } catch (err) {
5376
- console.warn(`[docker-ssh] bridge client failed (${opts.host ?? "?"}): ${safeErrorMessage(err)}`);
5470
+ console.warn(
5471
+ `[docker-ssh] bridge client failed (${opts.host ?? "?"}): ${safeErrorMessage(err)}`
5472
+ );
5377
5473
  capture.fail(bridgeFailureReason(opts, safeErrorMessage(err)));
5378
5474
  }
5379
5475
  };
@@ -5386,7 +5482,9 @@ function createDockerSshBridge(opts) {
5386
5482
  void bridgeClient(client);
5387
5483
  });
5388
5484
  server.on("error", (err) => {
5389
- console.warn(`[docker-ssh] bridge listener error (${opts.host ?? "?"}): ${safeErrorMessage(err)}`);
5485
+ console.warn(
5486
+ `[docker-ssh] bridge listener error (${opts.host ?? "?"}): ${safeErrorMessage(err)}`
5487
+ );
5390
5488
  });
5391
5489
  return {
5392
5490
  start: () => new Promise((resolve, reject) => {
@@ -5852,38 +5950,28 @@ function extractNetworkInfo(data) {
5852
5950
  break;
5853
5951
  }
5854
5952
  }
5855
- const hostPortByContainerPort = {};
5856
- let hostPort;
5857
5953
  const liveBindings = data.NetworkSettings?.Ports ?? {};
5858
5954
  const configuredBindings = data.HostConfig?.PortBindings ?? {};
5955
+ const publishedPorts = [];
5859
5956
  for (const key of /* @__PURE__ */ new Set([...Object.keys(liveBindings), ...Object.keys(configuredBindings)])) {
5860
5957
  const [containerRaw, protocol = "tcp"] = key.split("/");
5861
- if (protocol.toLowerCase() !== "tcp") continue;
5862
5958
  const containerPort = Number(containerRaw);
5863
- if (!Number.isSafeInteger(containerPort) || containerPort < 1 || containerPort > 65535) {
5864
- continue;
5865
- }
5866
5959
  const live = liveBindings[key];
5867
5960
  const bindings = live && live.some((binding) => binding?.HostPort) ? live : configuredBindings[key];
5868
- const candidates = (bindings ?? []).map((binding, index) => {
5869
- const port = Number(binding?.HostPort);
5870
- if (!Number.isSafeInteger(port) || port < 1 || port > 65535) return null;
5871
- const ip2 = binding?.HostIp?.trim().replace(/^\[|\]$/g, "") ?? "";
5872
- const priority = ip2 === "127.0.0.1" || ip2 === "::1" ? 3 : /^127(?:\.\d{1,3}){3}$/.test(ip2) ? 2 : ip2 && ip2 !== "0.0.0.0" && ip2 !== "::" ? 1 : 0;
5873
- return { index, port, priority };
5874
- }).filter(
5875
- (candidate) => Boolean(candidate)
5876
- ).sort((left, right) => right.priority - left.priority || left.index - right.index);
5877
- const selected = candidates[0]?.port;
5878
- if (selected !== void 0) {
5879
- hostPortByContainerPort[containerPort] = selected;
5880
- hostPort ??= selected;
5961
+ for (const binding of bindings ?? []) {
5962
+ publishedPorts.push({
5963
+ privatePort: containerPort,
5964
+ publicPort: Number(binding?.HostPort),
5965
+ type: protocol,
5966
+ ...binding?.HostIp ? { ip: binding.HostIp } : {}
5967
+ });
5881
5968
  }
5882
5969
  }
5970
+ const { hostPort, hostPortByContainerPort } = dockerPublishedPortInfo(publishedPorts);
5883
5971
  return {
5884
5972
  ip,
5885
5973
  hostPort,
5886
- ...Object.keys(hostPortByContainerPort).length ? { hostPortByContainerPort } : {}
5974
+ ...hostPortByContainerPort ? { hostPortByContainerPort } : {}
5887
5975
  };
5888
5976
  }
5889
5977
  function parseLoadedImageRef(output) {
@@ -5988,6 +6076,9 @@ var DockerRuntime = class _DockerRuntime {
5988
6076
  static async create(opts, systemManager, provisionLock) {
5989
6077
  const runtime = new _DockerRuntime(opts, systemManager, provisionLock);
5990
6078
  runtime._docker = new Dockerode(await runtime.transport.establish());
6079
+ if (process.versions.bun) {
6080
+ installDockerodeBuildKitSessionWorkaround(runtime._docker);
6081
+ }
5991
6082
  return runtime;
5992
6083
  }
5993
6084
  supports(cap) {
@@ -6338,48 +6429,64 @@ var DockerRuntime = class _DockerRuntime {
6338
6429
  config.gitSsh
6339
6430
  );
6340
6431
  }
6341
- const {
6342
- cloneUrl,
6343
- gitEnv: GIT_ENV,
6344
- credFlag: CRED
6345
- } = assembleGitClone({
6432
+ const gitInvocation = assembleGitClone({
6346
6433
  repoUrl: config.repoUrl,
6347
6434
  gitToken: config.gitToken,
6348
6435
  gitCredentialHelperPath: config.gitCredentialHelperPath,
6349
6436
  ssh: sshMaterial,
6350
6437
  ambient: config.gitAmbient
6351
6438
  });
6439
+ const { cloneUrl, credFlag: CRED } = gitInvocation;
6352
6440
  const dir = sq(remoteContextDir);
6353
6441
  const authLabel = config.gitSsh ? "ssh key" : useHelper ? "forwarded credentials" : config.gitAmbient ? `the server's own git credentials (${config.gitAmbient.via})` : "token";
6354
6442
  log.log(`Cloning ${config.repoUrl} on the server \u2192 ${remoteContextDir} (${authLabel})...
6355
6443
  `);
6356
6444
  await executor.exec(`rm -rf ${dir} && mkdir -p ${dir}`);
6357
- const run = async (cmd) => {
6445
+ const run = async (operation, cmd) => {
6358
6446
  const { code } = await executor.streamExec(
6359
6447
  cmd,
6360
6448
  (entry) => log.log(entry.message, parseLogLevel(entry.message))
6361
6449
  );
6362
- if (code !== 0) throw new Error(`git clone on server exited with code ${code}`);
6450
+ if (code !== 0) throw new Error(`git ${operation} on server exited with code ${code}`);
6363
6451
  };
6364
6452
  try {
6365
6453
  if (config.commitSha) {
6366
- try {
6367
- await run(
6368
- `${GIT_ENV} git ${CRED} clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${dir} && cd ${dir} && git ${CRED} -c advice.detachedHead=false checkout ${sq(config.commitSha)}`
6369
- );
6370
- } catch {
6454
+ await run(
6455
+ "clone",
6456
+ gitShellCommand(
6457
+ gitInvocation,
6458
+ `clone --progress --depth 50 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${dir}`
6459
+ )
6460
+ );
6461
+ const commitPresent = await executor.exec(`cd ${dir} && git ${CRED} cat-file -e ${sq(`${config.commitSha}^{commit}`)}`).then(
6462
+ () => true,
6463
+ () => false
6464
+ );
6465
+ if (!commitPresent) {
6371
6466
  log.log(
6372
6467
  `Commit ${config.commitSha} not in the shallow clone; unshallowing and retrying.
6373
6468
  `,
6374
6469
  "warn"
6375
6470
  );
6376
6471
  await run(
6377
- `cd ${dir} && ${GIT_ENV} git ${CRED} fetch --progress --unshallow && git ${CRED} -c advice.detachedHead=false checkout ${sq(config.commitSha)}`
6472
+ "fetch",
6473
+ `cd ${dir} && ${gitShellCommand(gitInvocation, "fetch --progress --unshallow")}`
6378
6474
  );
6379
6475
  }
6476
+ await run(
6477
+ "checkout",
6478
+ `cd ${dir} && ${gitShellCommand(
6479
+ gitInvocation,
6480
+ `-c advice.detachedHead=false checkout ${sq(config.commitSha)}`
6481
+ )}`
6482
+ );
6380
6483
  } else {
6381
6484
  await run(
6382
- `${GIT_ENV} git ${CRED} clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${dir}`
6485
+ "clone",
6486
+ gitShellCommand(
6487
+ gitInvocation,
6488
+ `clone --progress --depth 1 --branch ${sq(config.branch)} ${sq(cloneUrl)} ${dir}`
6489
+ )
6383
6490
  );
6384
6491
  }
6385
6492
  await executor.exec(`rm -rf ${sq(`${remoteContextDir}/.git`)}`).catch(() => {
@@ -8725,12 +8832,6 @@ var DockerRuntime = class _DockerRuntime {
8725
8832
  const log = onLog ?? (() => {
8726
8833
  });
8727
8834
  const containerName = `openship-${config.slug}-${config.serviceName}`;
8728
- try {
8729
- const existing = this.docker.getContainer(containerName);
8730
- await gracefulStopForGrace(existing);
8731
- await existing.remove({ force: true });
8732
- } catch {
8733
- }
8734
8835
  const serviceEnv = splitRuntimeEnv(config.environment);
8735
8836
  if (serviceEnv.dropped.length > 0) {
8736
8837
  log({
@@ -8758,6 +8859,31 @@ var DockerRuntime = class _DockerRuntime {
8758
8859
  const restartPolicy = resolveRestartPolicy(config.restart);
8759
8860
  const healthcheck = toDockerHealthcheck(config.advanced?.healthcheck);
8760
8861
  const stopConfig = toStopConfig(config.advanced);
8862
+ if (!config.imageAlreadyPrepared) {
8863
+ try {
8864
+ log({
8865
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8866
+ message: `Pulling image ${config.image}...
8867
+ `,
8868
+ level: "info"
8869
+ });
8870
+ await this.pullImage(config.image, { force: config.forcePull });
8871
+ } catch (err) {
8872
+ log({
8873
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8874
+ message: `Failed to pull ${config.image}: ${err}
8875
+ `,
8876
+ level: "error"
8877
+ });
8878
+ throw err;
8879
+ }
8880
+ }
8881
+ try {
8882
+ const existing = this.docker.getContainer(containerName);
8883
+ await gracefulStopForGrace(existing);
8884
+ await existing.remove({ force: true });
8885
+ } catch {
8886
+ }
8761
8887
  log({
8762
8888
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8763
8889
  message: `Creating service container ${containerName} from ${config.image}...
@@ -8787,25 +8913,6 @@ var DockerRuntime = class _DockerRuntime {
8787
8913
  level: "info"
8788
8914
  });
8789
8915
  }
8790
- if (!config.image.startsWith("openship/")) {
8791
- try {
8792
- log({
8793
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8794
- message: `Pulling image ${config.image}...
8795
- `,
8796
- level: "info"
8797
- });
8798
- await this.pullImage(config.image, { force: config.forcePull });
8799
- } catch (err) {
8800
- log({
8801
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8802
- message: `Failed to pull ${config.image}: ${err}
8803
- `,
8804
- level: "error"
8805
- });
8806
- throw err;
8807
- }
8808
- }
8809
8916
  const container = await this.docker.createContainer({
8810
8917
  name: containerName,
8811
8918
  Image: config.image,
@@ -10,28 +10,28 @@ import {
10
10
  readComposeEnvFile,
11
11
  resolveLocalEnvironmentSync,
12
12
  thisHost
13
- } from "./chunk-5UXHT6ED.js";
13
+ } from "./chunk-SPR7N2EK.js";
14
14
  import {
15
15
  OS_DIR
16
16
  } from "./chunk-ELGRMZOW.js";
17
17
  import {
18
18
  systemCatalog
19
- } from "./chunk-NIYD7MD2.js";
19
+ } from "./chunk-7MKZP4TW.js";
20
20
  import {
21
21
  EDGE_CONTAINER_MOUNTS,
22
22
  EDGE_HOST_STATE_DIR
23
- } from "./chunk-7PEXM2UI.js";
23
+ } from "./chunk-BOD2IQ3P.js";
24
24
  import {
25
25
  DEFAULT_DOCKER_SOCKET_PATH,
26
26
  resolveLocalDockerSocketPath
27
- } from "./chunk-3MP6ACKM.js";
27
+ } from "./chunk-TPGBT5UR.js";
28
28
  import {
29
29
  LocalExecutor
30
- } from "./chunk-T6DVUX4F.js";
30
+ } from "./chunk-J4SYG2LC.js";
31
31
  import {
32
32
  invalidateEdgeContainer,
33
33
  sanitizeEdgeVhosts
34
- } from "./chunk-X53JXRS4.js";
34
+ } from "./chunk-YGA52DHX.js";
35
35
  import {
36
36
  DEFAULT_IMAGE_REGISTRY,
37
37
  HOST_CHANNEL_BLOCKED,
@@ -51,7 +51,7 @@ import {
51
51
  hostFirewallRule,
52
52
  hostFirewallRuleRemoval,
53
53
  wrapText
54
- } from "./chunk-WEZFTL4V.js";
54
+ } from "./chunk-IVRTIKNZ.js";
55
55
 
56
56
  // src/lib/host-channel-preflight.ts
57
57
  import { spawnSync as spawnSync4 } from "child_process";
@@ -1627,7 +1627,7 @@ function operatorEnvPassthrough(managed, prev) {
1627
1627
  }
1628
1628
  var PRESERVED_ENV_HEADER = "# Preserved from your existing .env \u2014 set by you, not by `openship up` (#485).";
1629
1629
  function resolveImageVersion(opts) {
1630
- return opts.version?.trim() || process.env.OPENSHIP_VERSION?.trim() || (true ? "0.6.9" : "latest");
1630
+ return opts.version?.trim() || process.env.OPENSHIP_VERSION?.trim() || (true ? "0.7.0" : "latest");
1631
1631
  }
1632
1632
  function managedEnvLines(opts, host, cfg, prev) {
1633
1633
  const lines = [