diff --git a/package.json b/package.json index beaf1e0a..22de8da3 100644 --- a/package.json +++ b/package.json @@ -33,5 +33,6 @@ "overrides": { "handlebars": "4.7.9" } - } + }, + "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c" } diff --git a/packages/cli/package.json b/packages/cli/package.json index 0700b000..2bae4d6e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -9,13 +9,15 @@ "README.md" ], "bin": { - "ecloud": "./bin/run.js" + "ecloud": "./bin/run.js", + "ecloud-dev": "./bin/run.js" }, "dependencies": { "@inquirer/prompts": "^7.10.1", "@layr-labs/ecloud-sdk": "workspace:*", "@napi-rs/keyring": "^1.0.5", "@oclif/core": "^4.8.0", + "@oclif/plugin-autocomplete": "^3.2.40", "axios": "^1.13.2", "chalk": "^5.6.2", "cli-table3": "^0.6.5", @@ -44,6 +46,9 @@ "commands": "./dist/commands", "dirname": "", "topicSeparator": " ", + "plugins": [ + "@oclif/plugin-autocomplete" + ], "hooks": { "init": "./dist/hooks/init/version-check" }, diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index 877ec3cd..62fc1060 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -4,7 +4,7 @@ * Store an existing private key in OS keyring */ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { confirm, select } from "@inquirer/prompts"; import { storePrivateKey, @@ -22,81 +22,112 @@ import { withTelemetry } from "../../telemetry"; export default class AuthLogin extends Command { static description = "Store your private key in OS keyring"; - static examples = ["<%= config.bin %> <%= command.id %>"]; + static examples = [ + "<%= config.bin %> auth login", + "<%= config.bin %> auth login --private-key 0x...", + "<%= config.bin %> auth login --private-key 0x... --force", + ]; + + static flags = { + "private-key": Flags.string({ + description: "Private key to store (skips interactive prompt)", + env: "ECLOUD_PRIVATE_KEY", + }), + force: Flags.boolean({ + description: "Skip all confirmation prompts", + default: false, + }), + }; async run(): Promise { return withTelemetry(this, async () => { + const { flags } = await this.parse(AuthLogin); + const isNonInteractive = !!flags["private-key"]; + // Check if key already exists const exists = await keyExists(); if (exists) { - displayWarning([ - "WARNING: A private key for ecloud already exists!", - "Replacing it will cause PERMANENT DATA LOSS if not backed up.", - "The previous key will be lost forever.", - ]); - - const confirmReplace = await confirm({ - message: "Replace existing key?", - default: false, - }); + if (isNonInteractive) { + if (!flags.force) { + this.error( + "A private key already exists. Use --force to replace it.", + ); + } + } else { + displayWarning([ + "WARNING: A private key for ecloud already exists!", + "Replacing it will cause PERMANENT DATA LOSS if not backed up.", + "The previous key will be lost forever.", + ]); + + const confirmReplace = await confirm({ + message: "Replace existing key?", + default: false, + }); - if (!confirmReplace) { - this.log("\nLogin cancelled."); - return; + if (!confirmReplace) { + this.log("\nLogin cancelled."); + return; + } } } - // Check for legacy keys from eigenx-cli - const legacyKeys = await getLegacyKeys(); let privateKey: string | null = null; let selectedKey: LegacyKey | null = null; - if (legacyKeys.length > 0) { - this.log("\nFound legacy keys from eigenx-cli:"); - this.log(""); + if (isNonInteractive) { + // Use flag value directly + privateKey = flags["private-key"]!; + } else { + // Check for legacy keys from eigenx-cli + const legacyKeys = await getLegacyKeys(); - // Display legacy keys - for (const key of legacyKeys) { - this.log(` Address: ${key.address}`); - this.log(` Environment: ${key.environment}`); - this.log(` Source: ${key.source}`); + if (legacyKeys.length > 0) { + this.log("\nFound legacy keys from eigenx-cli:"); this.log(""); - } - const importLegacy = await confirm({ - message: "Would you like to import one of these legacy keys?", - default: false, - }); - - if (importLegacy) { - // Create choices for selection - const choices = legacyKeys.map((key) => ({ - name: `${key.address} (${key.environment} - ${key.source})`, - value: key, - })); + // Display legacy keys + for (const key of legacyKeys) { + this.log(` Address: ${key.address}`); + this.log(` Environment: ${key.environment}`); + this.log(` Source: ${key.source}`); + this.log(""); + } - selectedKey = await select({ - message: "Select a key to import:", - choices, + const importLegacy = await confirm({ + message: "Would you like to import one of these legacy keys?", + default: false, }); - // Retrieve the actual private key - privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source); + if (importLegacy) { + // Create choices for selection + const choices = legacyKeys.map((key) => ({ + name: `${key.address} (${key.environment} - ${key.source})`, + value: key, + })); - if (!privateKey) { - this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`); - } + selectedKey = await select({ + message: "Select a key to import:", + choices, + }); - this.log(`\nImporting key from ${selectedKey.source}:${selectedKey.environment}`); - } - } + // Retrieve the actual private key + privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source); - // If no legacy key was selected, prompt for private key input - if (!privateKey) { - privateKey = await getHiddenInput("Enter your private key:"); + if (!privateKey) { + this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`); + } + + this.log(`\nImporting key from ${selectedKey.source}:${selectedKey.environment}`); + } + } - privateKey = privateKey.trim(); + // If no legacy key was selected, prompt for private key input + if (!privateKey) { + privateKey = await getHiddenInput("Enter your private key:"); + privateKey = privateKey.trim(); + } } if (!validatePrivateKey(privateKey)) { @@ -108,14 +139,16 @@ export default class AuthLogin extends Command { this.log(`\nAddress: ${address}`); - const confirmStore = await confirm({ - message: "Store this key in OS keyring?", - default: true, - }); + if (!isNonInteractive) { + const confirmStore = await confirm({ + message: "Store this key in OS keyring?", + default: true, + }); - if (!confirmStore) { - this.log("\nLogin cancelled."); - return; + if (!confirmStore) { + this.log("\nLogin cancelled."); + return; + } } // Store in keyring @@ -129,12 +162,13 @@ export default class AuthLogin extends Command { // Ask if user wants to delete the legacy key (only if save was successful) if (selectedKey) { this.log(""); - const confirmDelete = await confirm({ + + const shouldDelete = flags.force || await confirm({ message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`, default: false, }); - if (confirmDelete) { + if (shouldDelete) { const deleted = await deleteLegacyPrivateKey( selectedKey.environment, selectedKey.source, diff --git a/packages/cli/src/commands/auth/migrate.ts b/packages/cli/src/commands/auth/migrate.ts index 44e69e4b..065e1a84 100644 --- a/packages/cli/src/commands/auth/migrate.ts +++ b/packages/cli/src/commands/auth/migrate.ts @@ -4,7 +4,7 @@ * Migrate a legacy eigenx-cli key to ecloud */ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import { confirm, select } from "@inquirer/prompts"; import { storePrivateKey, @@ -21,10 +21,30 @@ import { withTelemetry } from "../../telemetry"; export default class AuthMigrate extends Command { static description = "Migrate a private key from eigenx-cli to ecloud"; - static examples = ["<%= config.bin %> <%= command.id %>"]; + static examples = [ + "<%= config.bin %> auth migrate", + "<%= config.bin %> auth migrate --environment sepolia", + "<%= config.bin %> auth migrate --environment sepolia --delete-legacy --force", + ]; + + static flags = { + environment: Flags.string({ + description: "Environment of the legacy key to migrate (e.g. sepolia, mainnet)", + }), + "delete-legacy": Flags.boolean({ + description: "Delete the legacy key after migration", + default: false, + }), + force: Flags.boolean({ + description: "Skip all confirmation prompts", + default: false, + }), + }; async run(): Promise { return withTelemetry(this, async () => { + const { flags } = await this.parse(AuthMigrate); + const legacyKeys = await getLegacyKeys(); if (legacyKeys.length === 0) { @@ -46,16 +66,31 @@ export default class AuthMigrate extends Command { this.log(""); } - // Create choices for selection - const choices = legacyKeys.map((key) => ({ - name: `${key.address} (${key.environment} - ${key.source})`, - value: key, - })); - - const selectedKey = await select({ - message: "Select a key to migrate:", - choices, - }); + let selectedKey: LegacyKey; + + if (flags.environment) { + // Match by environment flag + const match = legacyKeys.find( + (k) => k.environment.toLowerCase() === flags.environment!.toLowerCase(), + ); + if (!match) { + this.error( + `No legacy key found for environment '${flags.environment}'. Available: ${legacyKeys.map((k) => k.environment).join(", ")}`, + ); + } + selectedKey = match; + } else { + // Interactive selection + const choices = legacyKeys.map((key) => ({ + name: `${key.address} (${key.environment} - ${key.source})`, + value: key, + })); + + selectedKey = await select({ + message: "Select a key to migrate:", + choices, + }); + } // Retrieve the actual private key const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source); @@ -73,21 +108,25 @@ export default class AuthMigrate extends Command { const exists = await keyExists(); if (exists) { - this.log(""); - displayWarning([ - "WARNING: A private key for ecloud already exists!", - "Replacing it will cause PERMANENT DATA LOSS if not backed up.", - "The previous key will be lost forever.", - ]); - - const confirmReplace = await confirm({ - message: "Replace existing ecloud key?", - default: false, - }); - - if (!confirmReplace) { - this.log("\nMigration cancelled."); - return; + if (flags.force) { + // Skip confirmation + } else { + this.log(""); + displayWarning([ + "WARNING: A private key for ecloud already exists!", + "Replacing it will cause PERMANENT DATA LOSS if not backed up.", + "The previous key will be lost forever.", + ]); + + const confirmReplace = await confirm({ + message: "Replace existing ecloud key?", + default: false, + }); + + if (!confirmReplace) { + this.log("\nMigration cancelled."); + return; + } } } @@ -98,14 +137,19 @@ export default class AuthMigrate extends Command { this.log(`✓ Address: ${address}`); this.log("\nNote: This key will be used for all environments (mainnet, sepolia, etc.)"); - // Ask if user wants to delete the legacy key (only if save was successful) - this.log(""); - const confirmDelete = await confirm({ - message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`, - default: false, - }); - - if (confirmDelete) { + // Delete legacy key + const shouldDelete = + flags["delete-legacy"] || + (!flags.force && + (await (async () => { + this.log(""); + return confirm({ + message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`, + default: false, + }); + })())); + + if (shouldDelete) { const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source); if (deleted) { diff --git a/packages/cli/src/commands/compute/app/configure/tls.ts b/packages/cli/src/commands/compute/app/configure/tls.ts index dfe15c72..8e7b355a 100644 --- a/packages/cli/src/commands/compute/app/configure/tls.ts +++ b/packages/cli/src/commands/compute/app/configure/tls.ts @@ -1,75 +1,196 @@ -import { Command } from "@oclif/core"; +import { Command, Flags } from "@oclif/core"; import * as fs from "fs"; import * as path from "path"; import chalk from "chalk"; -import { getCaddyfileTemplate, ENV_EXAMPLE_TLS } from "../../../../templates/tls/templates.js"; +import { input, confirm } from "@inquirer/prompts"; +import { + getCaddyfileTemplate, + getTlsEnvBlock, + TLS_ENV_EXAMPLE_BLOCK, +} from "../../../../templates/tls/templates.js"; + +function envFileHasTlsConfig(filePath: string): boolean { + if (!fs.existsSync(filePath)) return false; + const content = fs.readFileSync(filePath, "utf-8"); + return /^DOMAIN=/m.test(content); +} + +function validateDomain(value: string): true | string { + const trimmed = value.trim(); + if (!trimmed) return "Domain is required"; + if (trimmed.toLowerCase() === "localhost") return "Domain cannot be localhost"; + if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$/.test(trimmed)) + return "Enter a valid domain (e.g. myapp.example.com)"; + return true; +} + +function validatePort(value: string): true | string { + const num = Number(value.trim()); + if (!Number.isInteger(num) || num < 1 || num > 65535) return "Enter a valid port (1-65535)"; + return true; +} export default class ConfigureTLS extends Command { static description = "Configure TLS for your application"; - static summary = `Adds TLS configuration to your EigenCloud application. + static summary = `Configures TLS for your EigenCloud application. -This command creates: -- Caddyfile: Reverse proxy configuration for automatic HTTPS -- .env.example.tls: Example environment variables for TLS +Prompts for domain and TLS settings (or accepts them via flags), then: +- Creates a Caddyfile for automatic HTTPS via Caddy reverse proxy +- Appends TLS variables to .env with your values +- Appends TLS placeholders to .env.example TLS certificates are automatically obtained via Let's Encrypt using the tls-keygen tool.`; + static examples = [ + "<%= config.bin %> compute app configure tls", + "<%= config.bin %> compute app configure tls --domain myapp.example.com", + "<%= config.bin %> compute app configure tls --domain myapp.example.com --app-port 8080", + "<%= config.bin %> compute app configure tls --domain myapp.example.com --no-acme-staging", + ]; + + static flags = { + domain: Flags.string({ + description: "Domain name for TLS certificate", + }), + "app-port": Flags.string({ + description: "Port your application listens on", + default: "3000", + }), + "acme-staging": Flags.boolean({ + description: "Use Let's Encrypt staging environment", + default: true, + allowNo: true, + }), + "caddy-logs": Flags.boolean({ + description: "Enable Caddy debug logs", + default: false, + allowNo: true, + }), + }; + async run() { + const { flags } = await this.parse(ConfigureTLS); const cwd = process.cwd(); + // Check if TLS is already configured in .env + const envPath = path.join(cwd, ".env"); + if (envFileHasTlsConfig(envPath)) { + this.warn("TLS is already configured in .env (DOMAIN is set). Skipping."); + return; + } + // Write Caddyfile const caddyfilePath = path.join(cwd, "Caddyfile"); if (fs.existsSync(caddyfilePath)) { - this.warn("Caddyfile already exists. Skipping..."); + this.log("Caddyfile already exists, keeping existing file."); } else { const caddyfileContent = getCaddyfileTemplate(); fs.writeFileSync(caddyfilePath, caddyfileContent, { mode: 0o644 }); this.log("Created Caddyfile"); } - // Write .env.example.tls - const envTLSPath = path.join(cwd, ".env.example.tls"); - if (fs.existsSync(envTLSPath)) { - this.warn(".env.example.tls already exists. Skipping..."); + this.log(""); + + // Resolve values: use flags if provided, otherwise prompt + let domain = flags.domain; + if (!domain) { + domain = await input({ + message: "Domain name:", + validate: validateDomain, + }); } else { - fs.writeFileSync(envTLSPath, ENV_EXAMPLE_TLS, { mode: 0o644 }); - this.log("Created .env.example.tls"); + const result = validateDomain(domain); + if (result !== true) this.error(result); } - // Print success message and instructions - this.log(""); - this.log(chalk.green("TLS configuration added successfully")); - this.log(""); - this.log("Created:"); - this.log(" - Caddyfile"); - this.log(" - .env.example.tls"); - this.log(""); + let appPort = flags["app-port"]; + // Only prompt if the user didn't pass --app-port at all (default is "3000") + // Since oclif always provides the default, we use the default directly + const portResult = validatePort(appPort); + if (portResult !== true) this.error(portResult); - this.log("To enable TLS:"); + const acmeStaging = + flags["acme-staging"] !== undefined + ? flags["acme-staging"] + : await confirm({ + message: + "Use Let's Encrypt staging? (recommended for first deploy to avoid rate limits)", + default: true, + }); + + const enableCaddyLogs = + flags["caddy-logs"] !== undefined + ? flags["caddy-logs"] + : await confirm({ + message: "Enable Caddy debug logs?", + default: false, + }); + + // Show summary this.log(""); - this.log("1. Add TLS variables to .env:"); - this.log(" cat .env.example.tls >> .env"); + this.log(chalk.bold("TLS Configuration:")); + this.log(` Domain: ${domain.trim()}`); + this.log(` App port: ${appPort.trim()}`); + this.log(` ACME staging: ${acmeStaging}`); + this.log(` Caddy logs: ${enableCaddyLogs}`); this.log(""); - this.log("2. Configure required variables:"); - this.log(" DOMAIN=yourdomain.com"); + // Only ask for confirmation in interactive mode (no --domain flag) + if (!flags.domain) { + const confirmed = await confirm({ + message: "Write these settings to .env?", + default: true, + }); + + if (!confirmed) { + this.log("Cancelled."); + return; + } + } + + const vars = { + domain: domain.trim(), + appPort: appPort.trim(), + acmeStaging, + enableCaddyLogs, + }; + + // Append to .env + const envBlock = getTlsEnvBlock(vars); + fs.appendFileSync(envPath, envBlock, { mode: 0o644 }); + this.log(`Updated .env`); + + // Append to .env.example (with placeholders, skip if already has DOMAIN) + const envExamplePath = path.join(cwd, ".env.example"); + if (!envFileHasTlsConfig(envExamplePath)) { + fs.appendFileSync(envExamplePath, TLS_ENV_EXAMPLE_BLOCK, { mode: 0o644 }); + this.log(`Updated .env.example`); + } + + // Print next steps this.log(""); - this.log(" For first deployment (recommended):"); - this.log(" ENABLE_CADDY_LOGS=true"); - this.log(" ACME_STAGING=true"); + this.log(chalk.green("TLS configured successfully")); this.log(""); - - this.log("3. Set up DNS A record pointing to instance IP"); + this.log("Next steps:"); + this.log(""); + this.log("1. Set up DNS A record pointing to your instance IP"); this.log(" Run 'ecloud compute app list' to get IP address"); this.log(""); - - this.log("4. Upgrade:"); - this.log(" ecloud compute app upgrade"); + this.log("2. Deploy or upgrade:"); + this.log(" ecloud compute app deploy # new app"); + this.log(" ecloud compute app upgrade # existing app"); this.log(""); - this.log("Note: Let's Encrypt rate limit is 5 certificates/week per domain"); - this.log(" To switch staging -> production: set ACME_STAGING=false"); - this.log(" If cert exists, use ACME_FORCE_ISSUE=true once to replace"); + if (acmeStaging) { + this.log(chalk.yellow("Note: ACME_STAGING is enabled (recommended for first deploy)")); + this.log("Once verified, switch to production certs:"); + this.log(" 1. Set ACME_STAGING=false in .env"); + this.log(" 2. Set ACME_FORCE_ISSUE=true in .env (one-time)"); + this.log(" 3. Run: ecloud compute app upgrade"); + this.log(""); + } + + this.log("Let's Encrypt rate limit: 5 certificates/week per domain"); } } diff --git a/packages/cli/src/commands/compute/app/deploy.ts b/packages/cli/src/commands/compute/app/deploy.ts index 20381930..fc805c7c 100644 --- a/packages/cli/src/commands/compute/app/deploy.ts +++ b/packages/cli/src/commands/compute/app/deploy.ts @@ -142,6 +142,10 @@ export default class AppDeploy extends Command { required: false, env: "ECLOUD_BUILD_CADDYFILE", }), + force: Flags.boolean({ + description: "Skip all confirmation prompts", + default: false, + }), }; async run() { @@ -450,7 +454,7 @@ export default class AppDeploy extends Command { } this.log(`\nEstimated transaction cost: ${chalk.cyan(finalTx.maxCostEth)} ETH`); - if (isMainnet(environmentConfig)) { + if (isMainnet(environmentConfig) && !flags.force) { const confirmed = await confirm(`Continue with deployment?`); if (!confirmed) { this.log(`\n${chalk.gray(`Deployment cancelled`)}`); diff --git a/packages/cli/src/commands/compute/app/start.ts b/packages/cli/src/commands/compute/app/start.ts index 8d99cd14..78d0ddf8 100644 --- a/packages/cli/src/commands/compute/app/start.ts +++ b/packages/cli/src/commands/compute/app/start.ts @@ -1,4 +1,4 @@ -import { Command, Args } from "@oclif/core"; +import { Command, Args, Flags } from "@oclif/core"; import { createComputeClient } from "../../../client"; import { commonFlags, applyTxOverrides } from "../../../flags"; import { @@ -25,6 +25,10 @@ export default class AppLifecycleStart extends Command { static flags = { ...commonFlags, + force: Flags.boolean({ + description: "Skip all confirmation prompts", + default: false, + }), }; async run() { @@ -77,7 +81,7 @@ export default class AppLifecycleStart extends Command { } // On mainnet, prompt for confirmation with cost - if (isMainnet(environmentConfig)) { + if (isMainnet(environmentConfig) && !flags.force) { const confirmed = await confirm( `This will cost up to ${finalTx.maxCostEth} ETH. Continue?`, ); diff --git a/packages/cli/src/commands/compute/app/stop.ts b/packages/cli/src/commands/compute/app/stop.ts index 5d6a36ad..b4e86e7c 100644 --- a/packages/cli/src/commands/compute/app/stop.ts +++ b/packages/cli/src/commands/compute/app/stop.ts @@ -1,4 +1,4 @@ -import { Command, Args } from "@oclif/core"; +import { Command, Args, Flags } from "@oclif/core"; import { createComputeClient } from "../../../client"; import { commonFlags, applyTxOverrides } from "../../../flags"; import { @@ -25,6 +25,10 @@ export default class AppLifecycleStop extends Command { static flags = { ...commonFlags, + force: Flags.boolean({ + description: "Skip all confirmation prompts", + default: false, + }), }; async run() { @@ -77,7 +81,7 @@ export default class AppLifecycleStop extends Command { } // On mainnet, prompt for confirmation with cost - if (isMainnet(environmentConfig)) { + if (isMainnet(environmentConfig) && !flags.force) { const confirmed = await confirm( `This will cost up to ${finalTx.maxCostEth} ETH. Continue?`, ); diff --git a/packages/cli/src/commands/compute/app/upgrade.ts b/packages/cli/src/commands/compute/app/upgrade.ts index 4f00caf8..e19dcbd1 100644 --- a/packages/cli/src/commands/compute/app/upgrade.ts +++ b/packages/cli/src/commands/compute/app/upgrade.ts @@ -120,6 +120,10 @@ export default class AppUpgrade extends Command { required: false, env: "ECLOUD_BUILD_CADDYFILE", }), + force: Flags.boolean({ + description: "Skip all confirmation prompts", + default: false, + }), }; async run() { @@ -383,7 +387,7 @@ export default class AppUpgrade extends Command { } this.log(`\nEstimated transaction cost: ${chalk.cyan(finalTx.maxCostEth)} ETH`); - if (isMainnet(environmentConfig)) { + if (isMainnet(environmentConfig) && !flags.force) { const confirmed = await confirm(`Continue with upgrade?`); if (!confirmed) { this.log(`\n${chalk.gray(`Upgrade cancelled`)}`); diff --git a/packages/cli/src/templates/tls/templates.ts b/packages/cli/src/templates/tls/templates.ts index 03c1d846..e1c1aa2b 100644 --- a/packages/cli/src/templates/tls/templates.ts +++ b/packages/cli/src/templates/tls/templates.ts @@ -11,27 +11,35 @@ export function getCaddyfileTemplate(): string { return caddyfileTemplate; } +export interface TlsEnvVars { + domain: string; + appPort: string; + acmeStaging: boolean; + enableCaddyLogs: boolean; +} + /** - * Embedded .env.example.tls content - * (embedded directly since .env files are gitignored) + * Generate the TLS env block with user-provided values for .env */ -export const ENV_EXAMPLE_TLS = `# TLS Configuration -# Set these variables to enable TLS for your application - -# Your domain name (required for TLS) -DOMAIN=yourdomain.com - -# Port your application listens on -APP_PORT=3000 - -# Enable Caddy debug logs -ENABLE_CADDY_LOGS=false - -# Use Let's Encrypt staging environment (for testing) -# Set to true to avoid rate limits during development -ACME_STAGING=false - -# Force certificate reissue even if a valid one exists -# Useful when you need to update SANs or force a renewal +export function getTlsEnvBlock(vars: TlsEnvVars): string { + return ` +# TLS Configuration +DOMAIN=${vars.domain} +APP_PORT=${vars.appPort} +ENABLE_CADDY_LOGS=${vars.enableCaddyLogs} +ACME_STAGING=${vars.acmeStaging} ACME_FORCE_ISSUE=false `; +} + +/** + * Placeholder TLS block for .env.example + */ +export const TLS_ENV_EXAMPLE_BLOCK = ` +# TLS Configuration +# DOMAIN=yourdomain.com +# APP_PORT=3000 +# ENABLE_CADDY_LOGS=false +# ACME_STAGING=false +# ACME_FORCE_ISSUE=false +`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ecea0013..e0e552fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: '@oclif/core': specifier: ^4.8.0 version: 4.8.0 + '@oclif/plugin-autocomplete': + specifier: ^3.2.40 + version: 3.2.40 axios: specifier: ^1.13.2 version: 1.13.2 @@ -664,6 +667,10 @@ packages: resolution: {integrity: sha512-jteNUQKgJHLHFbbz806aGZqf+RJJ7t4gwF4MYa8fCwCxQ8/klJNWc0MvaJiBebk7Mc+J39mdlsB4XraaCKznFw==} engines: {node: '>=18.0.0'} + '@oclif/plugin-autocomplete@3.2.40': + resolution: {integrity: sha512-HCfDuUV3l5F5Wz7SKkaoFb+OMQ5vKul8zvsPNgI0QbZcQuGHmn3svk+392wSfXboyA1gq8kzEmKPAoQK6r6UNw==} + engines: {node: '>=18.0.0'} + '@pkgr/core@0.2.9': resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -2751,6 +2758,15 @@ snapshots: wordwrap: 1.0.0 wrap-ansi: 7.0.0 + '@oclif/plugin-autocomplete@3.2.40': + dependencies: + '@oclif/core': 4.8.0 + ansis: 3.17.0 + debug: 4.4.3(supports-color@8.1.1) + ejs: 3.1.10 + transitivePeerDependencies: + - supports-color + '@pkgr/core@0.2.9': {} '@posthog/core@1.9.1':