Skip to content

fix: Fix extractor.json being silently overwritten when it already exists but fails schema validation during auto-generation#813

Merged
kingston merged 1 commit into
mainfrom
kingston/eng-984-when-extracting-the-template-with-an-invalid-name-it-re
Mar 12, 2026
Merged

fix: Fix extractor.json being silently overwritten when it already exists but fails schema validation during auto-generation#813
kingston merged 1 commit into
mainfrom
kingston/eng-984-when-extracting-the-template-with-an-invalid-name-it-re

Conversation

@kingston

@kingston kingston commented Mar 12, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a critical data loss issue where extractor.json could be silently overwritten when an existing configuration file failed validation during the extraction process. Users now receive a detailed error message instead, explicitly requiring them to address and fix the problematic file before extraction proceeds. This safeguards against accidental data loss.

…ists but fails schema validation during auto-generation
@changeset-bot

changeset-bot Bot commented Mar 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0805dac

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@baseplate-dev/sync Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/create-project Patch
@baseplate-dev/fastify-generators Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-email Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-rate-limit Patch
@baseplate-dev/plugin-storage Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-dev Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/code-morph Patch
@baseplate-dev/tools Patch
@baseplate-dev/ui-components Patch
@baseplate-dev/utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a safeguard to prevent silent overwriting of extractor.json when it exists but fails schema validation. The system now throws an error requiring manual fixes before proceeding, along with test coverage and a changeset documenting the patch.

Changes

Cohort / File(s) Summary
Changeset Documentation
.changeset/fix-extractor-json-overwrite.md
Documents a patch release for @baseplate-dev/sync fixing the issue where extractor.json was silently overwritten when it exists but fails schema validation.
Guard Implementation and Tests
packages/sync/src/templates/extractor/configs/try-create-extractor-json.ts, packages/sync/src/templates/extractor/configs/try-create-extractor-json.unit.test.ts
Adds a pre-creation check that throws an error if extractor.json already exists at the target path before file creation. Includes test case verifying the error is raised with the expected message when the file is present.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 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 fix: preventing extractor.json from being silently overwritten when it already exists but fails schema validation, which matches the core changes in the code.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ 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 kingston/eng-984-when-extracting-the-template-with-an-invalid-name-it-re

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/sync/src/templates/extractor/configs/try-create-extractor-json.ts (1)

73-87: ⚠️ Potential issue | 🔴 Critical

Make the create step atomic to prevent data loss under concurrent execution.

The existsSync() check on line 73 is racy with writeFile() on lines 84–88: if multiple processes run concurrently, one process can create extractor.json after another process checks for its existence but before that process writes, causing the file to be truncated. Use exclusive create mode (flag: 'wx') and translate the EEXIST error to preserve the intended behavior without the race condition.

Proposed fix
-  if (fsAdapter.existsSync(extractorJsonPath)) {
-    throw new Error(
-      `extractor.json already exists at ${extractorJsonPath} but could not be loaded. Please fix the existing file before running extraction.`,
-    );
-  }
-
   // Create the extractor.json content
   const extractorConfig = {
     name: generatorPath,
   };
 
-  await writeFile(
-    extractorJsonPath,
-    `${JSON.stringify(extractorConfig, null, 2)}\n`,
-    'utf8',
-  );
+  try {
+    await writeFile(
+      extractorJsonPath,
+      `${JSON.stringify(extractorConfig, null, 2)}\n`,
+      { encoding: 'utf8', flag: 'wx' },
+    );
+  } catch (error) {
+    if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
+      throw new Error(
+        `extractor.json already exists at ${extractorJsonPath} but could not be loaded. Please fix the existing file before running extraction.`,
+      );
+    }
+
+    throw error;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/sync/src/templates/extractor/configs/try-create-extractor-json.ts`
around lines 73 - 87, Replace the racy existsSync() + writeFile() flow by
attempting an exclusive create when writing extractor.json: call
writeFile(extractorJsonPath, JSON.stringify(extractorConfig,... ) + '\n', {
encoding: 'utf8', flag: 'wx' }) and catch the EEXIST error to throw the same
"extractor.json already exists..." Error as before (preserving extractorJsonPath
in the message); keep the original extractorConfig generation and remove the
prior existsSync() branch so concurrent processes cannot truncate the file.
🧹 Nitpick comments (1)
packages/sync/src/templates/extractor/configs/try-create-extractor-json.unit.test.ts (1)

96-120: Assert that the existing file was not modified.

This test currently proves the helper rejects, but it does not prove the original extractor.json survived untouched. Adding that post-condition would guard the exact regression this PR is fixing.

Suggested test hardening
   it('should throw error when extractor.json already exists', async () => {
     // Arrange
     const packagePath = '/test-package';
     const generatorName = 'test-package#auth/login';
+    const extractorJsonPath =
+      `${packagePath}/src/generators/auth/login/extractor.json`;
     const packageMap = new Map<string, string>([
       ['test-package', '/test-package'],
     ]);
 
     vol.fromJSON({
       [`${packagePath}/src/generators/auth/login/login.generator.ts`]:
         'export const generator = {};',
-      [`${packagePath}/src/generators/auth/login/extractor.json`]:
-        '{"name": "INVALID NAME"}',
+      [extractorJsonPath]: '{"name": "INVALID NAME"}',
     });
 
     // Act & Assert
     await expect(
       tryCreateExtractorJson({
         packageMap,
         generatorName,
       }),
     ).rejects.toThrowError(
       /extractor\.json already exists at .+ but could not be loaded/,
     );
+    expect(vol.toJSON()[extractorJsonPath]).toBe('{"name": "INVALID NAME"}');
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/sync/src/templates/extractor/configs/try-create-extractor-json.unit.test.ts`
around lines 96 - 120, After the test asserts that tryCreateExtractorJson
rejects for an existing extractor.json, also verify the file was not modified by
reading the file contents (e.g., via fs.promises.readFile or vol.readFile) at
`${packagePath}/src/generators/auth/login/extractor.json` and asserting it still
equals the original string '{"name": "INVALID NAME"}'; update the 'should throw
error when extractor.json already exists' test to perform this post-condition
check using the same packagePath/generatorName/packageMap setup and the existing
vol.fromJSON fixture so the test ensures the original extractor.json survived
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/sync/src/templates/extractor/configs/try-create-extractor-json.ts`:
- Around line 73-87: Replace the racy existsSync() + writeFile() flow by
attempting an exclusive create when writing extractor.json: call
writeFile(extractorJsonPath, JSON.stringify(extractorConfig,... ) + '\n', {
encoding: 'utf8', flag: 'wx' }) and catch the EEXIST error to throw the same
"extractor.json already exists..." Error as before (preserving extractorJsonPath
in the message); keep the original extractorConfig generation and remove the
prior existsSync() branch so concurrent processes cannot truncate the file.

---

Nitpick comments:
In
`@packages/sync/src/templates/extractor/configs/try-create-extractor-json.unit.test.ts`:
- Around line 96-120: After the test asserts that tryCreateExtractorJson rejects
for an existing extractor.json, also verify the file was not modified by reading
the file contents (e.g., via fs.promises.readFile or vol.readFile) at
`${packagePath}/src/generators/auth/login/extractor.json` and asserting it still
equals the original string '{"name": "INVALID NAME"}'; update the 'should throw
error when extractor.json already exists' test to perform this post-condition
check using the same packagePath/generatorName/packageMap setup and the existing
vol.fromJSON fixture so the test ensures the original extractor.json survived
unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 47cb8145-6495-4cb1-8b8f-83b68be23fa9

📥 Commits

Reviewing files that changed from the base of the PR and between 04f25c3 and 0805dac.

📒 Files selected for processing (3)
  • .changeset/fix-extractor-json-overwrite.md
  • packages/sync/src/templates/extractor/configs/try-create-extractor-json.ts
  • packages/sync/src/templates/extractor/configs/try-create-extractor-json.unit.test.ts

@kingston kingston merged commit 168793d into main Mar 12, 2026
11 checks passed
@kingston kingston deleted the kingston/eng-984-when-extracting-the-template-with-an-invalid-name-it-re branch March 12, 2026 08:47
@github-actions github-actions Bot mentioned this pull request Mar 12, 2026
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.

1 participant