@open-mercato/cli 0.6.5-develop.4718.1.56d834bb34 → 0.6.5-develop.4734.1.dd8285dd2e

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.
@@ -14,10 +14,17 @@ function assertNode24Runtime(options) {
14
14
  );
15
15
  }
16
16
  async function getFreePort() {
17
- return new Promise((resolve, reject) => {
17
+ const tryListen = (host) => new Promise((resolve, reject) => {
18
18
  const server = createServer();
19
- server.on("error", reject);
20
- server.listen(0, "127.0.0.1", () => {
19
+ server.on("error", (error) => {
20
+ const errorCode = error.code;
21
+ if (errorCode === "EAFNOSUPPORT") {
22
+ resolve(null);
23
+ return;
24
+ }
25
+ reject(error);
26
+ });
27
+ server.listen(0, host, () => {
21
28
  const address = server.address();
22
29
  if (!address || typeof address === "string") {
23
30
  server.close();
@@ -34,6 +41,7 @@ async function getFreePort() {
34
41
  });
35
42
  });
36
43
  });
44
+ return await tryListen("::") ?? await tryListen("127.0.0.1") ?? Promise.reject(new Error("Unable to allocate free port"));
37
45
  }
38
46
  async function isPortAvailable(port) {
39
47
  const canBind = (host) => new Promise((resolve) => {
@@ -52,15 +60,15 @@ async function isPortAvailable(port) {
52
60
  });
53
61
  });
54
62
  });
55
- const ipv4Availability = await canBind("127.0.0.1");
56
- if (ipv4Availability === false) {
63
+ const wildcardIpv6Availability = await canBind("::");
64
+ if (wildcardIpv6Availability === false) {
57
65
  return false;
58
66
  }
59
- const ipv6Availability = await canBind("::1");
60
- if (ipv6Availability === false) {
67
+ const ipv4Availability = await canBind("127.0.0.1");
68
+ if (ipv4Availability === false) {
61
69
  return false;
62
70
  }
63
- return ipv4Availability === true || ipv6Availability === true;
71
+ return wildcardIpv6Availability === true || ipv4Availability === true;
64
72
  }
65
73
  async function getPreferredPort(preferredPort, logPrefix) {
66
74
  if (await isPortAvailable(preferredPort)) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/testing/runtime-utils.ts"],
4
- "sourcesContent": ["import { createServer } from 'node:net'\nimport { spawn } from 'node:child_process'\n\ntype Node24RuntimeGuardOptions = {\n context: string\n retryCommand: string\n}\n\nexport function assertNode24Runtime(options: Node24RuntimeGuardOptions): void {\n const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10)\n if (major >= 24) {\n return\n }\n\n throw new Error(\n [\n `Unsupported Node.js runtime for ${options.context}.`,\n `Cause: Detected Node ${process.versions.node}, but this repository requires Node 24.x.`,\n `What to do: switch your shell to Node 24 (for example \\`nvm use 24\\`), reinstall dependencies (\\`yarn install\\`), then retry \\`${options.retryCommand}\\`.`,\n ].join(' '),\n )\n}\n\nexport async function getFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer()\n server.on('error', reject)\n server.listen(0, '127.0.0.1', () => {\n const address = server.address()\n if (!address || typeof address === 'string') {\n server.close()\n reject(new Error('Unable to allocate free port'))\n return\n }\n const port = address.port\n server.close((closeError) => {\n if (closeError) {\n reject(closeError)\n return\n }\n resolve(port)\n })\n })\n })\n}\n\nexport async function isPortAvailable(port: number): Promise<boolean> {\n const canBind = (host: string): Promise<boolean | null> => new Promise((resolve) => {\n const server = createServer()\n server.once('error', (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n if (errorCode === 'EAFNOSUPPORT') {\n resolve(null)\n return\n }\n resolve(false)\n })\n server.listen(port, host, () => {\n server.close(() => {\n resolve(true)\n })\n })\n })\n\n const ipv4Availability = await canBind('127.0.0.1')\n if (ipv4Availability === false) {\n return false\n }\n\n const ipv6Availability = await canBind('::1')\n if (ipv6Availability === false) {\n return false\n }\n\n return ipv4Availability === true || ipv6Availability === true\n}\n\nexport async function getPreferredPort(preferredPort: number, logPrefix: string): Promise<number> {\n if (await isPortAvailable(preferredPort)) {\n return preferredPort\n }\n\n const fallbackPort = await getFreePort()\n console.log(`[${logPrefix}] Port ${preferredPort} is busy, using fallback port ${fallbackPort}.`)\n return fallbackPort\n}\n\nexport async function isEndpointResponsive(url: string, timeoutMs = 1500): Promise<boolean> {\n try {\n const response = await fetch(url, {\n method: 'GET',\n redirect: 'manual',\n signal: AbortSignal.timeout(timeoutMs),\n })\n return response.status > 0\n } catch {\n return false\n }\n}\n\nexport function redactPostgresUrl(url: string): string {\n const trimmed = url.trim()\n if (!trimmed) {\n return trimmed\n }\n\n try {\n const parsed = new URL(trimmed)\n if (parsed.password) {\n parsed.password = '***'\n }\n if (!parsed.username && parsed.password) {\n parsed.username = '***'\n }\n return parsed.toString()\n } catch {\n return trimmed.replace(/(postgres(?:ql)?:\\/\\/[^:/?#\\s]+:)[^@/\\s]+@/i, '$1***@')\n }\n}\n\nexport type DockerHostConfig = {\n dockerHost: string\n socketOverride: string\n}\n\nexport function runCommandAndCapture(command: string, args: string[], options?: { cwd?: string }): Promise<{ code: number | null; stdout: string; stderr: string }> {\n return new Promise((resolve) => {\n const proc = spawn(command, args, {\n cwd: options?.cwd ?? process.cwd(),\n env: process.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n let stdout = ''\n let stderr = ''\n proc.stdout?.on('data', (chunk: Buffer | string) => {\n stdout += chunk.toString()\n })\n proc.stderr?.on('data', (chunk: Buffer | string) => {\n stderr += chunk.toString()\n })\n proc.on('error', () => {\n resolve({ code: -1, stdout, stderr })\n })\n proc.on('exit', (code) => {\n resolve({ code, stdout, stderr })\n })\n })\n}\n\n/**\n * Resolve Docker host configuration from the active Docker context.\n *\n * Testcontainers only checks hardcoded socket paths and doesn't respect Docker CLI contexts.\n * This function queries the active Docker context to get the correct socket path for\n * non-standard Docker setups like Colima.\n *\n * @returns Docker host configuration or null if auto-detection fails/not needed\n */\nexport async function resolveDockerHostFromContext(logPrefix: string): Promise<DockerHostConfig | null> {\n // Respect explicit user configuration\n if (process.env.DOCKER_HOST) {\n return null\n }\n\n const result = await runCommandAndCapture('docker', ['context', 'inspect', '--format', '{{.Endpoints.docker.Host}}'])\n if (result.code !== 0) {\n return null\n }\n\n const dockerHost = result.stdout.trim()\n if (!dockerHost) {\n return null\n }\n\n // Standard socket path - testcontainers handles this natively\n if (dockerHost === 'unix:///var/run/docker.sock') {\n return null\n }\n\n // For non-standard Docker setups (Colima, Podman, etc.), the socket override\n // tells testcontainers what path containers should use to reach the Docker daemon.\n // This is typically /var/run/docker.sock inside the VM, regardless of the host path.\n const socketOverride = '/var/run/docker.sock'\n\n console.log(`[${logPrefix}] Auto-detected Docker context: ${dockerHost}`)\n\n return {\n dockerHost,\n socketOverride,\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { createServer } from 'node:net'\nimport { spawn } from 'node:child_process'\n\ntype Node24RuntimeGuardOptions = {\n context: string\n retryCommand: string\n}\n\nexport function assertNode24Runtime(options: Node24RuntimeGuardOptions): void {\n const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10)\n if (major >= 24) {\n return\n }\n\n throw new Error(\n [\n `Unsupported Node.js runtime for ${options.context}.`,\n `Cause: Detected Node ${process.versions.node}, but this repository requires Node 24.x.`,\n `What to do: switch your shell to Node 24 (for example \\`nvm use 24\\`), reinstall dependencies (\\`yarn install\\`), then retry \\`${options.retryCommand}\\`.`,\n ].join(' '),\n )\n}\n\nexport async function getFreePort(): Promise<number> {\n const tryListen = (host: string): Promise<number | null> => new Promise((resolve, reject) => {\n const server = createServer()\n server.on('error', (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n if (errorCode === 'EAFNOSUPPORT') {\n resolve(null)\n return\n }\n reject(error)\n })\n server.listen(0, host, () => {\n const address = server.address()\n if (!address || typeof address === 'string') {\n server.close()\n reject(new Error('Unable to allocate free port'))\n return\n }\n const port = address.port\n server.close((closeError) => {\n if (closeError) {\n reject(closeError)\n return\n }\n resolve(port)\n })\n })\n })\n\n return (await tryListen('::')) ?? await tryListen('127.0.0.1') ?? Promise.reject(new Error('Unable to allocate free port'))\n}\n\nexport async function isPortAvailable(port: number): Promise<boolean> {\n const canBind = (host: string): Promise<boolean | null> => new Promise((resolve) => {\n const server = createServer()\n server.once('error', (error) => {\n const errorCode = (error as NodeJS.ErrnoException).code\n if (errorCode === 'EAFNOSUPPORT') {\n resolve(null)\n return\n }\n resolve(false)\n })\n server.listen(port, host, () => {\n server.close(() => {\n resolve(true)\n })\n })\n })\n\n const wildcardIpv6Availability = await canBind('::')\n if (wildcardIpv6Availability === false) {\n return false\n }\n\n const ipv4Availability = await canBind('127.0.0.1')\n if (ipv4Availability === false) {\n return false\n }\n\n return wildcardIpv6Availability === true || ipv4Availability === true\n}\n\nexport async function getPreferredPort(preferredPort: number, logPrefix: string): Promise<number> {\n if (await isPortAvailable(preferredPort)) {\n return preferredPort\n }\n\n const fallbackPort = await getFreePort()\n console.log(`[${logPrefix}] Port ${preferredPort} is busy, using fallback port ${fallbackPort}.`)\n return fallbackPort\n}\n\nexport async function isEndpointResponsive(url: string, timeoutMs = 1500): Promise<boolean> {\n try {\n const response = await fetch(url, {\n method: 'GET',\n redirect: 'manual',\n signal: AbortSignal.timeout(timeoutMs),\n })\n return response.status > 0\n } catch {\n return false\n }\n}\n\nexport function redactPostgresUrl(url: string): string {\n const trimmed = url.trim()\n if (!trimmed) {\n return trimmed\n }\n\n try {\n const parsed = new URL(trimmed)\n if (parsed.password) {\n parsed.password = '***'\n }\n if (!parsed.username && parsed.password) {\n parsed.username = '***'\n }\n return parsed.toString()\n } catch {\n return trimmed.replace(/(postgres(?:ql)?:\\/\\/[^:/?#\\s]+:)[^@/\\s]+@/i, '$1***@')\n }\n}\n\nexport type DockerHostConfig = {\n dockerHost: string\n socketOverride: string\n}\n\nexport function runCommandAndCapture(command: string, args: string[], options?: { cwd?: string }): Promise<{ code: number | null; stdout: string; stderr: string }> {\n return new Promise((resolve) => {\n const proc = spawn(command, args, {\n cwd: options?.cwd ?? process.cwd(),\n env: process.env,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n let stdout = ''\n let stderr = ''\n proc.stdout?.on('data', (chunk: Buffer | string) => {\n stdout += chunk.toString()\n })\n proc.stderr?.on('data', (chunk: Buffer | string) => {\n stderr += chunk.toString()\n })\n proc.on('error', () => {\n resolve({ code: -1, stdout, stderr })\n })\n proc.on('exit', (code) => {\n resolve({ code, stdout, stderr })\n })\n })\n}\n\n/**\n * Resolve Docker host configuration from the active Docker context.\n *\n * Testcontainers only checks hardcoded socket paths and doesn't respect Docker CLI contexts.\n * This function queries the active Docker context to get the correct socket path for\n * non-standard Docker setups like Colima.\n *\n * @returns Docker host configuration or null if auto-detection fails/not needed\n */\nexport async function resolveDockerHostFromContext(logPrefix: string): Promise<DockerHostConfig | null> {\n // Respect explicit user configuration\n if (process.env.DOCKER_HOST) {\n return null\n }\n\n const result = await runCommandAndCapture('docker', ['context', 'inspect', '--format', '{{.Endpoints.docker.Host}}'])\n if (result.code !== 0) {\n return null\n }\n\n const dockerHost = result.stdout.trim()\n if (!dockerHost) {\n return null\n }\n\n // Standard socket path - testcontainers handles this natively\n if (dockerHost === 'unix:///var/run/docker.sock') {\n return null\n }\n\n // For non-standard Docker setups (Colima, Podman, etc.), the socket override\n // tells testcontainers what path containers should use to reach the Docker daemon.\n // This is typically /var/run/docker.sock inside the VM, regardless of the host path.\n const socketOverride = '/var/run/docker.sock'\n\n console.log(`[${logPrefix}] Auto-detected Docker context: ${dockerHost}`)\n\n return {\n dockerHost,\n socketOverride,\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/cli",
3
- "version": "0.6.5-develop.4718.1.56d834bb34",
3
+ "version": "0.6.5-develop.4734.1.dd8285dd2e",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -59,8 +59,8 @@
59
59
  "@mikro-orm/decorators": "^7.1.3",
60
60
  "@mikro-orm/migrations": "^7.1.3",
61
61
  "@mikro-orm/postgresql": "^7.1.3",
62
- "@open-mercato/queue": "0.6.5-develop.4718.1.56d834bb34",
63
- "@open-mercato/shared": "0.6.5-develop.4718.1.56d834bb34",
62
+ "@open-mercato/queue": "0.6.5-develop.4734.1.dd8285dd2e",
63
+ "@open-mercato/shared": "0.6.5-develop.4734.1.dd8285dd2e",
64
64
  "cross-spawn": "^7.0.6",
65
65
  "pg": "8.21.0",
66
66
  "semver": "^7.8.1",
@@ -70,10 +70,10 @@
70
70
  "typescript": "^6.0.3"
71
71
  },
72
72
  "peerDependencies": {
73
- "@open-mercato/shared": "0.6.5-develop.4718.1.56d834bb34"
73
+ "@open-mercato/shared": "0.6.5-develop.4734.1.dd8285dd2e"
74
74
  },
75
75
  "devDependencies": {
76
- "@open-mercato/shared": "0.6.5-develop.4718.1.56d834bb34",
76
+ "@open-mercato/shared": "0.6.5-develop.4734.1.dd8285dd2e",
77
77
  "@types/jest": "^30.0.0",
78
78
  "jest": "^30.4.2",
79
79
  "ts-jest": "^29.4.11"
@@ -1,4 +1,48 @@
1
- import { redactPostgresUrl } from '../runtime-utils'
1
+ import { createServer } from 'node:net'
2
+ import { getFreePort, isPortAvailable, redactPostgresUrl } from '../runtime-utils'
3
+
4
+ async function listen(host: string, port: number): Promise<{ close: () => Promise<void> }> {
5
+ return new Promise((resolve, reject) => {
6
+ const server = createServer()
7
+ server.once('error', reject)
8
+ server.listen(port, host, () => {
9
+ resolve({
10
+ close: () => new Promise((closeResolve, closeReject) => {
11
+ server.close((error) => {
12
+ if (error) {
13
+ closeReject(error)
14
+ return
15
+ }
16
+ closeResolve()
17
+ })
18
+ }),
19
+ })
20
+ })
21
+ })
22
+ }
23
+
24
+ describe('integration port helpers', () => {
25
+ it('treats wildcard IPv6 listeners as occupying the app port', async () => {
26
+ const port = await getFreePort()
27
+ let listener: { close: () => Promise<void> } | null = null
28
+
29
+ try {
30
+ listener = await listen('::', port)
31
+ } catch (error) {
32
+ const errorCode = (error as NodeJS.ErrnoException).code
33
+ if (errorCode === 'EAFNOSUPPORT') {
34
+ return
35
+ }
36
+ throw error
37
+ }
38
+
39
+ try {
40
+ await expect(isPortAvailable(port)).resolves.toBe(false)
41
+ } finally {
42
+ await listener.close()
43
+ }
44
+ })
45
+ })
2
46
 
3
47
  describe('redactPostgresUrl', () => {
4
48
  it('redacts password in a postgres url', () => {
@@ -1114,10 +1114,17 @@ export async function shouldRebuildBuildArtifacts(
1114
1114
  }
1115
1115
 
1116
1116
  async function getFreePort(): Promise<number> {
1117
- return new Promise((resolve, reject) => {
1117
+ const tryListen = (host: string): Promise<number | null> => new Promise((resolve, reject) => {
1118
1118
  const server = createServer()
1119
- server.on('error', reject)
1120
- server.listen(0, '127.0.0.1', () => {
1119
+ server.on('error', (error) => {
1120
+ const errorCode = (error as NodeJS.ErrnoException).code
1121
+ if (errorCode === 'EAFNOSUPPORT') {
1122
+ resolve(null)
1123
+ return
1124
+ }
1125
+ reject(error)
1126
+ })
1127
+ server.listen(0, host, () => {
1121
1128
  const address = server.address()
1122
1129
  if (!address || typeof address === 'string') {
1123
1130
  server.close()
@@ -1134,6 +1141,8 @@ async function getFreePort(): Promise<number> {
1134
1141
  })
1135
1142
  })
1136
1143
  })
1144
+
1145
+ return (await tryListen('::')) ?? await tryListen('127.0.0.1') ?? Promise.reject(new Error('Unable to allocate free port'))
1137
1146
  }
1138
1147
 
1139
1148
  async function isPortAvailable(port: number): Promise<boolean> {
@@ -1154,17 +1163,17 @@ async function isPortAvailable(port: number): Promise<boolean> {
1154
1163
  })
1155
1164
  })
1156
1165
 
1157
- const ipv4Availability = await canBind('127.0.0.1')
1158
- if (ipv4Availability === false) {
1166
+ const wildcardIpv6Availability = await canBind('::')
1167
+ if (wildcardIpv6Availability === false) {
1159
1168
  return false
1160
1169
  }
1161
1170
 
1162
- const ipv6Availability = await canBind('::1')
1163
- if (ipv6Availability === false) {
1171
+ const ipv4Availability = await canBind('127.0.0.1')
1172
+ if (ipv4Availability === false) {
1164
1173
  return false
1165
1174
  }
1166
1175
 
1167
- return ipv4Availability === true || ipv6Availability === true
1176
+ return wildcardIpv6Availability === true || ipv4Availability === true
1168
1177
  }
1169
1178
 
1170
1179
  async function getPreferredPort(preferredPort: number): Promise<number> {
@@ -22,10 +22,17 @@ export function assertNode24Runtime(options: Node24RuntimeGuardOptions): void {
22
22
  }
23
23
 
24
24
  export async function getFreePort(): Promise<number> {
25
- return new Promise((resolve, reject) => {
25
+ const tryListen = (host: string): Promise<number | null> => new Promise((resolve, reject) => {
26
26
  const server = createServer()
27
- server.on('error', reject)
28
- server.listen(0, '127.0.0.1', () => {
27
+ server.on('error', (error) => {
28
+ const errorCode = (error as NodeJS.ErrnoException).code
29
+ if (errorCode === 'EAFNOSUPPORT') {
30
+ resolve(null)
31
+ return
32
+ }
33
+ reject(error)
34
+ })
35
+ server.listen(0, host, () => {
29
36
  const address = server.address()
30
37
  if (!address || typeof address === 'string') {
31
38
  server.close()
@@ -42,6 +49,8 @@ export async function getFreePort(): Promise<number> {
42
49
  })
43
50
  })
44
51
  })
52
+
53
+ return (await tryListen('::')) ?? await tryListen('127.0.0.1') ?? Promise.reject(new Error('Unable to allocate free port'))
45
54
  }
46
55
 
47
56
  export async function isPortAvailable(port: number): Promise<boolean> {
@@ -62,17 +71,17 @@ export async function isPortAvailable(port: number): Promise<boolean> {
62
71
  })
63
72
  })
64
73
 
65
- const ipv4Availability = await canBind('127.0.0.1')
66
- if (ipv4Availability === false) {
74
+ const wildcardIpv6Availability = await canBind('::')
75
+ if (wildcardIpv6Availability === false) {
67
76
  return false
68
77
  }
69
78
 
70
- const ipv6Availability = await canBind('::1')
71
- if (ipv6Availability === false) {
79
+ const ipv4Availability = await canBind('127.0.0.1')
80
+ if (ipv4Availability === false) {
72
81
  return false
73
82
  }
74
83
 
75
- return ipv4Availability === true || ipv6Availability === true
84
+ return wildcardIpv6Availability === true || ipv4Availability === true
76
85
  }
77
86
 
78
87
  export async function getPreferredPort(preferredPort: number, logPrefix: string): Promise<number> {