From 83b7573bd12db5468c729cf90215a7026263cd35 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 23 Sep 2026 11:14:36 +0800 Subject: [PATCH 1/2] fix(install): honor --non-interactive, create api/.env, resolve --directory - --non-interactive no longer runs the four core enquirer prompts (host, environment, directory, app name); without a TTY they blocked or threw. Values come from flags or their defaults, and --app-name is a new flag. - --environment is validated (development | production) on the flag path. - --directory is path.resolve()d so relative and Git Bash style paths work for the file writes and the cwd handed to docker compose. - api/.env is created (empty, comment header) before docker compose up. docker-compose.yml bind-mounts it; when missing, Docker created a directory at that path and the API could not boot. If a directory is already there the installer explains and exits 1. - README documents --app-name, --non-interactive and the api/.env behaviour. --- README.md | 9 ++++++++ index.js | 67 +++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 405c534..d52a3df 100644 --- a/README.md +++ b/README.md @@ -159,12 +159,21 @@ flb install-fleetbase - `--host `: Host or IP address to bind to (default: `localhost`) - `--environment `: Environment: `development` or `production` (default: `development`) - `--directory `: Installation directory (default: current directory) +- `--app-name `: Application name (default: `Fleetbase`) +- `--non-interactive`: Skip every prompt and use the flags above plus safe defaults (CI/CD, scripted installs) + +The installer creates an empty `api/.env` (bind-mounted by `docker-compose.yml`) when one does not exist. **Example:** ```bash flb install-fleetbase --host 0.0.0.0 --environment production --directory /opt/fleetbase ``` +**Non-interactive example:** +```bash +flb install-fleetbase --non-interactive --directory /opt/fleetbase +``` + ### Publishing a Extension To publish a extension, navigate to the extension directory and run: diff --git a/index.js b/index.js index 8692f5c..4776753 100755 --- a/index.js +++ b/index.js @@ -1112,6 +1112,36 @@ function buildEnvBlock(vars, indent = 6) { .join('\n'); } +/** + * Make sure api/.env exists before `docker compose up`. + * docker-compose.yml bind-mounts ./api/.env into the application container; when the + * file is missing Docker creates a *directory* at that path and Laravel cannot boot. + * Values set in docker-compose.override.yml take precedence over this file, so an + * empty file with a comment header is the correct default. + * @param {string} directory resolved installation directory + */ +async function ensureApiEnvFile(directory) { + const envPath = path.join(directory, 'api', '.env'); + if (await fs.pathExists(envPath)) { + const stat = await fs.stat(envPath); + if (stat.isDirectory()) { + console.error(`\n✖ ${envPath} is a directory (left behind by an earlier "docker compose up" before the file existed).`); + console.error(' Remove it and re-run the installer.'); + process.exit(1); + } + console.log('✔ api/.env already present'); + return; + } + await fs.ensureDir(path.dirname(envPath)); + await fs.writeFile(envPath, [ + '# Fleetbase API environment overrides.', + '# Runtime configuration comes from docker-compose.override.yml and takes precedence', + '# over this file; add per-host secrets or extra overrides here.', + '', + ].join('\n')); + console.log('✔ api/.env created'); +} + // Command to install Fleetbase via Docker async function installFleetbaseCommand(options) { const crypto = require('crypto'); @@ -1156,8 +1186,15 @@ async function installFleetbaseCommand(options) { console.log('✔ Pre-flight checks complete\n'); try { + const nonInteractive = !!options.nonInteractive; + if (nonInteractive) { + console.log(' ℹ Non-interactive mode: no prompts; flags and safe defaults are used.'); + } + // ── Step 1: Core installation parameters ──────────────────────────── - const coreAnswers = await prompt([ + // Without a TTY (CI, piped stdin) enquirer would block or throw, so in + // non-interactive mode every core value comes from a flag or its default. + const coreAnswers = nonInteractive ? {} : await prompt([ { type: 'input', name: 'host', @@ -1186,14 +1223,21 @@ async function installFleetbaseCommand(options) { type: 'input', name: 'appName', message: 'Application name:', - initial: 'Fleetbase', + initial: options.appName || 'Fleetbase', }, ]); - const host = options.host || coreAnswers.host; - const environment = options.environment || coreAnswers.environment; - const directory = options.directory || coreAnswers.directory; - const appName = coreAnswers.appName || 'Fleetbase'; + const host = options.host || coreAnswers.host || 'localhost'; + const environment = options.environment || coreAnswers.environment || 'development'; + const appName = options.appName || coreAnswers.appName || 'Fleetbase'; + // Resolve so relative paths (and Git Bash style paths on Windows) work for both + // the file writes below and the `cwd` handed to docker compose. + const directory = path.resolve(options.directory || coreAnswers.directory || process.cwd()); + + if (!['development', 'production'].includes(environment)) { + console.error(`\n✖ Invalid environment "${environment}". Use "development" or "production".`); + process.exit(1); + } const useHttps = environment === 'production'; const appDebug = environment !== 'production'; @@ -1201,11 +1245,6 @@ async function installFleetbaseCommand(options) { const schemeApi = useHttps ? 'https' : 'http'; const schemeConsole = useHttps ? 'https' : 'http'; const isLocalhost = host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1'; - const nonInteractive = !!options.nonInteractive; - - if (nonInteractive) { - console.log(' ℹ Non-interactive mode: all optional steps will use safe defaults.'); - } // ── Step 2: Clone repo if needed ───────────────────────────────────── const dockerComposePath = path.join(directory, 'docker-compose.yml'); @@ -1557,6 +1596,9 @@ ${buildEnvBlock(dbEnvVars)} console.log('✔ Console configuration files updated'); + // ── Step 10b: Ensure api/.env exists (bind-mounted by docker-compose.yml) ── + await ensureApiEnvFile(directory); + // ── Step 11: Start containers ───────────────────────────────────────── console.log('\n⏳ Starting Fleetbase containers...'); console.log(' This may take a few minutes on first run...\n'); @@ -1998,7 +2040,8 @@ program .option('--host ', 'Host or IP address to bind to (default: localhost)') .option('--environment ', 'Environment: development or production (default: development)') .option('--directory ', 'Installation directory (default: current directory)') - .option('--non-interactive', 'Skip all optional prompts and use safe defaults (useful for CI/CD)') + .option('--app-name ', 'Application name (default: Fleetbase)') + .option('--non-interactive', 'Skip every prompt; use flags and safe defaults (useful for CI/CD)') .action(installFleetbaseCommand); program From 6cb6a7a60327ecdca551b4c9542fad5bc3976b95 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 23 Sep 2026 11:14:37 +0800 Subject: [PATCH 2/2] bump version to v0.0.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae12546..9f629a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fleetbase/cli", - "version": "0.0.4", + "version": "0.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fleetbase/cli", - "version": "0.0.4", + "version": "0.0.7", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^1.7.3", diff --git a/package.json b/package.json index ab1483a..540b6fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fleetbase/cli", - "version": "0.0.6", + "version": "0.0.7", "description": "CLI tool for managing Fleetbase Extensions", "repository": "https://github.com/fleetbase/fleetbase", "license": "AGPL-3.0-or-later",