Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/create-astro-fleet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ bunx create-astro-fleet ./my-fleet --domain acme.com --preset saas

### `init`

- `--template <source>` — giget source, default `github:indivar/astro-fleet`
- `--template <source>` — giget source, default `github:indivar/astro-fleet#v2.2.0` (pinned to a specific fleet release tag; bumped inside the CLI when a new template ships)
- `--domain <name>` — skip the first-site domain prompt
- `--preset <name>` — skip the preset prompt
- `--keep-demos` — keep the three demo sites as reference
- `--no-install` — skip dependency install (reserved; not yet implemented)
- `--install` — install dependencies after scaffold without prompting
- `--no-install` — skip dependency install without prompting

When neither `--install` nor `--no-install` is passed, the CLI asks interactively. The package manager is auto-detected from `npm_config_user_agent` (so `bunx create-astro-fleet` uses Bun, `npm create astro-fleet` uses npm, etc.), falling back to Bun.

### `add`

Expand Down
5 changes: 5 additions & 0 deletions packages/create-astro-fleet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"description": "Scaffold a new Astro Fleet monorepo or add a site to an existing one.",
"license": "MIT",
"author": "Varinder Singh (https://github.com/indivar)",
"type": "module",
"bin": {
"create-astro-fleet": "./bin/create-astro-fleet.mjs"
Expand All @@ -24,6 +25,10 @@
"url": "https://github.com/indivar/astro-fleet.git",
"directory": "packages/create-astro-fleet"
},
"bugs": {
"url": "https://github.com/indivar/astro-fleet/issues"
},
"homepage": "https://github.com/indivar/astro-fleet/tree/main/packages/create-astro-fleet#readme",
"engines": {
"node": ">=18.20.0"
},
Expand Down
5 changes: 4 additions & 1 deletion packages/create-astro-fleet/src/args.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
const BOOLEAN_FLAGS = new Set(['keep-demos', 'install', 'no-install', 'help', 'version']);

export function parseArgs(argv) {
const positional = [];
const flags = {};
for (let i = 0; i < argv.length; i++) {
const token = argv[i];
if (token.startsWith('--')) {
const key = token.slice(2);
const isBoolean = BOOLEAN_FLAGS.has(key) || key.startsWith('no-');
const next = argv[i + 1];
if (next === undefined || next.startsWith('--')) {
if (isBoolean || next === undefined || next.startsWith('--')) {
flags[key] = true;
} else {
flags[key] = next;
Expand Down
5 changes: 3 additions & 2 deletions packages/create-astro-fleet/src/help.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ ${pc.bold('Commands:')}
help Show this help

${pc.bold('Options (init):')}
--template <source> giget template source (default: github:indivar/astro-fleet)
--template <source> giget template source (default: github:indivar/astro-fleet#v2.2.0)
--preset <name> corporate | saas | warm (skips prompt)
--domain <name> first site domain (skips prompt)
--keep-demos keep the three demo sites as reference
--no-install skip dependency install
--install install dependencies after scaffold (skips prompt)
--no-install skip dependency install (skips prompt)

${pc.bold('Options (add):')}
--preset <name> corporate | saas | warm (default: corporate)
Expand Down
86 changes: 79 additions & 7 deletions packages/create-astro-fleet/src/init.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@ import pc from 'picocolors';
import { downloadTemplate } from 'giget';
import { resolve, join, basename } from 'node:path';
import { access, rm, readFile, writeFile, readdir } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { parseArgs, validateDomain, validatePreset, PRESETS } from './args.mjs';
import { scaffoldSite } from './scaffold.mjs';

const DEFAULT_TEMPLATE = 'github:indivar/astro-fleet';
// The fleet template release this CLI ships with.
// Bump to match the latest https://github.com/indivar/astro-fleet/releases tag
// when you cut a new template release.
const TEMPLATE_VERSION = 'v2.2.0';
const DEFAULT_TEMPLATE = `github:indivar/astro-fleet#${TEMPLATE_VERSION}`;

const DEMO_SITES = ['flux-analytics.com', 'meridian-advisory.com', 'olive-and-vine.com'];
const SUPPORTED_PMS = ['bun', 'pnpm', 'yarn', 'npm'];

async function pathExists(p) {
try {
Expand All @@ -27,6 +34,34 @@ async function isEmpty(dir) {
}
}

function detectPackageManager() {
const ua = process.env.npm_config_user_agent || '';
const name = ua.split(' ')[0]?.split('/')[0];
if (SUPPORTED_PMS.includes(name)) return name;
return 'bun';
}

function devCommand(pm) {
return pm === 'npm' ? 'npm run dev' : `${pm} dev`;
}

function runInstall(pm, cwd) {
return new Promise((resolvePromise, rejectPromise) => {
const child = spawn(pm, ['install'], { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
let stderr = '';
child.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
child.on('error', rejectPromise);
child.on('close', (code) => {
if (code === 0) return resolvePromise();
const err = new Error(`${pm} install exited with code ${code}`);
err.stderr = stderr;
rejectPromise(err);
});
});
}

export async function init(argv) {
const { positional, flags } = parseArgs(argv);

Expand Down Expand Up @@ -102,6 +137,22 @@ export async function init(argv) {
keepDemos = choice;
}

const pm = detectPackageManager();

let doInstall;
if (flags['no-install']) {
doInstall = false;
} else if (flags.install) {
doInstall = true;
} else {
const choice = await p.confirm({
message: `Install dependencies with ${pm} now?`,
initialValue: true,
});
if (p.isCancel(choice)) throw new Error('User cancelled.');
doInstall = choice;
}

const template = flags.template || DEFAULT_TEMPLATE;

const spin = p.spinner();
Expand Down Expand Up @@ -142,12 +193,33 @@ export async function init(argv) {
throw err;
}

p.outro(
`${pc.green('✓')} Fleet ready. Next:\n` +
` ${pc.cyan(`cd ${targetArg}`)}\n` +
` ${pc.cyan('bun install')}\n` +
` ${pc.cyan(`bun run dev --filter=${domain}`)}`
);
let installed = false;
if (doInstall) {
const installSpin = p.spinner();
installSpin.start(`Installing dependencies with ${pm}`);
try {
await runInstall(pm, targetDir);
installSpin.stop('Installed dependencies');
installed = true;
} catch (err) {
installSpin.stop(pc.red(`${pm} install failed`));
if (err.stderr) {
const tail = err.stderr.trim().split('\n').slice(-10).join('\n');
p.log.error(tail);
}
p.log.warn(`Run \`cd ${targetArg} && ${pm} install\` manually once the error above is resolved.`);
}
}

const nextSteps = [
` ${pc.cyan(`cd ${targetArg}`)}`,
installed ? null : ` ${pc.cyan(`${pm} install`)}`,
` ${pc.cyan(`${devCommand(pm)} --filter=${domain}`)}`,
]
.filter(Boolean)
.join('\n');

p.outro(`${pc.green('✓')} Fleet ready. Next:\n${nextSteps}`);
}

async function renameRootPackage(targetDir, newName) {
Expand Down
Loading