Skip to content

Feat: CLI tool for scaffolding new Accessible Astro projects with interactive configuration prompts#184

Merged
markteekman merged 12 commits into
mainfrom
feat/cli-tool
May 2, 2026
Merged

Feat: CLI tool for scaffolding new Accessible Astro projects with interactive configuration prompts#184
markteekman merged 12 commits into
mainfrom
feat/cli-tool

Conversation

@markteekman
Copy link
Copy Markdown
Member

@markteekman markteekman commented Apr 25, 2026

Summary by CodeRabbit

  • New Features

    • Added a CLI-based project scaffolder (npm create) with selectable presets (full, blog, portfolio, minimal, barebones); shows next steps after scaffold.
    • Component heading levels now configurable (h2–h6).
  • Documentation

    • New and expanded README guidance for the CLI workflow, interactive prompts, and post‑scaffold behavior.
  • Chores

    • Monorepo/workspace support added and package version bumped; updated ignore rules for build artifacts.
  • Tests

    • New unit and end-to-end tests validating scaffolds and CLI output.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 25, 2026

Deploy Preview for accessible-astro-starter-incluud ready!

Name Link
🔨 Latest commit 3d2ef41
🔍 Latest deploy log https://app.netlify.com/projects/accessible-astro-starter-incluud/deploys/69f5cd58bb5a1e00087fd34e
😎 Deploy Preview https://deploy-preview-184--accessible-astro-starter-incluud.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new monorepo-scoped CLI package create-accessible-astro-starter that scaffolds Astro projects across presets with interactive prompts, templates, FS utilities, build/test tooling, and E2E/unit tests; also minor top-level workspace and README updates and a small component prop tweak.

Changes

Create-accessible-astro-starter CLI & monorepo

Layer / File(s) Summary
Monorepo / Metadata
package.json, .gitignore, README.md
Enable workspaces packages/*, bump version to 5.2.0, add workspace scripts (build/test/create), ignore packages/*/dist and packages/*/dist-test, and document npm create workflow in README.
Package manifest & TS configs
packages/create-accessible-astro-starter/package.json, .../tsconfig.json, .../tsconfig.test.json
New ESM CLI package manifest with bin mapping to dist/src/index.js, runtime/dev deps, node >=22.12.0, and TS build/test configurations (dist, dist-test).
Build/run helpers
packages/.../scripts/run-local.mjs, .../scripts/clean-output.mjs
Local run helper to build and execute the compiled CLI with env injection and a cleanup script to remove build outputs.
CLI runtime & output
packages/.../src/index.ts, packages/.../src/cli.ts, packages/.../src/cli-output.ts
Add async CLI entrypoint that runs exported run(argv); orchestrates parsing, prompts, resolving options, manifest construction, scaffold invocation, spinner and formatted intro/outro output with color/gradient handling.
Argument parsing & types
packages/.../src/options.ts, packages/.../src/types.ts
CLI flag parsing, validation/alias for preset, --yes handling, getDefaultLauncher, and exported TS types (PRESETS, Preset, ParsedFlags, ResolvedOptions, ProjectManifest).
Preset manifest builder
packages/.../src/presets.ts
Compute keep/omit flags and concrete pathsToDelete per selected preset and launcher inclusion; returns ProjectManifest.
Scaffold implementation
packages/.../src/scaffold.ts
Materialize template (local override or download), remove/keep files per manifest, write generated files (config, pages, README), rewrite package.json (strip launcher/MDX deps when applicable), and prune empty directories.
Template generators
packages/.../src/templates.ts
Large set of string template generators for configs, navigation, header/footer, pages, launcher config, and README conditioned on preset and launcher.
Utilities
packages/.../src/utils.ts
String helpers (slugify, derive site name, preset label, escaping) and async FS helpers (exists, ensure directory, copy template, read/write JSON/text, remove paths, remove named files, prune empty dirs).
Tests: unit & e2e
packages/.../test/*, packages/.../test/e2e.ts, packages/.../test/scaffold.test.ts
Unit tests for CLI output; comprehensive scaffold tests validating generated files and conditional removals; e2e runner builds each preset (launcher on/off) and asserts dist/ produced.

Unrelated component tweak

Layer / File(s) Summary
Component prop change
src/components/Feature.astro
Add optional `level?: 'h2'

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "create-accessible-astro-starter CLI"
    participant Parser as "parseCliArgs"
    participant Prompter as "Prompts (clack)"
    participant Resolver as "Options Resolver"
    participant Manifest as "buildManifest"
    participant Scaffold as "scaffoldProject"
    participant FS as "File System / Template"

    User->>CLI: run `npm create accessible-astro-starter`
    CLI->>Parser: parseCliArgs(argv)
    Parser-->>CLI: ParsedFlags
    alt required fields missing and --yes not set
        CLI->>Prompter: prompt for targetDir, siteName, preset, launcher
        Prompter-->>CLI: responses
    end
    CLI->>Resolver: derive siteId & resolve options
    Resolver-->>CLI: ResolvedOptions
    CLI->>Manifest: buildManifest(preset, includeLauncher)
    Manifest-->>CLI: ProjectManifest (pathsToDelete)
    CLI->>Scaffold: scaffoldProject(options, manifest)
    Scaffold->>FS: materialize template (download/copy)
    FS-->>Scaffold: template ready
    Scaffold->>FS: remove paths per manifest & write generated files
    FS-->>Scaffold: updated project files
    Scaffold-->>CLI: scaffold complete
    CLI->>User: print success message & next steps
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 Hop! I scaffold fields of code with care,

prompts and presets braid pages in the air,
templates hum, files fall into place,
tests hop through hoops with gentle grace,
a tiny rabbit cheers: a site sprouts — hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: a new CLI tool for scaffolding Accessible Astro projects with interactive configuration prompts, which aligns with the substantial new package and tooling added throughout the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli-tool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@markteekman markteekman changed the title Feat/cli tool Feat: CLI tool for scaffolding new Accessible Astro projects with interactive configuration prompts Apr 25, 2026
const manifest = buildManifest(options)

await scaffoldProject(options, manifest)
await runCommand('npm', ['install'], targetDir)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Windows npm invocation can fail

runCommand('npm', ...) assumes npm is on PATH; on Windows the executable is typically npm.cmd, so this call can fail in CI runners. Consider resolving the npm command based on process.platform (as in the local run script) or using process.execPath with npm's JS entrypoint to keep the e2e test cross-platform.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Apr 25, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (1 files)
  • package-lock.json

Reviewed by gpt-5.2-codex-20260114 · 687,615 tokens

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (8)
package.json (1)

67-71: Confirm exact-version pinning of Tailwind is intentional.

tailwindcss and @tailwindcss/vite are now pinned to exact 4.2.2 (no caret), while every other dep still uses caret ranges. If the goal is to avoid surprise Tailwind v4 minor regressions, consider applying the same policy consistently (e.g., pin both Tailwind packages and document the reason), or revert to ^4.2.2 for parity. As-is, the inconsistency may confuse future contributors running npm update.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 67 - 71, The package.json currently pins
"tailwindcss" and "@tailwindcss/vite" to exact version "4.2.2" while other deps
use caret ranges; decide and apply a consistent policy: either change both
"tailwindcss" and "@tailwindcss/vite" to use caret ranges "^4.2.2" for parity
with other deps, or pin both packages exactly and add a brief comment in repo
docs (e.g., README or CONTRIBUTING) explaining why Tailwind is locked; update
the two package names "tailwindcss" and "@tailwindcss/vite" accordingly so they
match the chosen policy.
packages/create-accessible-astro-starter/src/presets.ts (1)

83-90: Redundant keepThankYouPage deletion branch.

Since keepThankYouPage = keepContactPage (line 36), the second branch is unreachable as a distinct case — src/pages/thank-you.astro is already added to pathsToDelete in the !keepContactPage branch. Either remove the dead block or decouple the two flags if a future preset is expected to keep contact but drop thank-you.

♻️ Proposed simplification
   if (!keepContactPage) {
     pathsToDelete.add('src/pages/contact.astro')
     pathsToDelete.add('src/pages/thank-you.astro')
   }

-  if (!keepThankYouPage) {
-    pathsToDelete.add('src/pages/thank-you.astro')
-  }
-
   if (!keepMdx) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/presets.ts` around lines 83 -
90, The second conditional checking keepThankYouPage is redundant because
keepThankYouPage is set equal to keepContactPage; remove the entire if
(!keepThankYouPage) { pathsToDelete.add('src/pages/thank-you.astro') } block, or
if intended behavior is to allow independent control, decouple the flags by
changing where keepThankYouPage is assigned so it can differ from
keepContactPage and update callers/values accordingly; locate symbols
keepContactPage, keepThankYouPage, and pathsToDelete in presets.ts and either
delete the dead branch or adjust the flag assignment so thank-you removal can be
controlled separately.
packages/create-accessible-astro-starter/test/scaffold.test.ts (1)

26-33: Hoist the env var setup once and consider cleanup.

process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR = repoRoot is repeated in every test (and again inside the parameterized loop) and never restored. With node:test running sequentially today this is safe, but if concurrency: true is ever enabled — or another test in the same process expects this var to be unset — this becomes a hidden coupling.

Consider setting it once at module top (or via test.before) and clearing it in test.after, e.g.:

test.before(() => {
  process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR = repoRoot
})
test.after(() => {
  delete process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR
})

Also, none of the tests rm -rf their tempRoots — the OS cleans tmpdir() eventually, but locally these accumulate. Optional follow-up.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/test/scaffold.test.ts` around lines
26 - 33, Hoist repeated environment setup by moving the assignment to
process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR out of individual tests and
into a module-level test.before hook (and remove the per-test assignment inside
tests like the one calling scaffoldProject with options from createOptions and
buildManifest); add a test.after hook to delete
process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR to restore global state, and
consider adding cleanup for temporary directories created via mkdtemp (e.g.,
remove tempRoot in a test.after or test.afterEach) so tests no longer leave tmp
files behind.
packages/create-accessible-astro-starter/src/scaffold.ts (1)

49-67: Template-ref coupling: CLI version → starter repo Git tag creates operational fragility.

getPublishedTemplateRef returns v${packageJson.version} (e.g., v5.1.0) and downloadTemplate requires that tag to exist on incluud/accessible-astro-starter. While the matching tag currently exists, this approach creates a tight coupling that relies on manual alignment during release—if the CLI is published before the Git tag is pushed, or if the tag naming scheme drifts, users will encounter confusing download failures.

Consider one of:

  • Documenting the release procedure to ensure the matching vX.Y.Z tag is always pushed before CLI publication.
  • Falling back to a known-good ref (e.g., main or the latest successful tag) on failure.
  • Pinning the template ref in the CLI's package.json explicitly rather than reusing the version field, so the two can be bumped independently.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/scaffold.ts` around lines 49 -
67, getPublishedTemplateRef currently builds a template ref from the CLI package
version and materializeTemplate always attempts to download that tag via
downloadTemplate (unless ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR is set), which
tightly couples CLI publishing to a matching repo tag; change
materializeTemplate to handle missing or mismatched tags by attempting a safe
fallback: call downloadTemplate with the version-based ref first (using
getPublishedTemplateRef), and on failure retry with a known-good ref such as
"main" or a configurable template ref from package.json (or env var), and
surface a clear error if all attempts fail; ensure the retry logic references
materializeTemplate, getPublishedTemplateRef, and downloadTemplate so
maintainers can find and update the fallback strategy or move the template ref
into package.json later.
packages/create-accessible-astro-starter/src/templates.ts (3)

1078-1241: Optional: tighten launcher blog/portfolio fetching paths.

Two small things in the generated LauncherConfig.astro:

  • Line 1090 checks if (!BLOG_API_URL) return [], but BLOG_API_URL has a non‑empty default in astro.config (line 222), so this guard is effectively dead in the default project. That's fine, but worth a comment in the generated code so users editing .env understand the contract.
  • Line 1104 builds href: \/blog/${slugify(truncatedTitle)}`` from a 4‑word title slug. If two upstream posts share the same first 4 words, both launcher items point to the same slug, and neither necessarily resolves to a real route. Consider also passing through a stable identifier from the API when available.

Both are nice‑to‑haves and don't block correctness.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/templates.ts` around lines 1078
- 1241, The generated launcher blog code (launcherBlogItems / truncateBlogTitle)
currently guards on BLOG_API_URL (which may have a default) but lacks a comment
explaining that contract and it builds hrefs using slugify(truncatedTitle) only,
which can collide; add an inline comment near the BLOG_API_URL check describing
that the env has a default and that users can override it via .env, and modify
the launcherBlogItems construction (in the async block that fetches BLOG_API_URL
and in the map that builds hrefs) to prefer a stable identifier from the API
response when present (e.g., post.id or post.slug) falling back to
slugify(truncatedTitle) otherwise; reference the symbols BLOG_API_URL,
launcherBlogItems, truncateBlogTitle and slugify when making the changes.

173-200: Minor: createFeatureCards body is interpolated raw into HTML.

card.title and card.body (line 177–178) are pasted directly into the generated HTML without escaping. Today the only callers (createMinimalIndex) pass hardcoded literals so this is safe, but if these helpers later accept user‑provided strings (e.g. from siteName or theme config), the same escaping issue raised on createHero / index pages will recur. Worth either documenting that the inputs must be HTML‑safe or running them through an escapeForHtml helper at the boundary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/templates.ts` around lines 173 -
200, createFeatureCards currently injects card.title and card.body directly into
HTML; update createFeatureCards to escape both values using the existing/ new
HTML-escaping utility (e.g., escapeForHtml) before interpolation so untrusted
input cannot break markup or inject scripts. Locate createFeatureCards and call
escapeForHtml on card.title and card.body (or add/rename helper if none exists)
to ensure safe output, and keep the rest of the template unchanged.

215-265: Empty envBlock leaves a stray blank line in astro.config.

When manifest.keepBlogEnv is false, envBlock is '' and the template still emits a blank line right before }). Cosmetic, but the generated config will have an empty line that Prettier will normalize away on first format. If you want the output clean as written, conditionally include the leading newline:

♻️ Suggested tweak
-  vite: viteConfig,
-${envBlock}
-})
+  vite: viteConfig,${envBlock ? `\n${envBlock}` : ''}
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/templates.ts` around lines 215 -
265, The template always injects envBlock (declared above, based on
manifest.keepBlogEnv) into the returned string with a leading newline, which
leaves a stray blank line when envBlock is ''. Update the return construction so
the leading newline is only included when envBlock is non-empty: either build
envBlock to include its own leading newline when manifest.keepBlogEnv is true,
or conditionally append `\n${envBlock}` in the template only when envBlock !==
''. Modify references in this file to the envBlock/manifest.keepBlogEnv logic
(where viteConfig and the final defineConfig string are assembled) so the
generated astro.config has no leftover blank line.
packages/create-accessible-astro-starter/src/utils.ts (1)

74-82: Optional: filter is path‑agnostic and can be too permissive.

filter(source) only checks the basename, so it will exclude .git / node_modules at any depth (good), but it won't exclude common build artifacts like dist, coverage, .cache, or .DS_Store that sometimes leak into a source directory. If the scaffolder is ever pointed at a developer working copy (rather than a curated published template), these would be copied verbatim.

Consider extending the deny‑list, or normalizing on isDirectory() for accuracy:

♻️ Suggested tightening
 export async function copyLocalTemplate(sourceDir: string, targetDir: string): Promise<void> {
   await cp(sourceDir, targetDir, {
     recursive: true,
     filter(source) {
       const name = basename(source)
-      return !['.git', 'node_modules'].includes(name)
+      return !['.git', 'node_modules', 'dist', '.cache', '.DS_Store'].includes(name)
     },
   })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/utils.ts` around lines 74 - 82,
The current copyLocalTemplate filter in copyLocalTemplate only checks basename
and is too permissive; update the cp filter to normalize the path and reject a
broader deny-list (e.g., dist, coverage, .cache, .DS_Store, .env, build) and
also distinguish dirs vs files by using fs.lstat or fs.stat (or the provided
filter's stats if available) so you can only exclude directory names for
directory artifacts while excluding specific file names like .DS_Store;
reference the copyLocalTemplate function, the cp call, the filter(source)
callback and basename usage when implementing these checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/create-accessible-astro-starter/README.md`:
- Around line 11-17: The "Local development" commands in README.md call
workspace-scoped scripts build:cli and test:cli that only exist in the repo
root; update this section to either (a) explicitly state these commands must be
run from the starter repo root (e.g., "From the starter repo root: npm run
build:cli && npm run test:cli") or (b) replace them with package-local
equivalents (npm run build and npm run test) and note that those run only within
the package; edit the README.md Local development block to include the chosen
clarification and reference the build:cli, test:cli (root-level) and build, test
(package-level) script names so contributors know where to run each command.

In `@packages/create-accessible-astro-starter/src/cli.ts`:
- Around line 132-148: Replace the unsafe startsWith logic for relativeDir by
using Node's path.relative to compute a correct relative path from process.cwd()
to options.targetDir (e.g., const rel = path.relative(process.cwd(),
options.targetDir)), normalize it to POSIX separators (replace backslashes with
'/'), default to '.' when rel is empty, and then use that value in the p.note
output (update the const relativeDir and keep using options.targetDir and p.note
as before); ensure you import path and handle the case where rel is '' -> '.' so
cd and display are correct.

In `@packages/create-accessible-astro-starter/src/templates.ts`:
- Around line 858-960: The template uses escapeForSingleQuotedString in
createHero to escape firstWord and remainingWords for inclusion in HTML/JSX
text, which produces backslash-escaped output (e.g. O\'Brien) and doesn't
HTML-escape special chars; add a new helper escapeForHtml in utils.ts that
replaces &, <, >, and " with their HTML entities, then update createHero to stop
calling escapeForSingleQuotedString and instead call escapeForHtml for firstWord
and the joined remainingWords (used in remainingTitle) as well as any other
interpolations emitted into HTML text nodes, keeping escapeForSingleQuotedString
only for actual JS single-quoted literal contexts.
- Around line 1243-1415: Generated Astro markup is vulnerable because
escapeForSingleQuotedString only handles backslash/apos and is used in
double‑quoted attributes and raw HTML text; create a small escapeForHtml helper
that escapes &, <, > and " (and optionally ') and replace usages of
escapeForSingleQuotedString in createFullIndex, createBlogIndex,
createPortfolioIndex, createMinimalIndex (the Heading interpolation),
createBarebonesIndex, and createAboutPage to call escapeForHtml for any value
interpolated into attributes or element text; also add a unit test in
scaffold.test.ts that generates pages with site names containing ', ", &, < to
assert the produced .astro is valid and contains the escaped text.
- Around line 396-576: The active-menu detection in setActiveMenuItem is using
currentPathname.includes(menuItem.pathname.replaceAll('/', '')) which produces
false positives; change the logic to compare pathname segments or exact paths
instead — for each menuItem (in setActiveMenuItem) normalize both
window.location.pathname and menuItem.pathname (ensure leading/trailing slashes
removed or added consistently), split them on '/' and compare the relevant
segment(s) or use strict equality for full-path matches (and treat '/' specially
for Home), so menuItem.classList.add('is-active') and aria-current are only set
when the normalized segments or exact path match.

In `@packages/create-accessible-astro-starter/src/utils.ts`:
- Around line 84-105: removeNamedFiles currently treats symlinks as regular
entries because entry.isDirectory() is false for symlinks; to avoid accidentally
removing symlinked targets, detect and skip symlinks before acting: in
removeNamedFiles (and the entries.map callback) check entry.isSymbolicLink() (or
use lstat on entryPath) and return/skip if true, otherwise keep the existing
behavior (recurse when entry.isDirectory(), compare entry.name to fileName and
call removePath). This ensures symlinks are not followed or unlinked by mistake.

In `@packages/create-accessible-astro-starter/test/e2e.ts`:
- Around line 26-58: The runCommand helper and main test need three fixes: (1)
make spawn cross-platform by resolving the executable as done in
scripts/run-local.mjs (e.g., use command + (process.platform === 'win32' ?
'.cmd' : '') or use cross-spawn) so calling runCommand('npm', ...) works on
Windows; (2) add an 'error' listener on the spawned child in runCommand to
reject the Promise on spawn errors (child.on('error', rejectPromise)); and (3)
remove the no-op assert.ok(true) in main and replace it with a real post-build
assertion (e.g., check that the built output directory like join(targetDir,
'dist') exists using fs.existsSync or fs.stat) to verify the build succeeded.
Ensure you update references to runCommand and main accordingly.

---

Nitpick comments:
In `@package.json`:
- Around line 67-71: The package.json currently pins "tailwindcss" and
"@tailwindcss/vite" to exact version "4.2.2" while other deps use caret ranges;
decide and apply a consistent policy: either change both "tailwindcss" and
"@tailwindcss/vite" to use caret ranges "^4.2.2" for parity with other deps, or
pin both packages exactly and add a brief comment in repo docs (e.g., README or
CONTRIBUTING) explaining why Tailwind is locked; update the two package names
"tailwindcss" and "@tailwindcss/vite" accordingly so they match the chosen
policy.

In `@packages/create-accessible-astro-starter/src/presets.ts`:
- Around line 83-90: The second conditional checking keepThankYouPage is
redundant because keepThankYouPage is set equal to keepContactPage; remove the
entire if (!keepThankYouPage) { pathsToDelete.add('src/pages/thank-you.astro') }
block, or if intended behavior is to allow independent control, decouple the
flags by changing where keepThankYouPage is assigned so it can differ from
keepContactPage and update callers/values accordingly; locate symbols
keepContactPage, keepThankYouPage, and pathsToDelete in presets.ts and either
delete the dead branch or adjust the flag assignment so thank-you removal can be
controlled separately.

In `@packages/create-accessible-astro-starter/src/scaffold.ts`:
- Around line 49-67: getPublishedTemplateRef currently builds a template ref
from the CLI package version and materializeTemplate always attempts to download
that tag via downloadTemplate (unless ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR is
set), which tightly couples CLI publishing to a matching repo tag; change
materializeTemplate to handle missing or mismatched tags by attempting a safe
fallback: call downloadTemplate with the version-based ref first (using
getPublishedTemplateRef), and on failure retry with a known-good ref such as
"main" or a configurable template ref from package.json (or env var), and
surface a clear error if all attempts fail; ensure the retry logic references
materializeTemplate, getPublishedTemplateRef, and downloadTemplate so
maintainers can find and update the fallback strategy or move the template ref
into package.json later.

In `@packages/create-accessible-astro-starter/src/templates.ts`:
- Around line 1078-1241: The generated launcher blog code (launcherBlogItems /
truncateBlogTitle) currently guards on BLOG_API_URL (which may have a default)
but lacks a comment explaining that contract and it builds hrefs using
slugify(truncatedTitle) only, which can collide; add an inline comment near the
BLOG_API_URL check describing that the env has a default and that users can
override it via .env, and modify the launcherBlogItems construction (in the
async block that fetches BLOG_API_URL and in the map that builds hrefs) to
prefer a stable identifier from the API response when present (e.g., post.id or
post.slug) falling back to slugify(truncatedTitle) otherwise; reference the
symbols BLOG_API_URL, launcherBlogItems, truncateBlogTitle and slugify when
making the changes.
- Around line 173-200: createFeatureCards currently injects card.title and
card.body directly into HTML; update createFeatureCards to escape both values
using the existing/ new HTML-escaping utility (e.g., escapeForHtml) before
interpolation so untrusted input cannot break markup or inject scripts. Locate
createFeatureCards and call escapeForHtml on card.title and card.body (or
add/rename helper if none exists) to ensure safe output, and keep the rest of
the template unchanged.
- Around line 215-265: The template always injects envBlock (declared above,
based on manifest.keepBlogEnv) into the returned string with a leading newline,
which leaves a stray blank line when envBlock is ''. Update the return
construction so the leading newline is only included when envBlock is non-empty:
either build envBlock to include its own leading newline when
manifest.keepBlogEnv is true, or conditionally append `\n${envBlock}` in the
template only when envBlock !== ''. Modify references in this file to the
envBlock/manifest.keepBlogEnv logic (where viteConfig and the final defineConfig
string are assembled) so the generated astro.config has no leftover blank line.

In `@packages/create-accessible-astro-starter/src/utils.ts`:
- Around line 74-82: The current copyLocalTemplate filter in copyLocalTemplate
only checks basename and is too permissive; update the cp filter to normalize
the path and reject a broader deny-list (e.g., dist, coverage, .cache,
.DS_Store, .env, build) and also distinguish dirs vs files by using fs.lstat or
fs.stat (or the provided filter's stats if available) so you can only exclude
directory names for directory artifacts while excluding specific file names like
.DS_Store; reference the copyLocalTemplate function, the cp call, the
filter(source) callback and basename usage when implementing these checks.

In `@packages/create-accessible-astro-starter/test/scaffold.test.ts`:
- Around line 26-33: Hoist repeated environment setup by moving the assignment
to process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR out of individual tests and
into a module-level test.before hook (and remove the per-test assignment inside
tests like the one calling scaffoldProject with options from createOptions and
buildManifest); add a test.after hook to delete
process.env.ACCESSIBLE_ASTRO_STARTER_TEMPLATE_DIR to restore global state, and
consider adding cleanup for temporary directories created via mkdtemp (e.g.,
remove tempRoot in a test.after or test.afterEach) so tests no longer leave tmp
files behind.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 48664536-90fe-4bee-bbf3-168b1f89efb5

📥 Commits

Reviewing files that changed from the base of the PR and between 90e6e78 and 4f8c4bf.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (27)
  • .gitignore
  • README.md
  • package.json
  • packages/create-accessible-astro-starter/README.md
  • packages/create-accessible-astro-starter/package.json
  • packages/create-accessible-astro-starter/scripts/clean-output.mjs
  • packages/create-accessible-astro-starter/scripts/run-local.mjs
  • packages/create-accessible-astro-starter/src/cli.ts
  • packages/create-accessible-astro-starter/src/index.ts
  • packages/create-accessible-astro-starter/src/options.ts
  • packages/create-accessible-astro-starter/src/presets.ts
  • packages/create-accessible-astro-starter/src/scaffold.ts
  • packages/create-accessible-astro-starter/src/templates.ts
  • packages/create-accessible-astro-starter/src/types.ts
  • packages/create-accessible-astro-starter/src/utils.ts
  • packages/create-accessible-astro-starter/test/e2e.ts
  • packages/create-accessible-astro-starter/test/scaffold.test.ts
  • packages/create-accessible-astro-starter/tsconfig.json
  • packages/create-accessible-astro-starter/tsconfig.test.json
  • src/components/CallToAction.astro
  • src/components/ColorContrast.astro
  • src/components/Feature.astro
  • src/components/Header.astro
  • src/components/Navigation.astro
  • src/components/ResponsiveToggle.astro
  • src/layouts/DefaultLayout.astro
  • src/pages/accessible-components.astro

Comment thread packages/create-accessible-astro-starter/README.md
Comment thread packages/create-accessible-astro-starter/src/cli.ts Outdated
Comment thread packages/create-accessible-astro-starter/src/templates.ts
Comment thread packages/create-accessible-astro-starter/src/templates.ts
Comment thread packages/create-accessible-astro-starter/src/templates.ts
Comment on lines +84 to +105
export async function removeNamedFiles(rootDir: string, fileName: string): Promise<void> {
if (!(await pathExists(rootDir))) {
return
}

const entries = await readdir(rootDir, { withFileTypes: true })

await Promise.all(
entries.map(async (entry) => {
const entryPath = resolve(rootDir, entry.name)

if (entry.isDirectory()) {
await removeNamedFiles(entryPath, fileName)
return
}

if (entry.name === fileName) {
await removePath(entryPath)
}
}),
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

removeNamedFiles doesn't handle symlinks.

entry.isDirectory() is false for symlinks (even those pointing to directories), so symlinked entries are compared as a file by name and unlinked if they match. Conversely, a symlink to a directory whose basename equals fileName will be removed even if its target isn't a regular file. For a scaffolder operating on a freshly copied template this is unlikely to bite, but worth a brief note since cp can preserve symlinks. No change needed unless the template tree may contain symlinks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/utils.ts` around lines 84 - 105,
removeNamedFiles currently treats symlinks as regular entries because
entry.isDirectory() is false for symlinks; to avoid accidentally removing
symlinked targets, detect and skip symlinks before acting: in removeNamedFiles
(and the entries.map callback) check entry.isSymbolicLink() (or use lstat on
entryPath) and return/skip if true, otherwise keep the existing behavior
(recurse when entry.isDirectory(), compare entry.name to fileName and call
removePath). This ensures symlinks are not followed or unlinked by mistake.

Comment thread packages/create-accessible-astro-starter/test/e2e.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 4: The root package.json was bumped to version "5.2.0" but the CLI
package still declares version "5.1.0"; update the "version" field in the
create-accessible-astro-starter package's package.json (the package named
"create-accessible-astro-starter") to match the root (5.2.0) so the CLI and
monorepo versions track together, then run your usual monorepo/versioning checks
to ensure consistency.
- Around line 67-71: Update the pinned Tailwind package versions to allow
patch-level updates by changing the exact versions for "tailwindcss" and
"@tailwindcss/vite" from "4.2.2" to a caret range "^4.2.2"; locate the entries
for the "tailwindcss" key in the root dependencies block and the
"@tailwindcss/vite" key in the "dependencies" section and replace their version
strings accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40612650-42c3-4b9a-af65-be45f841f0a6

📥 Commits

Reviewing files that changed from the base of the PR and between ec1f53e and 33d475c.

📒 Files selected for processing (1)
  • package.json

Comment thread package.json
Comment thread package.json Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/create-accessible-astro-starter/test/e2e.ts (1)

48-66: Note: temp directories are never cleaned up, and npm run build won't execute generated <script> blocks.

Two small caveats worth being aware of:

  1. Each preset × launcher combination creates a fresh mkdtemp directory (10 per run) and never removes it. On developer machines this leaks roughly 10 full node_modules trees per invocation. Consider wrapping the inner loop body in try/finally and rm(tempRoot, { recursive: true, force: true }) on completion (CI is fine either way).
  2. npm run build produces static HTML and emits client <script> blocks verbatim — it doesn't execute them. So a syntax error inside Navigation.astro's script (see the regex/template-literal issue flagged in templates.ts) won't fail this test. If you want this harness to catch script-level breakage, add a quick node --check (or a headless DOM smoke test) on the generated dist/_astro/*.js chunks.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/test/e2e.ts` around lines 48 - 66,
The test's main() currently creates temporary dirs with mkdtemp into tempRoot
and never removes them and it also doesn't validate generated client scripts;
wrap the inner loop body (the code that sets targetDir, options, manifest,
scaffoldProject, runCommand installs and build, and stat) in a try/finally so
you always call rm(tempRoot, { recursive: true, force: true }) in finally to
clean up, and after runCommand(npmCommand, ['run', 'build'], targetDir) add a
lightweight script-level check (e.g., run node --check against the generated JS
chunks under dist/_astro/*.js using runCommand and npmCommand or a direct
child_process invocation) so syntax errors in generated scripts (e.g., from
Navigation.astro) cause the test to fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/create-accessible-astro-starter/src/utils.ts`:
- Around line 115-141: The removeEmptyDirectories function currently deletes the
passed-in rootDir itself (via rm(rootDir, { recursive: true, force: true }))
which can remove the user's target project directory (options.targetDir) when
everything inside is pruned; update removeEmptyDirectories so it only removes
nested empty directories and never deletes the initial rootDir: change the
recursion to accept an additional flag/parameter (e.g., isRoot or stopAtRoot) or
add a wrapper so the top-level call from scaffold.ts passes a guard that
prevents rm from running on the original root, ensuring only child directories
(resolved via resolve(rootDir, entry.name) in the entries.map) are removed and
leave the root intact; also either remove or document the boolean return value
since scaffold.ts ignores it.

In `@packages/create-accessible-astro-starter/test/scaffold.test.ts`:
- Around line 252-255: The test helper expectMissing currently passes a plain
string as the second argument to assert.rejects, which Node treats as the
optional message overload; update expectMissing so assert.rejects receives a
proper error matcher (e.g., Error or a RegExp) as the second argument and the
message as the third. Locate the expectMissing function and change the call to
assert.rejects(() => readFile(file, 'utf8'), Error, `${relativePath} should be
removed`) (or use a suitable RegExp matcher) so the matcher is the second param
and the message is the third.

---

Nitpick comments:
In `@packages/create-accessible-astro-starter/test/e2e.ts`:
- Around line 48-66: The test's main() currently creates temporary dirs with
mkdtemp into tempRoot and never removes them and it also doesn't validate
generated client scripts; wrap the inner loop body (the code that sets
targetDir, options, manifest, scaffoldProject, runCommand installs and build,
and stat) in a try/finally so you always call rm(tempRoot, { recursive: true,
force: true }) in finally to clean up, and after runCommand(npmCommand, ['run',
'build'], targetDir) add a lightweight script-level check (e.g., run node
--check against the generated JS chunks under dist/_astro/*.js using runCommand
and npmCommand or a direct child_process invocation) so syntax errors in
generated scripts (e.g., from Navigation.astro) cause the test to fail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9374e68-5467-4030-b100-4e375e0c7f52

📥 Commits

Reviewing files that changed from the base of the PR and between 33d475c and 049ea3f.

📒 Files selected for processing (8)
  • package.json
  • packages/create-accessible-astro-starter/README.md
  • packages/create-accessible-astro-starter/package.json
  • packages/create-accessible-astro-starter/src/cli.ts
  • packages/create-accessible-astro-starter/src/templates.ts
  • packages/create-accessible-astro-starter/src/utils.ts
  • packages/create-accessible-astro-starter/test/e2e.ts
  • packages/create-accessible-astro-starter/test/scaffold.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/create-accessible-astro-starter/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/create-accessible-astro-starter/README.md
  • package.json

Comment on lines +115 to +141
export async function removeEmptyDirectories(rootDir: string): Promise<boolean> {
if (!(await pathExists(rootDir))) {
return true
}

const metadata = await stat(rootDir)
if (!metadata.isDirectory()) {
return false
}

const entries = await readdir(rootDir, { withFileTypes: true })

await Promise.all(
entries
.filter((entry) => entry.isDirectory())
.map((entry) => removeEmptyDirectories(resolve(rootDir, entry.name))),
)

const remainingEntries = await readdir(rootDir)

if (remainingEntries.length === 0) {
await rm(rootDir, { recursive: true, force: true })
return true
}

return false
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

removeEmptyDirectories will delete rootDir itself when fully empty.

When every nested entry has been pruned, line 136 calls rm(rootDir, { recursive: true, force: true }) on the root passed in. From scaffold.ts, that root is options.targetDir, so a pathological/empty scaffold could delete the user's just-created project directory. The boolean return value is also unused by the only caller, so the "did I delete myself" signal is silently dropped.

Consider only removing nested directories and leaving the root intact (or document that the caller must guard the root):

♻️ Proposed refactor
-export async function removeEmptyDirectories(rootDir: string): Promise<boolean> {
-  if (!(await pathExists(rootDir))) {
-    return true
-  }
-
-  const metadata = await stat(rootDir)
-  if (!metadata.isDirectory()) {
-    return false
-  }
-
-  const entries = await readdir(rootDir, { withFileTypes: true })
-
-  await Promise.all(
-    entries
-      .filter((entry) => entry.isDirectory())
-      .map((entry) => removeEmptyDirectories(resolve(rootDir, entry.name))),
-  )
-
-  const remainingEntries = await readdir(rootDir)
-
-  if (remainingEntries.length === 0) {
-    await rm(rootDir, { recursive: true, force: true })
-    return true
-  }
-
-  return false
-}
+export async function removeEmptyDirectories(rootDir: string, isRoot = true): Promise<boolean> {
+  if (!(await pathExists(rootDir))) {
+    return true
+  }
+
+  const metadata = await stat(rootDir)
+  if (!metadata.isDirectory()) {
+    return false
+  }
+
+  const entries = await readdir(rootDir, { withFileTypes: true })
+
+  await Promise.all(
+    entries
+      .filter((entry) => entry.isDirectory())
+      .map((entry) => removeEmptyDirectories(resolve(rootDir, entry.name), false)),
+  )
+
+  const remainingEntries = await readdir(rootDir)
+
+  if (remainingEntries.length === 0 && !isRoot) {
+    await rm(rootDir, { recursive: true, force: true })
+    return true
+  }
+
+  return remainingEntries.length === 0
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/utils.ts` around lines 115 -
141, The removeEmptyDirectories function currently deletes the passed-in rootDir
itself (via rm(rootDir, { recursive: true, force: true })) which can remove the
user's target project directory (options.targetDir) when everything inside is
pruned; update removeEmptyDirectories so it only removes nested empty
directories and never deletes the initial rootDir: change the recursion to
accept an additional flag/parameter (e.g., isRoot or stopAtRoot) or add a
wrapper so the top-level call from scaffold.ts passes a guard that prevents rm
from running on the original root, ensuring only child directories (resolved via
resolve(rootDir, entry.name) in the entries.map) are removed and leave the root
intact; also either remove or document the boolean return value since
scaffold.ts ignores it.

Comment on lines +252 to +255
async function expectMissing(rootDir: string, relativePath: string): Promise<void> {
const file = resolve(rootDir, relativePath)
await assert.rejects(() => readFile(file, 'utf8'), `${relativePath} should be removed`)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Node.js assert.rejects: is the second argument allowed to be a plain string in Node 20+, and does it serve as the assertion message?

💡 Result:

In Node.js 20+ (and still in current docs), the second argument to assert.rejects is not allowed to be a plain string as the “error matcher” parameter. - Signature: assert.rejects(asyncFn[, error][, message]). - The “error” parameter cannot be a string. The docs explicitly say: if a string is provided as the second argument, then error is assumed to be omitted and the string is treated as the optional “message” argument instead. - Therefore, a plain string as the second argument does serve as the assertion message (not as the error matcher). However, relying on this overload is discouraged: the docs warn that it’s easy to make mistakes and mention that this confusing notation can lead to specific ambiguous-argument behavior. Recommendation: if you want to supply an assertion message, pass it as the third argument: assert.rejects(fn, expectedErrorMatcher, 'your message'). If you want to match on error messages/content, prefer passing a RegExp or predicate via the second argument, not a string.

Citations:


Correct the assert.rejects signature to follow Node.js best practices.

The second argument to assert.rejects should be an error matcher (Class/RegExp/validation object/function), not a plain string. When a string is passed as the second argument, Node.js treats it as the optional message parameter due to an overload—this confusing notation is explicitly discouraged in the Node.js documentation. Pass the message as the third argument instead for clarity and adherence to best practices:

♻️ Proposed fix
-  await assert.rejects(() => readFile(file, 'utf8'), `${relativePath} should be removed`)
+  await assert.rejects(() => readFile(file, 'utf8'), undefined, `${relativePath} should be removed`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function expectMissing(rootDir: string, relativePath: string): Promise<void> {
const file = resolve(rootDir, relativePath)
await assert.rejects(() => readFile(file, 'utf8'), `${relativePath} should be removed`)
}
async function expectMissing(rootDir: string, relativePath: string): Promise<void> {
const file = resolve(rootDir, relativePath)
await assert.rejects(() => readFile(file, 'utf8'), undefined, `${relativePath} should be removed`)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/test/scaffold.test.ts` around lines
252 - 255, The test helper expectMissing currently passes a plain string as the
second argument to assert.rejects, which Node treats as the optional message
overload; update expectMissing so assert.rejects receives a proper error matcher
(e.g., Error or a RegExp) as the second argument and the message as the third.
Locate the expectMissing function and change the call to assert.rejects(() =>
readFile(file, 'utf8'), Error, `${relativePath} should be removed`) (or use a
suitable RegExp matcher) so the matcher is the second param and the message is
the third.

peterpadberg
peterpadberg previously approved these changes May 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/create-accessible-astro-starter/package.json (1)

22-22: ⚡ Quick win

@clack/prompts ^0.10.1 is locked to the 0.x series while the stable release line is 1.x

The latest published version of @clack/prompts is 1.3.0. The caret range ^0.10.1 only allows semver updates within 0.x.x, so this package will never resolve to any 1.x release. All consumers and CI jobs will install an old pre-stable build, missing every fix and API addition shipped in the 1.x line.

📦 Proposed fix
-    "@clack/prompts": "^0.10.1",
+    "@clack/prompts": "^1.0.0",

Verify that the cli.ts import surface is compatible with the 1.x API before updating.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/package.json` at line 22, Update the
`@clack/prompts` dependency from ^0.10.1 to a 1.x caret range (e.g., ^1.3.0) in
package.json so consumers receive the stable 1.x releases, then audit the CLI
import surface in cli.ts to ensure every imported symbol and API usage
(functions/types you call from `@clack/prompts`) matches the 1.x API and adjust
call sites to the new signatures if needed; once verified, commit the
package.json change and run CI/locally to confirm no runtime or type errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/create-accessible-astro-starter/src/cli.ts`:
- Around line 127-131: The spinner started with p.spinner() is not stopped if
scaffoldProject(options, manifest) throws; wrap the await scaffoldProject(...)
call in a try/finally so spinner.stop(...) is always invoked: call
spinner.stop(`Created ${options.siteName}`) on successful completion and ensure
spinner.stop() (or spinner.fail(...) if you prefer) is executed in the finally
block to guarantee the terminal spinner is cleaned up even on errors; reference
spinner, p.spinner(), scaffoldProject, and options.siteName when making the
change.

---

Nitpick comments:
In `@packages/create-accessible-astro-starter/package.json`:
- Line 22: Update the `@clack/prompts` dependency from ^0.10.1 to a 1.x caret
range (e.g., ^1.3.0) in package.json so consumers receive the stable 1.x
releases, then audit the CLI import surface in cli.ts to ensure every imported
symbol and API usage (functions/types you call from `@clack/prompts`) matches the
1.x API and adjust call sites to the new signatures if needed; once verified,
commit the package.json change and run CI/locally to confirm no runtime or type
errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62c6431d-65dc-4e6f-a332-10ffa383b39c

📥 Commits

Reviewing files that changed from the base of the PR and between 049ea3f and d4b84c9.

📒 Files selected for processing (4)
  • packages/create-accessible-astro-starter/package.json
  • packages/create-accessible-astro-starter/src/cli-output.ts
  • packages/create-accessible-astro-starter/src/cli.ts
  • packages/create-accessible-astro-starter/test/cli-output.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/create-accessible-astro-starter/test/cli-output.test.ts

Comment on lines +127 to +131
const spinner = p.spinner()

spinner.start('Scaffolding your project')
await scaffoldProject(options, manifest)
spinner.stop(`Created ${options.siteName}`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Spinner is not stopped on scaffoldProject failure, leaving the terminal in an inconsistent state.

If scaffoldProject throws, spinner.stop() is never called and the error propagates up to the index.ts catch block, where console.error writes over a still-"running" spinner.

🛠️ Proposed fix
  spinner.start('Scaffolding your project')
- await scaffoldProject(options, manifest)
- spinner.stop(`Created ${options.siteName}`)
+
+  try {
+    await scaffoldProject(options, manifest)
+  } catch (error) {
+    spinner.stop('Scaffolding failed')
+    throw error
+  }
+
+  spinner.stop(`Created ${options.siteName}`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const spinner = p.spinner()
spinner.start('Scaffolding your project')
await scaffoldProject(options, manifest)
spinner.stop(`Created ${options.siteName}`)
const spinner = p.spinner()
spinner.start('Scaffolding your project')
try {
await scaffoldProject(options, manifest)
} catch (error) {
spinner.stop('Scaffolding failed')
throw error
}
spinner.stop(`Created ${options.siteName}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-accessible-astro-starter/src/cli.ts` around lines 127 - 131,
The spinner started with p.spinner() is not stopped if scaffoldProject(options,
manifest) throws; wrap the await scaffoldProject(...) call in a try/finally so
spinner.stop(...) is always invoked: call spinner.stop(`Created
${options.siteName}`) on successful completion and ensure spinner.stop() (or
spinner.fail(...) if you prefer) is executed in the finally block to guarantee
the terminal spinner is cleaned up even on errors; reference spinner,
p.spinner(), scaffoldProject, and options.siteName when making the change.

@markteekman markteekman merged commit 0f5d949 into main May 2, 2026
6 checks passed
@markteekman markteekman deleted the feat/cli-tool branch May 2, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants