Skip to content

feat: Sort entity arrays by name in project-definition.json for deterministic output#861

Merged
kingston merged 2 commits intomainfrom
kingston/eng-1132-add-structured-ordering-to-project-definition
Mar 22, 2026
Merged

feat: Sort entity arrays by name in project-definition.json for deterministic output#861
kingston merged 2 commits intomainfrom
kingston/eng-1132-add-structured-ordering-to-project-definition

Conversation

@kingston
Copy link
Collaborator

@kingston kingston commented Mar 22, 2026

Summary by CodeRabbit

  • New Features

    • Entity arrays now serialize deterministically, sorted alphabetically by name when configuration enables sorting
    • Added billing feature models: BillingAccount and BillingSubscription for subscription management
  • Chores

    • Updated project definition configurations for better organization
    • Improved formatting and linting tools with enhanced Prettier integration and configuration caching

@changeset-bot
Copy link

changeset-bot bot commented Mar 22, 2026

🦋 Changeset detected

Latest commit: 8b40ecc

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

This PR includes changesets to release 23 packages
Name Type
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/create-project Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-dev Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-email Patch
@baseplate-dev/plugin-observability Patch
@baseplate-dev/plugin-payments Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-rate-limit Patch
@baseplate-dev/plugin-storage Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/code-morph Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/fastify-generators Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/sync 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
Copy link

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

This PR introduces deterministic sorting of entity arrays in project definitions. A new sortEntityArrays utility function traverses the schema and data tree to sort arrays by entity name when sortByName is enabled in schema metadata. Multiple schema definitions are updated to enable this sorting, and the serialization pipeline integrates the sorting step.

Changes

Cohort / File(s) Summary
Entity sorting core
packages/project-builder-lib/src/tools/sort-entity-arrays.ts, packages/project-builder-lib/src/tools/sort-entity-arrays.unit.test.ts
New utility function and comprehensive unit tests for sorting entity arrays by name during serialization, with tests verifying sorting behavior, immutability, empty arrays, and deterministic output.
Reference infrastructure
packages/project-builder-lib/src/references/definition-ref-builder.ts, packages/project-builder-lib/src/references/definition-ref-registry.ts, packages/project-builder-lib/src/references/extend-parser-context-with-refs.ts
Added optional sortByName?: boolean flag to DefinitionEntityInputBase and EntitySchemaMeta, and updated context parser to pass the flag through to the registry.
Schema configuration
packages/project-builder-lib/src/schema/features/feature.ts, packages/project-builder-lib/src/schema/libraries/library.ts, packages/project-builder-lib/src/schema/models/enums.ts, packages/project-builder-lib/src/schema/models/models.ts, packages/project-builder-lib/src/schema/plugins/definition.ts, packages/project-builder-lib/src/schema/project-definition.ts
Updated six schema definitions to enable sortByName: true for features, libraries, enums, models, plugins, and apps respectively.
Serialization integration
packages/project-builder-lib/src/definition/project-definition-container.ts, packages/project-builder-lib/src/tools/index.ts
Integrated sortEntityArrays into the serialization pipeline via ProjectDefinitionContainer.toSerializedContents() and exported the utility function from the tools module.
Project definition examples
examples/blog-with-auth/baseplate/project-definition.json, examples/todo-with-better-auth/baseplate/project-definition.json, examples/todo-with-better-auth/apps/.../.paths-metadata.json
Updated example project definitions and metadata files with reordered features, models, plugins, and path mappings, reflecting the new deterministic sorting behavior.
Changeset documentation
.changeset/sort-entity-arrays.md
Added changeset documenting the patch version bump for the entity array sorting feature.
Formatting and linting script
scripts/format-and-lint.ts
Substantially refactored to integrate Prettier API directly for example files with per-file formatting and ignore-path resolution, replacing batch npx prettier calls and splitting monorepo vs. example file handling into separate control flows.
Plugin schema
plugins/plugin-payments/src/stripe/core/schema/plugin-definition.ts, plugins/plugin-payments/src/stripe/core/schema/schema-issue-checker.ts
Updated billing plan schema formatting and renamed config variable from config to stripeConfig for consistency in feature reference lookups.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding deterministic sorting of entity arrays in project-definition.json files by name, which is the core feature across all modifications.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kingston/eng-1132-add-structured-ordering-to-project-definition

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.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

Copy link

@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/project-builder-lib/src/tools/sort-entity-arrays.unit.test.ts (1)

126-132: Use independent model instances for each container in the determinism test.

