@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.
- package/dist/lib/testing/integration.js +16 -8
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/lib/testing/runtime-utils.js +16 -8
- package/dist/lib/testing/runtime-utils.js.map +2 -2
- package/package.json +5 -5
- package/src/lib/testing/__tests__/runtime-utils.test.ts +45 -1
- package/src/lib/testing/integration.ts +17 -8
- package/src/lib/testing/runtime-utils.ts +17 -8
|
@@ -14,10 +14,17 @@ function assertNode24Runtime(options) {
|
|
|
14
14
|
);
|
|
15
15
|
}
|
|
16
16
|
async function getFreePort() {
|
|
17
|
-
|
|
17
|
+
const tryListen = (host) => new Promise((resolve, reject) => {
|
|
18
18
|
const server = createServer();
|
|
19
|
-
server.on("error",
|
|
20
|
-
|
|
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
|
|
56
|
-
if (
|
|
63
|
+
const wildcardIpv6Availability = await canBind("::");
|
|
64
|
+
if (wildcardIpv6Availability === false) {
|
|
57
65
|
return false;
|
|
58
66
|
}
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
67
|
+
const ipv4Availability = await canBind("127.0.0.1");
|
|
68
|
+
if (ipv4Availability === false) {
|
|
61
69
|
return false;
|
|
62
70
|
}
|
|
63
|
-
return
|
|
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
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,aAAa;AAOf,SAAS,oBAAoB,SAA0C;AAC5E,QAAM,QAAQ,OAAO,SAAS,QAAQ,SAAS,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE;AAC5E,MAAI,SAAS,IAAI;AACf;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,MACE,mCAAmC,QAAQ,OAAO;AAAA,MAClD,wBAAwB,QAAQ,SAAS,IAAI;AAAA,MAC7C,kIAAkI,QAAQ,YAAY;AAAA,IACxJ,EAAE,KAAK,GAAG;AAAA,EACZ;AACF;AAEA,eAAsB,cAA+B;AACnD,
|
|
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.
|
|
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.
|
|
63
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
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.
|
|
73
|
+
"@open-mercato/shared": "0.6.5-develop.4734.1.dd8285dd2e"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
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 {
|
|
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
|
-
|
|
1117
|
+
const tryListen = (host: string): Promise<number | null> => new Promise((resolve, reject) => {
|
|
1118
1118
|
const server = createServer()
|
|
1119
|
-
server.on('error',
|
|
1120
|
-
|
|
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
|
|
1158
|
-
if (
|
|
1166
|
+
const wildcardIpv6Availability = await canBind('::')
|
|
1167
|
+
if (wildcardIpv6Availability === false) {
|
|
1159
1168
|
return false
|
|
1160
1169
|
}
|
|
1161
1170
|
|
|
1162
|
-
const
|
|
1163
|
-
if (
|
|
1171
|
+
const ipv4Availability = await canBind('127.0.0.1')
|
|
1172
|
+
if (ipv4Availability === false) {
|
|
1164
1173
|
return false
|
|
1165
1174
|
}
|
|
1166
1175
|
|
|
1167
|
-
return
|
|
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
|
-
|
|
25
|
+
const tryListen = (host: string): Promise<number | null> => new Promise((resolve, reject) => {
|
|
26
26
|
const server = createServer()
|
|
27
|
-
server.on('error',
|
|
28
|
-
|
|
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
|
|
66
|
-
if (
|
|
74
|
+
const wildcardIpv6Availability = await canBind('::')
|
|
75
|
+
if (wildcardIpv6Availability === false) {
|
|
67
76
|
return false
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
79
|
+
const ipv4Availability = await canBind('127.0.0.1')
|
|
80
|
+
if (ipv4Availability === false) {
|
|
72
81
|
return false
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
return
|
|
84
|
+
return wildcardIpv6Availability === true || ipv4Availability === true
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
export async function getPreferredPort(preferredPort: number, logPrefix: string): Promise<number> {
|