Created
September 12, 2025 16:59
-
-
Save vitormv/57f4310b3c47634c9f3b271fcda6d855 to your computer and use it in GitHub Desktop.
Nelly Start
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ts-node-script | |
| import { execSync } from "node:child_process"; | |
| import { chdir } from "node:process"; | |
| import * as path from "node:path"; | |
| import assert from "node:assert"; | |
| const red = (text: string) => `\x1b[31m${text}\x1b[0m`; | |
| // Define your paths 🛤️ | |
| const NELLY_MONO_PATH = path.resolve(path.join(import.meta.dirname, "..")); | |
| const NELLY_MONO_DOCKER_COMPOSE_CMD = "docker compose up -d"; | |
| // check if docker is running | |
| // docker info > /dev/null 2>&1 && echo "Docker is running" || echo "Docker is not running" | |
| // using the command above, if docker is not running, start it | |
| try { | |
| execSync("docker info", { stdio: "ignore" }); | |
| } catch (error) { | |
| console.error( | |
| ` ${red("Docker is not running. Please start it, and try again.")}` | |
| ); | |
| process.exit(1); | |
| } | |
| // Define Service type - consolidate all possible services as a union type | |
| export type ServiceName = | |
| | "api" | |
| | "lib" | |
| | "patient-portal" | |
| | "website" | |
| | "doctor-portal" | |
| | "backoffice-web" | |
| | "backoffice-api" | |
| | "feature-flags"; | |
| export type ServiceDefinition = { | |
| cmd: string; | |
| workingDir: string; | |
| dependencies?: ServiceName[]; | |
| alias?: string[]; | |
| color: string; // Color to use in concurrently output | |
| }; | |
| // Define all services with organized structure | |
| const serviceConfig: Record<ServiceName, ServiceDefinition> = { | |
| lib: { | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/lib"), | |
| cmd: "pnpx nodemon -w src -e js,ts --exec 'pnpm build'", | |
| color: "yellow.bold", | |
| }, | |
| "backoffice-api": { | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/backoffice-api"), | |
| cmd: "PORT=8081 pnpm dev", | |
| dependencies: ["lib"], | |
| color: "#eb8334", | |
| }, | |
| "backoffice-web": { | |
| alias: ["bo"], | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/backoffice-web"), | |
| cmd: "PORT=3000 pnpm dev", | |
| dependencies: ["lib", "feature-flags", "backoffice-api"], | |
| color: "cyan", | |
| }, | |
| api: { | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/api"), | |
| cmd: "pnpx wait-on --timeout 10s tcp:5432 && pnpx wait-on --timeout 10s tcp:9094 && sleep 3 && pnpm dev", | |
| dependencies: ["lib", "feature-flags"], | |
| color: "magenta", | |
| }, | |
| "patient-portal": { | |
| alias: ["papo"], | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/patient-portal"), | |
| cmd: "PORT=3002 pnpm dev", | |
| dependencies: ["lib", "feature-flags", "api"], | |
| color: "blue", | |
| }, | |
| website: { | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/website"), | |
| cmd: "PORT=3003 pnpm dev", | |
| dependencies: ["lib", "api"], | |
| color: "red", | |
| }, | |
| "doctor-portal": { | |
| alias: ["dopo"], | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/doctor-portal"), | |
| cmd: "PORT=3001 pnpm dev", | |
| dependencies: ["lib", "feature-flags", "api"], | |
| color: "#eb8334", // nelly green | |
| }, | |
| "feature-flags": { | |
| alias: ["ff"], | |
| workingDir: path.join(NELLY_MONO_PATH, "packages/feature-flag-node"), | |
| cmd: "ldcli dev-server start --base-uri https://app.eu.launchdarkly.com --dev-stream-uri https://stream.eu.launchdarkly.com --project default --source demo", | |
| color: "blue.bold", | |
| }, | |
| }; | |
| // Define all available services | |
| const ALL_SERVICE_NAMES = Object.keys(serviceConfig) as ServiceName[]; | |
| // Function to start a service | |
| const startService = (service: ServiceName): string => { | |
| const config = serviceConfig[service]; | |
| if (!config) { | |
| throw new Error(`Unknown service: ${service}`); | |
| } | |
| return `cd ${config.workingDir} && ${config.cmd}`; | |
| }; | |
| // Function to convert alias to service name | |
| const resolveServiceName = (input: string): ServiceName => { | |
| // Check if input is a direct service name | |
| if (input in serviceConfig) { | |
| return input as ServiceName; | |
| } | |
| // Check if input is an alias | |
| for (const [serviceName, config] of Object.entries(serviceConfig)) { | |
| if (config.alias?.includes(input)) { | |
| return serviceName as ServiceName; | |
| } | |
| } | |
| throw new Error(`Unknown service or alias: ${input}`); | |
| }; | |
| // typeguard to check if a string is a valid service or alias | |
| const isValidServiceInput = (service: string): boolean => { | |
| return resolveServiceName(service) !== null; | |
| }; | |
| // Function to get unique services including dependencies | |
| const getServices = (requestedInputs: string[]): ServiceName[] => { | |
| const services = new Set<ServiceName>(); | |
| const invalidInputs = requestedInputs.filter( | |
| (input) => !isValidServiceInput(input) | |
| ); | |
| assert( | |
| invalidInputs.length === 0, | |
| `Invalid service(s) or alias(es) provided: ${invalidInputs.join(", ")}` | |
| ); | |
| // Convert inputs (which might be aliases) to actual service names | |
| const requestedServices: ServiceName[] = requestedInputs.map((input) => | |
| resolveServiceName(input) | |
| ); | |
| const allDeps = requestedServices.flatMap( | |
| (service) => serviceConfig[service].dependencies ?? [] | |
| ); | |
| for (const dep of allDeps) { | |
| if (!services.has(dep)) { | |
| services.add(dep); | |
| } | |
| } | |
| // Add the requested services themselves | |
| for (const service of requestedServices) { | |
| services.add(service); | |
| } | |
| return Array.from(services); | |
| }; | |
| // Set the name of your developer profile | |
| process.env.AWS_PROFILE = "nelly-staging-services"; | |
| // Check if AWS SSO is logged in, if not login | |
| const isAwsSsoLoggedIn = (): boolean => { | |
| try { | |
| execSync("aws sts get-caller-identity", { stdio: "ignore" }); | |
| return true; | |
| } catch (error) { | |
| return false; | |
| } | |
| }; | |
| // start docker cmd | |
| execSync(NELLY_MONO_DOCKER_COMPOSE_CMD, { stdio: "ignore" }); | |
| if (!isAwsSsoLoggedIn()) { | |
| console.log("Logging in with AWS SSO..."); | |
| execSync("aws sso login", { stdio: "inherit" }); | |
| } | |
| // Install dependencies 📦 at start-up | |
| chdir(NELLY_MONO_PATH); | |
| console.log("Installing dependencies..."); | |
| execSync("pnpm install", { stdio: "inherit" }); | |
| // Get services to start | |
| const args = process.argv.slice(2); | |
| const serviceNames = args.length === 0 ? ALL_SERVICE_NAMES : getServices(args); | |
| const maxNameLength = Math.max( | |
| ...serviceNames.map((service) => service.length) | |
| ); | |
| // Prepare concurrently command with fixed colors for each service | |
| const names = serviceNames | |
| .map((name) => name.padStart(maxNameLength, " ")) | |
| .join(","); | |
| const colors = serviceNames | |
| .map((service) => serviceConfig[service].color) | |
| .join(","); | |
| const cmd = [ | |
| "pnpx", | |
| "concurrently", | |
| "--kill-others-on-fail", | |
| `--names="${names}"`, | |
| `--prefix-colors=${colors}`, | |
| ]; | |
| for (const service of serviceNames) { | |
| const serviceCmd = startService(service as ServiceName); | |
| cmd.push(`"${serviceCmd}"`); | |
| } | |
| // Run the command | |
| console.log(`Starting services: ${serviceNames.join(", ")}`); | |
| const fullCmd = cmd.join(" "); | |
| console.log(`Running: ${fullCmd}`); | |
| // Execute the command | |
| execSync(fullCmd, { stdio: "inherit" }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment