Skip to content

fix(create-rezi): make CLI main-entry detection symlink-safe#275

Merged
RtlZeroMemory merged 1 commit intomainfrom
fix/create-rezi-cli-symlink-entry
Mar 14, 2026
Merged

fix(create-rezi): make CLI main-entry detection symlink-safe#275
RtlZeroMemory merged 1 commit intomainfrom
fix/create-rezi-cli-symlink-entry

Conversation

@RtlZeroMemory
Copy link
Copy Markdown
Owner

@RtlZeroMemory RtlZeroMemory commented Mar 14, 2026

Summary

  • fix create-rezi main-entry detection so the CLI executes when invoked through symlinked launchers (node_modules/.bin/create-rezi)
  • extract the entrypoint check into mainEntry.ts and canonicalize both paths via realpathSync
  • add regression tests for direct execution, symlinked launcher execution, and non-matching paths
  • add an Unreleased changelog note for the scaffolding fix

Root Cause

index.ts compared fileURLToPath(import.meta.url) with resolve(process.argv[1]) directly.
When launched via npm create rezi / bun create rezi, process.argv[1] points at a symlink (.bin/create-rezi), so main() was never called.

Validation

  • node --import tsx --test packages/create-rezi/src/__tests__/mainEntry.test.ts packages/create-rezi/src/__tests__/scaffold.test.ts
  • npx tsc -b packages/create-rezi
  • npm run check:create-rezi-templates
  • manual smoke: install local package and run npx create-rezi demo --template minimal --no-install (project scaffolded successfully)
  • npm run typecheck
  • npm run lint (fails due pre-existing unrelated workspace formatting/import-order issues in packages/core; no new diagnostics from touched create-rezi files)

Closes #274

Summary by CodeRabbit

  • Bug Fixes

    • Fixed no-op scaffolding when running create commands in symlinked environments; CLI now reliably detects the main entry.
    • Improved failure handling so the CLI exits with a non-zero status on error.
  • Tests

    • Added tests for main-entry detection covering direct execution, symlinked launchers, and non-matching paths.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 14, 2026

📝 Walkthrough

Walkthrough

Adds a symlink-safe main-entry detection helper isMainModuleEntry() and uses it in the CLI; includes tests for direct execution, symlinked launchers, and non-matching paths; CLI now exits with code 1 on main invocation failure; changelog updated.

Changes

Cohort / File(s) Summary
Symlink-safe Entry Detection
packages/create-rezi/src/mainEntry.ts, packages/create-rezi/src/__tests__/mainEntry.test.ts
New isMainModuleEntry(argvPath, moduleUrl) that resolves canonical paths (handles symlinks and resolution errors) and tests covering direct execution, symlinked launcher resolution, and non-matching/undefined argv cases.
CLI Main Entry Integration
packages/create-rezi/src/index.ts
Replaced direct file URL comparison with isMainModuleEntry(process.argv[1], import.meta.url) and ensure the CLI exits with code 1 on failure.
Changelog
CHANGELOG.md
Added Unreleased bugfix entry documenting symlink-safe CLI main-entry detection fix.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through paths both real and linked,
Resolved each twist so installs don't blinked,
A gentle nudge, a canonical cheer,
Now quick-create sprouts where once was fear. 🌱

🚥 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 title accurately describes the main change: fixing CLI main-entry detection to be symlink-safe, which is the core objective of the PR.
Linked Issues check ✅ Passed The PR successfully addresses issue #274 by implementing symlink-safe main-entry detection, extracting the logic into mainEntry.ts, adding comprehensive test coverage, and validating through manual testing.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the main-entry detection issue: the new mainEntry.ts module, tests, index.ts integration, CHANGELOG.md entry, and validation steps are all in scope.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/create-rezi-cli-symlink-entry
📝 Coding Plan
  • Generate coding plan for human review comments

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

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

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.

🧹 Nitpick comments (1)
packages/create-rezi/src/__tests__/mainEntry.test.ts (1)

1-38: Optional: clean up temp dirs created by tests.

Not a blocker, but removing test temp directories avoids /tmp buildup during repeated local runs.

♻️ Suggested cleanup patch
-import { mkdtemp, symlink, writeFile } from "node:fs/promises";
+import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
@@
 test("isMainModuleEntry returns true for direct script execution", async () => {
   const root = await mkdtemp(join(tmpdir(), "rezi-main-entry-"));
-  const scriptPath = join(root, "create-rezi.mjs");
-  await writeFile(scriptPath, "export {};\n", "utf8");
-
-  const moduleUrl = pathToFileURL(scriptPath).href;
-  assert.equal(isMainModuleEntry(scriptPath, moduleUrl), true);
+  try {
+    const scriptPath = join(root, "create-rezi.mjs");
+    await writeFile(scriptPath, "export {};\n", "utf8");
+
+    const moduleUrl = pathToFileURL(scriptPath).href;
+    assert.equal(isMainModuleEntry(scriptPath, moduleUrl), true);
+  } finally {
+    await rm(root, { recursive: true, force: true });
+  }
 });
@@
 test("isMainModuleEntry resolves symlinked launchers", async () => {
   const root = await mkdtemp(join(tmpdir(), "rezi-main-entry-"));
-  const scriptPath = join(root, "dist-index.mjs");
-  const launcherPath = join(root, "launcher.mjs");
-  await writeFile(scriptPath, "export {};\n", "utf8");
-  await symlink(scriptPath, launcherPath);
-
-  const moduleUrl = pathToFileURL(scriptPath).href;
-  assert.equal(isMainModuleEntry(launcherPath, moduleUrl), true);
+  try {
+    const scriptPath = join(root, "dist-index.mjs");
+    const launcherPath = join(root, "launcher.mjs");
+    await writeFile(scriptPath, "export {};\n", "utf8");
+    await symlink(scriptPath, launcherPath);
+
+    const moduleUrl = pathToFileURL(scriptPath).href;
+    assert.equal(isMainModuleEntry(launcherPath, moduleUrl), true);
+  } finally {
+    await rm(root, { recursive: true, force: true });
+  }
 });
@@
 test("isMainModuleEntry rejects non-matching entry paths", async () => {
   const root = await mkdtemp(join(tmpdir(), "rezi-main-entry-"));
-  const scriptPath = join(root, "dist-index.mjs");
-  const otherPath = join(root, "other.mjs");
-  await writeFile(scriptPath, "export {};\n", "utf8");
-  await writeFile(otherPath, "export {};\n", "utf8");
-
-  const moduleUrl = pathToFileURL(scriptPath).href;
-  assert.equal(isMainModuleEntry(otherPath, moduleUrl), false);
-  assert.equal(isMainModuleEntry(undefined, moduleUrl), false);
+  try {
+    const scriptPath = join(root, "dist-index.mjs");
+    const otherPath = join(root, "other.mjs");
+    await writeFile(scriptPath, "export {};\n", "utf8");
+    await writeFile(otherPath, "export {};\n", "utf8");
+
+    const moduleUrl = pathToFileURL(scriptPath).href;
+    assert.equal(isMainModuleEntry(otherPath, moduleUrl), false);
+    assert.equal(isMainModuleEntry(undefined, moduleUrl), false);
+  } finally {
+    await rm(root, { recursive: true, force: true });
+  }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/src/__tests__/mainEntry.test.ts` around lines 1 - 38,
Tests create temporary directories via mkdtemp assigned to variable root but
never remove them; add cleanup to delete each temp dir after the test runs (use
fs.promises.rm or fs.promises.rmdir with recursive:true) — either call await
rm(root, { recursive: true, force: true }) at the end of each test that creates
root or add a shared afterEach/after hook that removes any created roots; update
the tests that define root (the three tests using mkdtemp and variables root,
scriptPath, launcherPath, otherPath) to ensure the temp directory is removed
even on failures (try/finally or test framework teardown).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/create-rezi/src/__tests__/mainEntry.test.ts`:
- Around line 1-38: Tests create temporary directories via mkdtemp assigned to
variable root but never remove them; add cleanup to delete each temp dir after
the test runs (use fs.promises.rm or fs.promises.rmdir with recursive:true) —
either call await rm(root, { recursive: true, force: true }) at the end of each
test that creates root or add a shared afterEach/after hook that removes any
created roots; update the tests that define root (the three tests using mkdtemp
and variables root, scriptPath, launcherPath, otherPath) to ensure the temp
directory is removed even on failures (try/finally or test framework teardown).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 749fe751-40ef-4d8c-b44f-763709e2112a

📥 Commits

Reviewing files that changed from the base of the PR and between 13e58c4 and d5c1a60.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • packages/create-rezi/src/__tests__/mainEntry.test.ts
  • packages/create-rezi/src/index.ts
  • packages/create-rezi/src/mainEntry.ts

@RtlZeroMemory RtlZeroMemory force-pushed the fix/create-rezi-cli-symlink-entry branch from d5c1a60 to ce14cae Compare March 14, 2026 06:46
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.

🧹 Nitpick comments (1)
packages/create-rezi/src/__tests__/mainEntry.test.ts (1)

8-38: Clean up the temp fixtures after each test.

Each case leaves a rezi-main-entry-* directory behind in the system temp folder. Wrapping the fixture setup in try/finally and removing root would keep repeated local runs tidy.

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

In `@packages/create-rezi/src/__tests__/mainEntry.test.ts` around lines 8 - 38,
Each test that creates a temporary root via mkdtemp should clean it up in a
finally block: wrap the body of each test that sets const root = await
mkdtemp(...) in try { ... } finally { await fs.rm(root, { recursive: true,
force: true }) } (or fs.rmdir/fs.promises.rm equivalent), so update the tests in
packages/create-rezi/src/__tests__/mainEntry.test.ts that call mkdtemp and
create files (the three tests using root, scriptPath, launcherPath, otherPath)
to remove the created temporary directory in a finally block to ensure fixtures
are deleted even on failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/create-rezi/src/__tests__/mainEntry.test.ts`:
- Around line 8-38: Each test that creates a temporary root via mkdtemp should
clean it up in a finally block: wrap the body of each test that sets const root
= await mkdtemp(...) in try { ... } finally { await fs.rm(root, { recursive:
true, force: true }) } (or fs.rmdir/fs.promises.rm equivalent), so update the
tests in packages/create-rezi/src/__tests__/mainEntry.test.ts that call mkdtemp
and create files (the three tests using root, scriptPath, launcherPath,
otherPath) to remove the created temporary directory in a finally block to
ensure fixtures are deleted even on failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e0fb1057-1a5d-48dc-a215-401a14cd65f0

📥 Commits

Reviewing files that changed from the base of the PR and between d5c1a60 and ce14cae.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • packages/create-rezi/src/__tests__/mainEntry.test.ts
  • packages/create-rezi/src/index.ts
  • packages/create-rezi/src/mainEntry.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • packages/create-rezi/src/index.ts

@RtlZeroMemory RtlZeroMemory merged commit 88e4884 into main Mar 14, 2026
32 checks passed
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.

Quick install is not installing anything

1 participant