feat(sdk): use config file as single source of truth, remove env var fallbacks (#19409)
## Summary - **Config as source of truth**: `~/.twenty/config.json` is now the single source of truth for SDK authentication — env var fallbacks have been removed from the config resolution chain. - **Test instance support**: `twenty server start --test` spins up a dedicated Docker instance on port 2021 with its own config (`config.test.json`), so integration tests don't interfere with the dev environment. - **API key auth for marketplace**: Removed `UserAuthGuard` from `MarketplaceResolver` so API key tokens (workspace-scoped) can call `installMarketplaceApp`. - **CI for example apps**: Added monorepo CI workflows for `hello-world` and `postcard` example apps to catch regressions. - **Simplified CI**: All `ci-create-app-e2e` and example app workflows now use a shared `spawn-twenty-app-dev-test` action (Docker-based) instead of building the server from source. Consolidated auth env vars to `TWENTY_API_URL` + `TWENTY_API_KEY`. - **Template publishing fix**: `create-twenty-app` template now correctly preserves `.github/` and `.gitignore` through npm publish (stored without leading dot, renamed after copy). ## Test plan - [x] CI SDK (lint, typecheck, unit, integration, e2e) — all green - [x] CI Example App Hello World — green - [x] CI Example App Postcard — green - [x] CI Create App E2E minimal — green - [x] CI Front, CI Server, CI Shared — green
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import axios from 'axios';
|
||||
import { SERVER_URL } from '@/cli/__tests__/constants/server-url.constant';
|
||||
import { getServerUrl } from '@/cli/__tests__/constants/server-url.constant';
|
||||
|
||||
describe('Twenty Server Health Check (E2E)', () => {
|
||||
const HEALTH_ENDPOINT = `${SERVER_URL}/healthz`;
|
||||
let healthEndpoint: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const serverUrl = await getServerUrl();
|
||||
|
||||
healthEndpoint = `${serverUrl}/healthz`;
|
||||
});
|
||||
|
||||
it('should return 200 for health', async () => {
|
||||
const response = await axios.get(HEALTH_ENDPOINT);
|
||||
const response = await axios.get(healthEndpoint);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
export const SERVER_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export const getServerUrl = async (): Promise<string> => {
|
||||
const config = await new ConfigService().getConfig();
|
||||
|
||||
return config.apiUrl;
|
||||
};
|
||||
|
||||
@@ -5,20 +5,44 @@ import { beforeAll } from 'vitest';
|
||||
import { ensureDir } from '@/cli/utilities/file/fs-utils';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
|
||||
const testConfigPath = getConfigPath();
|
||||
const testConfigPath = getConfigPath(true);
|
||||
|
||||
beforeAll(async () => {
|
||||
const apiUrl = process.env.TWENTY_API_URL;
|
||||
const token = process.env.TWENTY_API_KEY;
|
||||
|
||||
if (!apiUrl || !token) {
|
||||
throw new Error(
|
||||
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
|
||||
'Run: twenty server start --test\n' +
|
||||
'Or set them in vitest env config.',
|
||||
);
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiUrl}/healthz`).catch(() => null);
|
||||
|
||||
if (!response?.ok) {
|
||||
throw new Error(
|
||||
`Twenty server not reachable at ${apiUrl}. ` +
|
||||
'Run: twenty server start --test',
|
||||
);
|
||||
}
|
||||
|
||||
await ensureDir(path.dirname(testConfigPath));
|
||||
|
||||
const configFile = {
|
||||
remotes: {
|
||||
local: {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
apiKey: process.env.TWENTY_API_KEY,
|
||||
await writeFile(
|
||||
testConfigPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
remotes: {
|
||||
local: { apiUrl, apiKey: token },
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
},
|
||||
},
|
||||
defaultRemote: 'local',
|
||||
};
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
await writeFile(testConfigPath, JSON.stringify(configFile, null, 2));
|
||||
process.env.TWENTY_APP_ACCESS_TOKEN ??= token;
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { DevModeOrchestrator } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator';
|
||||
import { OrchestratorState } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
|
||||
@@ -29,9 +30,11 @@ export class AppDevCommand {
|
||||
|
||||
await checkSdkVersionCompatibility(appPath);
|
||||
|
||||
const config = await new ConfigService().getConfig();
|
||||
|
||||
const orchestratorState = new OrchestratorState({
|
||||
appPath,
|
||||
frontendUrl: process.env.FRONTEND_URL,
|
||||
frontendUrl: config.apiUrl,
|
||||
});
|
||||
|
||||
if (!options.headless) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authLogin } from '@/cli/operations/login';
|
||||
import { authLoginOAuth } from '@/cli/operations/login-oauth';
|
||||
import { ApiService } from '@/cli/utilities/api/api-service';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
import { detectLocalServer } from '@/cli/utilities/server/detect-local-server';
|
||||
import chalk from 'chalk';
|
||||
import type { Command } from 'commander';
|
||||
@@ -72,14 +73,19 @@ export const registerRemoteCommands = (program: Command): void => {
|
||||
.option('--api-key <apiKey>', 'API key for non-interactive auth')
|
||||
.option('--api-url <apiUrl>', 'Server URL')
|
||||
.option('--local', 'Connect to a local Twenty server (auto-detect)')
|
||||
.option('--test', 'Write to config.test.json (for integration tests)')
|
||||
.action(
|
||||
async (options: {
|
||||
as?: string;
|
||||
apiKey?: string;
|
||||
apiUrl?: string;
|
||||
local?: boolean;
|
||||
test?: boolean;
|
||||
}) => {
|
||||
const configService = new ConfigService();
|
||||
const configPath = options.test ? getConfigPath(true) : undefined;
|
||||
const configService = new ConfigService(
|
||||
configPath ? { configPath } : undefined,
|
||||
);
|
||||
const existingRemotes = await configService.getRemotes();
|
||||
|
||||
if (options.as !== undefined && existingRemotes.includes(options.as)) {
|
||||
|
||||
@@ -3,8 +3,10 @@ import {
|
||||
CONTAINER_NAME,
|
||||
containerExists,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TEST_PORT,
|
||||
getContainerPort,
|
||||
isContainerRunning,
|
||||
TEST_CONTAINER_NAME,
|
||||
} from '@/cli/utilities/server/docker-container';
|
||||
import { checkServerHealth } from '@/cli/utilities/server/detect-local-server';
|
||||
import chalk from 'chalk';
|
||||
@@ -19,9 +21,11 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('start')
|
||||
.description('Start a local Twenty server')
|
||||
.option('-p, --port <port>', 'HTTP port', String(DEFAULT_PORT))
|
||||
.action(async (options: { port: string }) => {
|
||||
const port = parseInt(options.port, 10);
|
||||
.option('-p, --port <port>', 'HTTP port')
|
||||
.option('--test', 'Start a separate test instance (port 2021)')
|
||||
.action(async (options: { port?: string; test?: boolean }) => {
|
||||
const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
const port = options.port ? parseInt(options.port, 10) : defaultPort;
|
||||
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
console.error(chalk.red('Invalid port number.'));
|
||||
@@ -30,6 +34,7 @@ export const registerServerCommands = (program: Command): void => {
|
||||
|
||||
const result = await serverStart({
|
||||
port,
|
||||
test: options.test,
|
||||
onProgress: (message) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
@@ -42,14 +47,17 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('stop')
|
||||
.description('Stop the local Twenty server')
|
||||
.action(() => {
|
||||
if (!containerExists()) {
|
||||
.option('--test', 'Stop the test instance')
|
||||
.action((options: { test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
|
||||
if (!containerExists(containerName)) {
|
||||
console.log(chalk.yellow('No Twenty server container found.'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
execSync(`docker stop ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
execSync(`docker stop ${containerName}`, { stdio: 'ignore' });
|
||||
console.log(chalk.green('Twenty server stopped.'));
|
||||
});
|
||||
|
||||
@@ -57,8 +65,11 @@ export const registerServerCommands = (program: Command): void => {
|
||||
.command('logs')
|
||||
.description('Stream Twenty server logs')
|
||||
.option('-n, --lines <lines>', 'Number of lines to show', '50')
|
||||
.action((options: { lines: string }) => {
|
||||
if (!containerExists()) {
|
||||
.option('--test', 'Show logs for the test instance')
|
||||
.action((options: { lines: string; test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
|
||||
if (!containerExists(containerName)) {
|
||||
console.log(chalk.yellow('No Twenty server container found.'));
|
||||
|
||||
return;
|
||||
@@ -67,7 +78,7 @@ export const registerServerCommands = (program: Command): void => {
|
||||
try {
|
||||
spawnSync(
|
||||
'docker',
|
||||
['logs', '-f', '--tail', options.lines, CONTAINER_NAME],
|
||||
['logs', '-f', '--tail', options.lines, containerName],
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
} catch {
|
||||
@@ -78,18 +89,24 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('status')
|
||||
.description('Show Twenty server status')
|
||||
.action(async () => {
|
||||
if (!containerExists()) {
|
||||
.option('--test', 'Show status of the test instance')
|
||||
.action(async (options: { test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
|
||||
if (!containerExists(containerName)) {
|
||||
console.log(` Status: ${chalk.gray('not created')}`);
|
||||
console.log(
|
||||
chalk.gray(" Run 'yarn twenty server start' to create one."),
|
||||
chalk.gray(
|
||||
` Run 'yarn twenty server start${options.test ? ' --test' : ''}' to create one.`,
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const running = isContainerRunning();
|
||||
const port = running ? getContainerPort() : DEFAULT_PORT;
|
||||
const running = isContainerRunning(containerName);
|
||||
const port = running ? getContainerPort(containerName) : defaultPort;
|
||||
const healthy = running ? await checkServerHealth(port) : false;
|
||||
|
||||
const statusText = healthy
|
||||
@@ -109,25 +126,33 @@ export const registerServerCommands = (program: Command): void => {
|
||||
server
|
||||
.command('reset')
|
||||
.description('Delete all data and start fresh')
|
||||
.action(() => {
|
||||
if (containerExists()) {
|
||||
execSync(`docker rm -f ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
.option('--test', 'Reset the test instance')
|
||||
.action((options: { test?: boolean }) => {
|
||||
const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
const volumeData = options.test
|
||||
? 'twenty-app-dev-test-data'
|
||||
: 'twenty-app-dev-data';
|
||||
const volumeStorage = options.test
|
||||
? 'twenty-app-dev-test-storage'
|
||||
: 'twenty-app-dev-storage';
|
||||
|
||||
if (containerExists(containerName)) {
|
||||
execSync(`docker rm -f ${containerName}`, { stdio: 'ignore' });
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(
|
||||
'docker volume rm twenty-app-dev-data twenty-app-dev-storage',
|
||||
{
|
||||
stdio: 'ignore',
|
||||
},
|
||||
);
|
||||
execSync(`docker volume rm ${volumeData} ${volumeStorage}`, {
|
||||
stdio: 'ignore',
|
||||
});
|
||||
} catch {
|
||||
// Volumes may not exist
|
||||
}
|
||||
|
||||
console.log(chalk.green('Twenty server data reset.'));
|
||||
console.log(
|
||||
chalk.gray("Run 'yarn twenty server start' to start a fresh instance."),
|
||||
chalk.gray(
|
||||
`Run 'yarn twenty server start${options.test ? ' --test' : ''}' to start a fresh instance.`,
|
||||
),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { SERVER_ERROR_CODES, type CommandResult } from '@/cli/types';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
import { runSafe } from '@/cli/utilities/run-safe';
|
||||
import {
|
||||
checkDockerRunning,
|
||||
CONTAINER_NAME,
|
||||
containerExists,
|
||||
DEFAULT_PORT,
|
||||
DEFAULT_TEST_PORT,
|
||||
getContainerPort,
|
||||
IMAGE,
|
||||
isContainerRunning,
|
||||
TEST_CONTAINER_NAME,
|
||||
} from '@/cli/utilities/server/docker-container';
|
||||
import {
|
||||
checkServerHealth,
|
||||
@@ -22,14 +25,17 @@ const HEALTH_TIMEOUT_MS = 180 * 1000;
|
||||
const MILESTONE_START = '==> START ';
|
||||
const MILESTONE_DONE = '==> DONE';
|
||||
|
||||
const waitForHealthy = async (port: number): Promise<boolean> => {
|
||||
const waitForHealthy = async (
|
||||
port: number,
|
||||
containerName: string,
|
||||
): Promise<boolean> => {
|
||||
const startTime = Date.now();
|
||||
const onProgress = (message: string) =>
|
||||
process.stdout.write(chalk.gray(message));
|
||||
|
||||
const logStream = spawn(
|
||||
'docker',
|
||||
['logs', '-f', '--since', '1s', CONTAINER_NAME],
|
||||
['logs', '-f', '--since', '1s', containerName],
|
||||
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
||||
);
|
||||
|
||||
@@ -98,6 +104,7 @@ const waitForHealthy = async (port: number): Promise<boolean> => {
|
||||
|
||||
export type ServerStartOptions = {
|
||||
port?: number;
|
||||
test?: boolean;
|
||||
onProgress?: (message: string) => void;
|
||||
};
|
||||
|
||||
@@ -109,12 +116,23 @@ export type ServerStartResult = {
|
||||
const innerServerStart = async (
|
||||
options: ServerStartOptions = {},
|
||||
): Promise<CommandResult<ServerStartResult>> => {
|
||||
const { onProgress } = options;
|
||||
const { onProgress, test: isTest } = options;
|
||||
|
||||
const existingUrl = await detectLocalServer(options.port);
|
||||
const containerName = isTest ? TEST_CONTAINER_NAME : CONTAINER_NAME;
|
||||
const defaultPort = isTest ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
const volumeData = isTest
|
||||
? 'twenty-app-dev-test-data'
|
||||
: 'twenty-app-dev-data';
|
||||
const volumeStorage = isTest
|
||||
? 'twenty-app-dev-test-storage'
|
||||
: 'twenty-app-dev-storage';
|
||||
|
||||
const existingUrl = await detectLocalServer(options.port ?? defaultPort);
|
||||
|
||||
if (existingUrl) {
|
||||
const configService = new ConfigService();
|
||||
const configService = new ConfigService(
|
||||
isTest ? { configPath: getConfigPath(true) } : undefined,
|
||||
);
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: existingUrl });
|
||||
@@ -139,12 +157,12 @@ const innerServerStart = async (
|
||||
};
|
||||
}
|
||||
|
||||
if (isContainerRunning()) {
|
||||
const port = getContainerPort();
|
||||
if (isContainerRunning(containerName)) {
|
||||
const port = getContainerPort(containerName);
|
||||
|
||||
onProgress?.('Container is running, waiting for it to become healthy...');
|
||||
|
||||
const healthy = await waitForHealthy(port);
|
||||
const healthy = await waitForHealthy(port, containerName);
|
||||
|
||||
if (!healthy) {
|
||||
return {
|
||||
@@ -159,7 +177,9 @@ const innerServerStart = async (
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
const configService = new ConfigService(
|
||||
isTest ? { configPath: getConfigPath(true) } : undefined,
|
||||
);
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: url });
|
||||
@@ -169,21 +189,21 @@ const innerServerStart = async (
|
||||
return { success: true, data: { port, url } };
|
||||
}
|
||||
|
||||
let port = options.port ?? DEFAULT_PORT;
|
||||
let port = options.port ?? defaultPort;
|
||||
|
||||
if (containerExists()) {
|
||||
const existingPort = getContainerPort();
|
||||
if (containerExists(containerName)) {
|
||||
const existingPort = getContainerPort(containerName);
|
||||
|
||||
if (existingPort !== port) {
|
||||
onProgress?.(
|
||||
`Existing container uses port ${existingPort}. Run 'yarn twenty server reset' first to change ports.`,
|
||||
`Existing container uses port ${existingPort}. Run 'yarn twenty server reset${isTest ? ' --test' : ''}' first to change ports.`,
|
||||
);
|
||||
}
|
||||
|
||||
port = existingPort;
|
||||
|
||||
onProgress?.('Starting existing container...');
|
||||
execSync(`docker start ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
execSync(`docker start ${containerName}`, { stdio: 'ignore' });
|
||||
} else {
|
||||
onProgress?.('Starting Twenty container...');
|
||||
|
||||
@@ -193,7 +213,7 @@ const innerServerStart = async (
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
CONTAINER_NAME,
|
||||
containerName,
|
||||
'-p',
|
||||
`${port}:${port}`,
|
||||
'-e',
|
||||
@@ -201,9 +221,9 @@ const innerServerStart = async (
|
||||
'-e',
|
||||
`SERVER_URL=http://localhost:${port}`,
|
||||
'-v',
|
||||
'twenty-app-dev-data:/data/postgres',
|
||||
`${volumeData}:/data/postgres`,
|
||||
'-v',
|
||||
'twenty-app-dev-storage:/app/packages/twenty-server/.local-storage',
|
||||
`${volumeStorage}:/app/packages/twenty-server/.local-storage`,
|
||||
IMAGE,
|
||||
],
|
||||
{ stdio: 'inherit' },
|
||||
@@ -222,7 +242,7 @@ const innerServerStart = async (
|
||||
|
||||
onProgress?.('Waiting for Twenty to be ready...');
|
||||
|
||||
const healthy = await waitForHealthy(port);
|
||||
const healthy = await waitForHealthy(port, containerName);
|
||||
|
||||
if (!healthy) {
|
||||
return {
|
||||
@@ -237,7 +257,9 @@ const innerServerStart = async (
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
const configService = new ConfigService(
|
||||
isTest ? { configPath: getConfigPath(true) } : undefined,
|
||||
);
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: url });
|
||||
|
||||
@@ -145,12 +145,6 @@ export class ApiClient {
|
||||
return this.tokenOverride;
|
||||
}
|
||||
|
||||
const envToken = process.env.TWENTY_TOKEN;
|
||||
|
||||
if (envToken) {
|
||||
return envToken;
|
||||
}
|
||||
|
||||
const config = await this.configService.getConfig();
|
||||
const accessToken = config.accessToken;
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ export class ConfigService {
|
||||
private readonly configPath: string;
|
||||
private static activeRemote = DEFAULT_REMOTE_NAME;
|
||||
|
||||
constructor() {
|
||||
this.configPath = getConfigPath();
|
||||
constructor(options?: { configPath?: string }) {
|
||||
this.configPath = options?.configPath ?? getConfigPath();
|
||||
}
|
||||
|
||||
static setActiveRemote(name?: string) {
|
||||
@@ -127,13 +127,6 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
async getConfig(): Promise<RemoteConfig> {
|
||||
if (process.env.TWENTY_TOKEN && process.env.TWENTY_API_URL) {
|
||||
return {
|
||||
apiUrl: process.env.TWENTY_API_URL,
|
||||
accessToken: process.env.TWENTY_TOKEN,
|
||||
};
|
||||
}
|
||||
|
||||
return this.getConfigForRemote(this.getActiveRemoteName());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
const TWENTY_DIR = path.join(os.homedir(), '.twenty');
|
||||
|
||||
export const getConfigPath = (): string => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return path.join(TEST_CONFIG_DIR, 'config.json');
|
||||
export const getConfigPath = (test = false): string => {
|
||||
if (test || process.env.NODE_ENV === 'test') {
|
||||
return path.join(TWENTY_DIR, 'config.test.json');
|
||||
}
|
||||
|
||||
return path.join(os.homedir(), '.twenty', 'config.json');
|
||||
return path.join(TWENTY_DIR, 'config.json');
|
||||
};
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
export const CONTAINER_NAME = 'twenty-app-dev';
|
||||
export const TEST_CONTAINER_NAME = 'twenty-app-dev-test';
|
||||
export const IMAGE = 'twentycrm/twenty-app-dev:latest';
|
||||
export const DEFAULT_PORT = 2020;
|
||||
export const DEFAULT_TEST_PORT = 2021;
|
||||
|
||||
export const isContainerRunning = (): boolean => {
|
||||
export const isContainerRunning = (containerName = CONTAINER_NAME): boolean => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{.State.Running}}' ${CONTAINER_NAME}`,
|
||||
`docker inspect -f '{{.State.Running}}' ${containerName}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
).trim();
|
||||
|
||||
@@ -17,24 +19,27 @@ export const isContainerRunning = (): boolean => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getContainerPort = (): number => {
|
||||
export const getContainerPort = (containerName = CONTAINER_NAME): number => {
|
||||
const defaultPort =
|
||||
containerName === TEST_CONTAINER_NAME ? DEFAULT_TEST_PORT : DEFAULT_PORT;
|
||||
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' ${CONTAINER_NAME}`,
|
||||
`docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' ${containerName}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
);
|
||||
|
||||
const match = result.match(/^NODE_PORT=(\d+)$/m);
|
||||
|
||||
return match ? parseInt(match[1], 10) : DEFAULT_PORT;
|
||||
return match ? parseInt(match[1], 10) : defaultPort;
|
||||
} catch {
|
||||
return DEFAULT_PORT;
|
||||
return defaultPort;
|
||||
}
|
||||
};
|
||||
|
||||
export const containerExists = (): boolean => {
|
||||
export const containerExists = (containerName = CONTAINER_NAME): boolean => {
|
||||
try {
|
||||
execSync(`docker inspect ${CONTAINER_NAME}`, {
|
||||
execSync(`docker inspect ${containerName}`, {
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user