modelsCBA currently reuses the same objects from modelsABC, which can hide coupling if helper code mutates model inputs. Prefer constructing a fresh reversed set for container2.

Suggested tweak
-    const modelsABC = [
+    const modelsABC = [
       createTestModel({ name: 'Alpha', featureRef: testFeature.name }),
       createTestModel({ name: 'Bravo', featureRef: testFeature.name }),
       createTestModel({ name: 'Charlie', featureRef: testFeature.name }),
     ];
-    const modelsCBA = [modelsABC[2], modelsABC[1], modelsABC[0]];
+    const modelsCBA = [
+      createTestModel({ name: 'Charlie', featureRef: testFeature.name }),
+      createTestModel({ name: 'Bravo', featureRef: testFeature.name }),
+      createTestModel({ name: 'Alpha', featureRef: testFeature.name }),
+    ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/project-builder-lib/src/tools/sort-entity-arrays.unit.test.ts`
around lines 126 - 132, The determinism test reuses the same model objects by
assigning modelsCBA = [modelsABC[2], modelsABC[1], modelsABC[0]] which masks
mutations; instead construct fresh model instances for the reversed set by
calling createTestModel for each element (using the same name and featureRef
values) so modelsCBA contains independent objects; update the test setup that
defines modelsABC and modelsCBA (and any reference to container2) to build
modelsCBA via createTestModel invocations in reversed order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/format-and-lint.ts`:
- Around line 76-86: getPrettierConfig currently caches by config file path only
(prettierConfigCache) which causes incorrect results when
resolveConfig(filePath) applies different overrides per file type; change the
caching strategy so the cache key includes the file-specific discriminator
(e.g., file extension) or remove the cache and always call resolveConfig.
Specifically, in getPrettierConfig and where prettierConfigCache is used,
compute a key like `${configFile}::${extension}` (using path.extname(filePath))
before checking/setting the cache, or alternatively cache only the raw config
file contents from resolveConfigFile and call resolveConfig(filePath, { config:
configFile }) every time to apply overrides per-file.

---

Nitpick comments:
In `@packages/project-builder-lib/src/tools/sort-entity-arrays.unit.test.ts`:
- Around line 126-132: The determinism test reuses the same model objects by
assigning modelsCBA = [modelsABC[2], modelsABC[1], modelsABC[0]] which masks
mutations; instead construct fresh model instances for the reversed set by
calling createTestModel for each element (using the same name and featureRef
values) so modelsCBA contains independent objects; update the test setup that
defines modelsABC and modelsCBA (and any reference to container2) to build
modelsCBA via createTestModel invocations in reversed order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4e4b2119-5652-4987-986f-43511d94e856

📥 Commits

Reviewing files that changed from the base of the PR and between fc8f158 and 8b40ecc.

⛔ Files ignored due to path filters (4)
  • examples/todo-with-better-auth/apps/admin/baseplate/generated/.paths-metadata.json is excluded by !**/generated/**, !**/generated/**
  • examples/todo-with-better-auth/apps/backend/baseplate/generated/.paths-metadata.json is excluded by !**/generated/**, !**/generated/**
  • examples/todo-with-better-auth/apps/web/baseplate/generated/.paths-metadata.json is excluded by !**/generated/**, !**/generated/**
  • tests/simple/project-definition.json is excluded by !tests/**
📒 Files selected for processing (22)
  • .changeset/sort-entity-arrays.md
  • examples/blog-with-auth/baseplate/project-definition.json
  • examples/todo-with-better-auth/apps/admin/.paths-metadata.json
  • examples/todo-with-better-auth/apps/backend/.paths-metadata.json
  • examples/todo-with-better-auth/apps/web/.paths-metadata.json
  • examples/todo-with-better-auth/baseplate/project-definition.json
  • packages/project-builder-lib/src/definition/project-definition-container.ts
  • packages/project-builder-lib/src/references/definition-ref-builder.ts
  • packages/project-builder-lib/src/references/definition-ref-registry.ts
  • packages/project-builder-lib/src/references/extend-parser-context-with-refs.ts
  • packages/project-builder-lib/src/schema/features/feature.ts
  • packages/project-builder-lib/src/schema/libraries/library.ts
  • packages/project-builder-lib/src/schema/models/enums.ts
  • packages/project-builder-lib/src/schema/models/models.ts
  • packages/project-builder-lib/src/schema/plugins/definition.ts
  • packages/project-builder-lib/src/schema/project-definition.ts
  • packages/project-builder-lib/src/tools/index.ts
  • packages/project-builder-lib/src/tools/sort-entity-arrays.ts
  • packages/project-builder-lib/src/tools/sort-entity-arrays.unit.test.ts
  • plugins/plugin-payments/src/stripe/core/schema/plugin-definition.ts
  • plugins/plugin-payments/src/stripe/core/schema/schema-issue-checker.ts
  • scripts/format-and-lint.ts

Comment on lines +76 to +86
const prettierConfigCache = new Map<string, Options | null>();

async function getPrettierConfig(filePath: string): Promise<Options | null> {
const configFile = await resolveConfigFile(filePath);
if (!configFile) return null;
if (prettierConfigCache.has(configFile)) {
return prettierConfigCache.get(configFile) ?? null;
}
const config = await resolveConfig(filePath, { config: configFile });
prettierConfigCache.set(configFile, config);
return config;
Copy link

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:

Prettier resolveConfig overrides behavior

💡 Result:

Prettier's resolveConfig(filePath) API resolves the effective configuration options for the given filePath by searching up the directory tree from that file's location for a configuration file (e.g., .prettierrc). It merges base options from the config file with any matching overrides based on the file's path and extension, returning a single flat Options object containing the final resolved settings (or null if no config found). The raw overrides array is not returned; instead, matching override options override base options. Key behaviors: - Requires a specific file path (not directory) to correctly apply overrides, as overrides depend on filename/extension (e.g., "*.test.js"). - Passing absolute paths may have had bugs in older versions (e.g., issue #3005 from 2017), but current versions handle them properly when the path is valid. - Resolution order: Prettier defaults -> EditorConfig (if enabled) -> config file base options -> matching overrides. - Usage example: const options = await prettier.resolveConfig('/path/to/file.js'); then prettier.format(code, { ...options, filepath: '/path/to/file.js' }); This matches how Prettier CLI and editor integrations resolve configs per file.

Citations:


🏁 Script executed:

cat -n scripts/format-and-lint.ts | head -150

Repository: halfdomelabs/baseplate

Length of output: 6057


🏁 Script executed:

cat -n scripts/format-and-lint.ts | sed -n '150,225p'

Repository: halfdomelabs/baseplate

Length of output: 2960


Prettier config cache doesn't account for file-specific overrides.

When processing multiple files from the same project, the cache keys by config file path only. However, resolveConfig(filePath) returns the merged configuration including any overrides that match the specific file's extension. With different file types (e.g., foo.ts and bar.tsx) that share a config file but match different override patterns, the second file receives the cached config resolved for the first file instead of its own.

Consider caching by filePath extension, or removing the cache entirely (config resolution is fast), or cache only the raw config file content and apply overrides per-file.

🔧 Proposed fix: cache by config file + file extension
-const prettierConfigCache = new Map<string, Options | null>();
+const prettierConfigCache = new Map<string, Options | null>();

 async function getPrettierConfig(filePath: string): Promise<Options | null> {
   const configFile = await resolveConfigFile(filePath);
   if (!configFile) return null;
-  if (prettierConfigCache.has(configFile)) {
-    return prettierConfigCache.get(configFile) ?? null;
+  const ext = path.extname(filePath);
+  const cacheKey = `${configFile}::${ext}`;
+  if (prettierConfigCache.has(cacheKey)) {
+    return prettierConfigCache.get(cacheKey) ?? null;
   }
   const config = await resolveConfig(filePath, { config: configFile });
-  prettierConfigCache.set(configFile, config);
+  prettierConfigCache.set(cacheKey, config);
   return config;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/format-and-lint.ts` around lines 76 - 86, getPrettierConfig currently
caches by config file path only (prettierConfigCache) which causes incorrect
results when resolveConfig(filePath) applies different overrides per file type;
change the caching strategy so the cache key includes the file-specific
discriminator (e.g., file extension) or remove the cache and always call
resolveConfig. Specifically, in getPrettierConfig and where prettierConfigCache
is used, compute a key like `${configFile}::${extension}` (using
path.extname(filePath)) before checking/setting the cache, or alternatively
cache only the raw config file contents from resolveConfigFile and call
resolveConfig(filePath, { config: configFile }) every time to apply overrides
per-file.

@kingston kingston merged commit efcf233 into main Mar 22, 2026
14 checks passed
@kingston kingston deleted the kingston/eng-1132-add-structured-ordering-to-project-definition branch March 22, 2026 07:42
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