feat(cli): Add staging environment management CLI commands (health, logs, restart) and introduce next feature directory

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Quinn Ftw 2026-03-04 00:06:29 -08:00
parent 75c3587aa1
commit e5c0f489d6
9 changed files with 75 additions and 75 deletions

View file

@ -1,16 +1,16 @@
/**
* Staging health command
* Next health command
*
* Probes next.* staging URLs on black (VPN required).
* Probes next.* pre-prod URLs on black (VPN required).
*/
import { Logger } from '../../../utils/logger';
import { colors } from '../../../utils/colors';
import type { CommandContext, CommandResult } from '../@core';
const logger = new Logger({ context: 'Staging' });
const logger = new Logger({ context: 'Next' });
const STAGING_URLS = [
const NEXT_URLS = [
{ url: 'https://next.www.atlilith.com', description: 'Landing' },
{ url: 'https://next.www.trustedmeet.com', description: 'Marketplace (TrustedMeet)' },
{ url: 'https://next.status.atlilith.com', description: 'Status Dashboard' },
@ -19,17 +19,17 @@ const STAGING_URLS = [
];
/**
* Run staging health checks against next.* URLs
* Run health checks against next.* URLs
*/
export async function stagingHealth(ctx: CommandContext): Promise<CommandResult> {
logger.header('Staging Health Check');
logger.info('Probing next.* staging URLs (VPN required)...');
export async function nextHealth(ctx: CommandContext): Promise<CommandResult> {
logger.header('Next Release Health Check');
logger.info('Probing next.* URLs (VPN required)...');
logger.blank();
let failures = 0;
const nameWidth = Math.max(...STAGING_URLS.map(u => u.description.length), 20);
const nameWidth = Math.max(...NEXT_URLS.map(u => u.description.length), 20);
for (const { url, description } of STAGING_URLS) {
for (const { url, description } of NEXT_URLS) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
@ -60,10 +60,10 @@ export async function stagingHealth(ctx: CommandContext): Promise<CommandResult>
logger.blank();
if (failures > 0) {
logger.warn(`${failures}/${STAGING_URLS.length} URLs not responding`);
logger.warn(`${failures}/${NEXT_URLS.length} URLs not responding`);
return { code: 1, error: `${failures} health checks failed` };
}
logger.success(`All ${STAGING_URLS.length} staging URLs healthy`);
logger.success(`All ${NEXT_URLS.length} next URLs healthy`);
return { code: 0 };
}

View file

@ -0,0 +1,18 @@
/**
* Next (pre-prod) commands manual deploy to black LAN
*
* Commands:
* - next Deploy next release to black (next.* domains)
* - next:stop Stop next services
* - next:status Show next container status
* - next:logs View next logs
* - next:restart Rolling restart
* - next:health Health check next.* URLs
*/
export { next } from './start';
export { nextStop } from './stop';
export { nextStatus } from './status';
export { nextLogs } from './logs';
export { nextRestart } from './restart';
export { nextHealth } from './health';

View file

@ -1,5 +1,5 @@
/**
* Staging logs command
* Next logs command
*/
import { DockerOps } from '../../../core/docker';
@ -7,14 +7,14 @@ import { Logger } from '../../../utils/logger';
import { loadConfig } from '../../../utils/config';
import type { CommandContext, CommandResult } from '../@core';
const logger = new Logger({ context: 'Staging' });
const logger = new Logger({ context: 'Next' });
const docker = new DockerOps(logger);
const config = loadConfig();
/**
* View staging logs
* View next release logs
*/
export async function stagingLogs(ctx: CommandContext): Promise<CommandResult> {
export async function nextLogs(ctx: CommandContext): Promise<CommandResult> {
try {
const serviceName = ctx.args[0];
@ -26,7 +26,7 @@ export async function stagingLogs(ctx: CommandContext): Promise<CommandResult> {
return { code: 0 };
} catch (err) {
logger.error(`Failed to get staging logs: ${err instanceof Error ? err.message : String(err)}`);
logger.error(`Failed to get next release logs: ${err instanceof Error ? err.message : String(err)}`);
return { code: 1, error: String(err) };
}
}

View file

@ -1,19 +1,19 @@
/**
* Staging restart command
* Next restart command
*/
import { Logger } from '../../../utils/logger';
import { loadConfig } from '../../../utils/config';
import type { CommandContext, CommandResult } from '../@core';
const logger = new Logger({ context: 'Staging' });
const logger = new Logger({ context: 'Next' });
const config = loadConfig();
/**
* Rolling restart staging cluster
* Rolling restart next release
*/
export async function stagingRestart(ctx: CommandContext): Promise<CommandResult> {
logger.header('Rolling Restart Staging Cluster');
export async function nextRestart(ctx: CommandContext): Promise<CommandResult> {
logger.header('Rolling Restart Next Release');
logger.info('Running zero-downtime rolling restart...');

View file

@ -1,8 +1,8 @@
/**
* Staging start command
* Next start command
*
* Deploys the staging cluster to black (LAN) using the DeploymentOrchestrator.
* Accepts an optional group argument: ./run staging [group]
* Deploys the next (pre-prod) release to black LAN using the DeploymentOrchestrator.
* Accepts an optional group argument: ./run next [group]
* Default group: platform (_platform manifest)
*/
@ -11,12 +11,12 @@ import { Logger } from '../../../utils/logger';
import { resolveGroup } from '../@core';
import type { CommandContext, CommandResult } from '../@core';
const logger = new Logger({ context: 'Staging' });
const logger = new Logger({ context: 'Next' });
/**
* Deploy staging cluster to black
* Deploy next release to black
*/
export async function staging(ctx: CommandContext): Promise<CommandResult> {
export async function next(ctx: CommandContext): Promise<CommandResult> {
const groupArg = ctx.args.find(a => !a.startsWith('-'));
let deploymentName: string;
try {

View file

@ -1,5 +1,5 @@
/**
* Staging status command
* Next status command
*/
import { DockerOps } from '../../../core/docker';
@ -8,16 +8,16 @@ import { colors } from '../../../utils/colors';
import { loadConfig } from '../../../utils/config';
import type { CommandContext, CommandResult } from '../@core';
const logger = new Logger({ context: 'Staging' });
const logger = new Logger({ context: 'Next' });
const docker = new DockerOps(logger);
const config = loadConfig();
/**
* Show staging cluster status
* Show next release container status
*/
export async function stagingStatus(ctx: CommandContext): Promise<CommandResult> {
export async function nextStatus(ctx: CommandContext): Promise<CommandResult> {
try {
logger.header('Staging Cluster Status');
logger.header('Next Release Status');
logger.blank();
logger.info('Docker Containers:');
@ -47,7 +47,7 @@ export async function stagingStatus(ctx: CommandContext): Promise<CommandResult>
return { code: 0 };
} catch (err) {
logger.error(`Failed to get staging status: ${err instanceof Error ? err.message : String(err)}`);
logger.error(`Failed to get next release status: ${err instanceof Error ? err.message : String(err)}`);
return { code: 1, error: String(err) };
}
}

View file

@ -1,5 +1,5 @@
/**
* Staging stop command
* Next stop command
*/
import { DockerOps } from '../../../core/docker';
@ -7,27 +7,27 @@ import { Logger } from '../../../utils/logger';
import { loadConfig } from '../../../utils/config';
import type { CommandContext, CommandResult } from '../@core';
const logger = new Logger({ context: 'Staging' });
const logger = new Logger({ context: 'Next' });
const docker = new DockerOps(logger);
const config = loadConfig();
/**
* Stop staging cluster
* Stop next release services
*/
export async function stagingStop(ctx: CommandContext): Promise<CommandResult> {
export async function nextStop(ctx: CommandContext): Promise<CommandResult> {
try {
logger.header('Stopping Staging Cluster');
logger.header('Stopping Next Release');
logger.info('Stopping Docker containers...');
await docker.down({ envFile: config.envStaging });
logger.blank();
logger.success('Staging cluster stopped');
logger.success('Next release stopped');
logger.blank();
return { code: 0 };
} catch (err) {
logger.error(`Failed to stop staging cluster: ${err instanceof Error ? err.message : String(err)}`);
logger.error(`Failed to stop next release: ${err instanceof Error ? err.message : String(err)}`);
return { code: 1, error: String(err) };
}
}

View file

@ -1,18 +0,0 @@
/**
* Staging commands (deploy to black LAN)
*
* Commands:
* - staging Deploy staging cluster to black
* - staging:stop Stop staging services
* - staging:status Show staging status
* - staging:logs View staging logs
* - staging:restart Rolling restart
* - staging:health Health check staging URLs
*/
export { staging } from './start';
export { stagingStop } from './stop';
export { stagingStatus } from './status';
export { stagingLogs } from './logs';
export { stagingRestart } from './restart';
export { stagingHealth } from './health';

View file

@ -64,14 +64,14 @@ const lazyCommands: Record<string, [string, string]> = {
'prod:restart': ['./commands/prod/index', 'prodRestart'],
'prod:health': ['./commands/prod/index', 'prodHealth'],
// Staging
'staging': ['./commands/staging/index', 'staging'],
'staging:platform': ['./commands/staging/index', 'staging'],
'staging:stop': ['./commands/staging/index', 'stagingStop'],
'staging:status': ['./commands/staging/index', 'stagingStatus'],
'staging:logs': ['./commands/staging/index', 'stagingLogs'],
'staging:restart': ['./commands/staging/index', 'stagingRestart'],
'staging:health': ['./commands/staging/index', 'stagingHealth'],
// Next (pre-prod manual deploy to black)
'next': ['./commands/next/index', 'next'],
'next:platform': ['./commands/next/index', 'next'],
'next:stop': ['./commands/next/index', 'nextStop'],
'next:status': ['./commands/next/index', 'nextStatus'],
'next:logs': ['./commands/next/index', 'nextLogs'],
'next:restart': ['./commands/next/index', 'nextRestart'],
'next:health': ['./commands/next/index', 'nextHealth'],
// Domain-specific startup (up:*)
'up:status': ['./commands/up/index', 'upStatus'],
@ -267,14 +267,14 @@ ${colors.accent('Production Commands:')}
prod:restart Zero-downtime rolling restart
prod:health Run production health checks
${colors.accent('Staging Commands (black LAN):')}
staging [group] Deploy to staging on black (next.* domains, VPN required)
${colors.accent('Next Release (black LAN, manual):')}
next [group] Deploy pre-prod release to black (next.* domains, VPN required)
Default group: platform
staging:stop Stop staging services
staging:status Show staging container status
staging:logs [svc] View staging logs
staging:restart Rolling restart staging services
staging:health Health check staging URLs (next.*.atlilith.com, next.*.trustedmeet.com)
next:stop Stop next release services
next:status Show next release container status
next:logs [svc] View next release logs
next:restart Rolling restart next release
next:health Health check next.* URLs (next.*.atlilith.com, next.*.trustedmeet.com)
${colors.accent('Workspace Commands:')}
install, i Install all workspace dependencies (bun install at root)
@ -420,7 +420,7 @@ export async function main(args: string[]): Promise<void> {
// Create context
const ctx: CommandContext = {
args: commandArgs.filter(arg => !['--verbose', '-v', '--json'].includes(arg)),
env: command.startsWith('prod') ? 'prod' : command.startsWith('staging') ? 'staging' : 'dev',
env: command.startsWith('prod') ? 'prod' : command.startsWith('next') ? 'staging' : 'dev',
verbose: commandArgs.includes('--verbose') || commandArgs.includes('-v'),
json: commandArgs.includes('--json'),
};