Skip to content

feat: Introduce persistent user config file#506

Merged
kingston merged 6 commits into
mainfrom
kingston/eng-641-add-baseplate-user-settings-file-to-allow-global-user
Apr 21, 2025
Merged

feat: Introduce persistent user config file#506
kingston merged 6 commits into
mainfrom
kingston/eng-641-add-baseplate-user-settings-file-to-allow-global-user

Conversation

@kingston

@kingston kingston commented Apr 21, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Introduced persistent user configuration for Baseplate, allowing options to be saved across sessions via a configuration file in the user’s home directory.
    • User configuration now influences build and sync behavior, including support for custom merge drivers and generator step output options.
  • Bug Fixes

    • Improved error handling for missing configuration files, ensuring the system falls back gracefully.
  • Chores

    • Updated internal processes to use user configuration instead of environment variables for certain settings.

@linear

linear Bot commented Apr 21, 2025

Copy link
Copy Markdown
ENG-641 Add Baseplate user settings file to allow global user configurations

Implement a user settings file for Baseplate that allows global configuration preferences to be stored persistently, replacing the current approach of using environment variables. This will provide a more user-friendly way to configure Baseplate across projects and enable more complex configuration options.

Design Doc: https://app.nuclino.com/Half-Dome-Labs/Engineering-Design-Docs/Baseplate-User-Settings-File-80b8eacb-55ce-434d-bb82-948bc054dfc5

@changeset-bot

changeset-bot Bot commented Apr 21, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a4bf643

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

This PR includes changesets to release 4 packages
Name Type
@halfdomelabs/project-builder-server Patch
@halfdomelabs/project-builder-cli Patch
@halfdomelabs/project-builder-common Patch
@halfdomelabs/project-builder-test 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 Apr 21, 2025

Copy link
Copy Markdown

Walkthrough

This change introduces support for persistent user configuration in the Baseplate project builder ecosystem. A new configuration file at ~/.baseplate/config.json allows users to define options that persist across sessions. The configuration schema is defined and validated using Zod, and the configuration is loaded and injected into the build and server processes. All relevant interfaces and function signatures in both the CLI and server packages are updated to accept and use this user configuration, replacing previous reliance on environment variables for certain options. Test utilities are also updated to accommodate the new configuration parameter.

Changes

File(s) Change Summary
.changeset/loud-pandas-create.md Documents the introduction of persistent user configuration via ~/.baseplate/config.json and notes affected packages.
packages/project-builder-cli/src/commands/build.ts
packages/project-builder-cli/src/commands/server.ts
Imports and uses getUserConfig to retrieve user config and passes it to build/server processes.
packages/project-builder-cli/src/services/user-config.ts New module for loading and validating user configuration from the user's home directory, with schema validation and error handling.
packages/project-builder-server/src/index.ts
packages/project-builder-server/src/user-config/index.ts
Adds exports to make user config schema/types available in the public API.
packages/project-builder-server/src/user-config/user-config-schema.ts Adds a Zod schema and TypeScript type for user configuration, supporting sync options.
packages/project-builder-server/src/runner/index.ts
packages/project-builder-server/src/service/builder-service.ts
packages/project-builder-server/src/server/builder-service-manager.ts
packages/project-builder-server/src/sync/index.ts
Updates interfaces, constructors, and function signatures to require and propagate userConfig; replaces use of environment variables with user config values.
packages/project-builder-server/src/service/environment-flags.ts Removes the file that previously provided environment-based configuration flags.
packages/project-builder-test/src/commands/serve.ts
packages/project-builder-test/src/runner/generate.ts
Updates test utilities to pass an empty userConfig object to match new function signatures.
packages/sync/src/output/write-generator-output.ts Updates comment to clarify the usage of the mergeDriver option for custom Git merge drivers.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI
    participant UserConfigService
    participant Server
    participant BuilderServiceManager
    participant ProjectBuilderService

    User->>CLI: run build or serve command
    CLI->>UserConfigService: getUserConfig()
    UserConfigService-->>CLI: returns userConfig
    CLI->>Server: start/build with userConfig
    Server->>BuilderServiceManager: instantiate with userConfig
    BuilderServiceManager->>ProjectBuilderService: instantiate with userConfig
    ProjectBuilderService->>ProjectBuilderService: buildProject(userConfig)
    ProjectBuilderService->>...: downstream functions receive userConfig
Loading
sequenceDiagram
    participant ProjectBuilderService
    participant generateForDirectory

    ProjectBuilderService->>generateForDirectory: call with userConfig
    generateForDirectory->>generateForDirectory: use userConfig.sync options
Loading

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 68dfbd0 and a4bf643.

📒 Files selected for processing (3)
  • .changeset/loud-pandas-create.md (1 hunks)
  • packages/project-builder-server/src/user-config/user-config-schema.ts (1 hunks)
  • packages/sync/src/output/write-generator-output.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/sync/src/output/write-generator-output.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/loud-pandas-create.md
  • packages/project-builder-server/src/user-config/user-config-schema.ts

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
packages/project-builder-server/src/sync/index.ts (1)

23-240: 💡 Verification agent

🧩 Analysis chain

Consider adding fallback for backward compatibility

The changes migrate from environment variables to a user configuration object without providing fallbacks to the original environment variables. This might cause issues for existing users who haven't migrated to the new configuration system yet.

Consider adding fallbacks to maintain backward compatibility during the transition period:

      mergeDriver: userConfig.sync?.customMergeDriver
        ? {
            name: 'baseplate-custom-merge-driver',
            driver: userConfig.sync.customMergeDriver,
          }
        : undefined,

Let's verify if there are any environment variable fallbacks implemented elsewhere in the codebase:


🏁 Script executed:

#!/bin/bash
# Check if environment variables are still used or if fallbacks are implemented

# Search for BASEPLATE_CUSTOM_MERGE_DRIVER and BASEPLATE_WRITE_GENERATOR_STEPS_JSON
echo "Searching for environment variable references:"
rg "BASEPLATE_CUSTOM_MERGE_DRIVER|BASEPLATE_WRITE_GENERATOR_STEPS_JSON" --type ts

# Check if there's any migration logic or fallbacks implemented
echo "\nChecking for potential migration or fallback logic:"
rg "environment.*fallback|fallback.*environment|process\.env" --type ts -A 3 -B 3 packages/project-builder-server/src/user-config/

Length of output: 436


Add environment‐variable fallbacks for backward compatibility

The sync code now only reads from userConfig.sync, but we didn’t preserve the old process.env flags. To avoid breaking existing users, add fallbacks to the original env vars:

• File: packages/project-builder-server/src/sync/index.ts
Location: around the mergeDriver option in generateForDirectory
Suggestion:

-     mergeDriver: userConfig.sync?.customMergeDriver
-       ? {
-           name: 'baseplate-custom-merge-driver',
-           driver: userConfig.sync.customMergeDriver,
-         }
-       : undefined,
+     mergeDriver:
+       userConfig.sync?.customMergeDriver
+         ? {
+             name: 'baseplate-custom-merge-driver',
+             driver: userConfig.sync.customMergeDriver,
+           }
+         : process.env.BASEPLATE_CUSTOM_MERGE_DRIVER
+         ? {
+             name: 'baseplate-custom-merge-driver',
+             driver: process.env.BASEPLATE_CUSTOM_MERGE_DRIVER,
+           }
+         : undefined,

• File: same—around the writeGeneratorStepsJson check
Suggestion:

-    if (userConfig.sync?.writeGeneratorStepsJson && output.metadata) {
+    const writeSteps =
+      userConfig.sync?.writeGeneratorStepsJson ??
+      (process.env.BASEPLATE_WRITE_GENERATOR_STEPS_JSON === 'true');
+    if (writeSteps && output.metadata) {

This preserves the old BASEPLATE_CUSTOM_MERGE_DRIVER and BASEPLATE_WRITE_GENERATOR_STEPS_JSON behavior until users fully migrate to userConfig.sync.

🧹 Nitpick comments (7)
packages/project-builder-server/src/user-config/user-config-schema.ts (2)

3-16: The schema looks good, but could benefit from more comprehensive documentation.

The schema defines the user configuration structure well using Zod. However, consider adding a top-level JSDoc comment that explains the overall purpose of this configuration file, where it's stored, and how it's used by the system.

 import { z } from 'zod';

+/**
+ * Schema for validating user configuration stored in ~/.baseplate/config.json
+ * This configuration allows users to persist preferences across sessions.
+ */
 export const userConfigSchema = z.object({
   sync: z
     .object({
       /**
        * Whether to write the generator steps JSON file
        */
       writeGeneratorStepsJson: z.boolean().optional().default(false),
       /**
        * The custom merge driver to use
        */
       customMergeDriver: z.string().optional(),
     })
     .optional(),
 });

13-13: Consider adding validation for the customMergeDriver string.

The customMergeDriver is defined as an arbitrary string without constraints. If there are specific formats or values expected for this field, adding validation would help users avoid errors.

       /**
        * The custom merge driver to use
        */
-      customMergeDriver: z.string().optional(),
+      customMergeDriver: z.string().min(1).optional(),
packages/project-builder-cli/src/services/user-config.ts (2)

11-14: Fixed configuration path may limit flexibility.

The configuration path is hardcoded to .baseplate/config.json in the user's home directory. Consider adding support for an environment variable to override this location.

 function getConfigPath(): string {
   const homeDir = os.homedir();
-  return path.join(homeDir, '.baseplate', 'config.json');
+  const configPathOverride = process.env.BASEPLATE_CONFIG_PATH;
+  return configPathOverride || path.join(homeDir, '.baseplate', 'config.json');
 }

21-27: Good error handling for missing configuration files, but consider enhancing error handling.

The function gracefully handles missing config files by falling back to an empty object. However, it doesn't handle other potential errors such as JSON parsing errors or permission issues.

 export async function getUserConfig(): Promise<BaseplateUserConfig> {
   const configPath = getConfigPath();
-  const config = await readJsonWithSchema(configPath, userConfigSchema).catch(
-    handleFileNotFoundError,
-  );
-  return config ?? {};
+  try {
+    const config = await readJsonWithSchema(configPath, userConfigSchema);
+    return config;
+  } catch (error) {
+    if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
+      // File not found, return empty config
+      return {};
+    }
+    
+    // For other errors (like permission issues or malformed JSON), log and return empty config
+    console.error(`Error loading config from ${configPath}:`, error);
+    return {};
+  }
 }
packages/project-builder-server/src/service/builder-service.ts (1)

28-29: Consider using the imported type from the index file.

For consistency, consider importing BaseplateUserConfig from the index file rather than directly from the schema file, especially if other files follow this pattern.

-import type { BaseplateUserConfig } from '../user-config/user-config-schema.js';
+import type { BaseplateUserConfig } from '../user-config/index.js';
packages/project-builder-server/src/sync/index.ts (2)

169-173: Custom merge driver configuration properly migrated

The code now correctly uses userConfig.sync?.customMergeDriver instead of the previous environment flag. The optional chaining operator is appropriately used to handle cases where the sync property might be undefined.

However, consider adding a comment documenting the mapping from the old environment variable name to the new configuration property for future reference.

      mergeDriver: userConfig.sync?.customMergeDriver
        ? {
            name: 'baseplate-custom-merge-driver',
+           // Previously controlled by BASEPLATE_CUSTOM_MERGE_DRIVER environment variable
            driver: userConfig.sync.customMergeDriver,
          }
        : undefined,

240-240: Generator steps JSON writing configuration migrated properly

The conditional check for writing generator steps JSON is now properly using userConfig.sync?.writeGeneratorStepsJson instead of the environment flag.

Similarly to the merge driver configuration, consider adding a comment about the previous environment variable:

+    // Previously controlled by BASEPLATE_WRITE_GENERATOR_STEPS_JSON environment variable
    if (userConfig.sync?.writeGeneratorStepsJson && output.metadata) {
      await writeGeneratorSteps(output.metadata, projectDirectory);
    }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Lite

📥 Commits

Reviewing files that changed from the base of the PR and between 1027f68 and 68dfbd0.

📒 Files selected for processing (14)
  • .changeset/loud-pandas-create.md (1 hunks)
  • packages/project-builder-cli/src/commands/build.ts (2 hunks)
  • packages/project-builder-cli/src/commands/server.ts (2 hunks)
  • packages/project-builder-cli/src/services/user-config.ts (1 hunks)
  • packages/project-builder-server/src/index.ts (1 hunks)
  • packages/project-builder-server/src/runner/index.ts (3 hunks)
  • packages/project-builder-server/src/server/builder-service-manager.ts (3 hunks)
  • packages/project-builder-server/src/service/builder-service.ts (5 hunks)
  • packages/project-builder-server/src/service/environment-flags.ts (0 hunks)
  • packages/project-builder-server/src/sync/index.ts (5 hunks)
  • packages/project-builder-server/src/user-config/index.ts (1 hunks)
  • packages/project-builder-server/src/user-config/user-config-schema.ts (1 hunks)
  • packages/project-builder-test/src/commands/serve.ts (1 hunks)
  • packages/project-builder-test/src/runner/generate.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/project-builder-server/src/service/environment-flags.ts
🧰 Additional context used
🧬 Code Graph Analysis (4)
packages/project-builder-server/src/server/builder-service-manager.ts (1)
packages/project-builder-server/src/user-config/user-config-schema.ts (1)
  • BaseplateUserConfig (18-18)
packages/project-builder-server/src/sync/index.ts (1)
packages/project-builder-server/src/user-config/user-config-schema.ts (1)
  • BaseplateUserConfig (18-18)
packages/project-builder-server/src/service/builder-service.ts (1)
packages/project-builder-server/src/user-config/user-config-schema.ts (1)
  • BaseplateUserConfig (18-18)
packages/project-builder-server/src/runner/index.ts (1)
packages/project-builder-server/src/user-config/user-config-schema.ts (1)
  • BaseplateUserConfig (18-18)
🔇 Additional comments (22)
packages/project-builder-test/src/commands/serve.ts (1)

88-88: Implementation looks good, consider adding a comment for context.

The addition of userConfig: {} maintains compatibility with the new persistent user configuration feature. Since this is test code, an empty object is appropriate as a default value. Consider adding a brief comment to explain why an empty configuration is being used here.

    builtInPlugins,
    cliVersion,
+   // Empty user config for testing purposes
    userConfig: {},
  });
packages/project-builder-cli/src/commands/server.ts (3)

13-14: LGTM: Clean import for user config service.

The import is correctly placed and follows the project's import ordering pattern.


49-50: LGTM: Proper async loading of user configuration.

The user configuration is correctly loaded asynchronously before being needed by the service manager.


55-55: LGTM: Configuration correctly passed to service manager.

The user configuration is properly passed to the BuilderServiceManager constructor.

packages/project-builder-server/src/index.ts (1)

8-8: LGTM: Proper export of user configuration module.

This export makes the user configuration functionality available to consumers of this package, maintaining consistency with how other modules are exposed.

packages/project-builder-test/src/runner/generate.ts (1)

20-20: Implementation looks good, consider adding a comment for context.

The addition of userConfig: {} maintains compatibility with the new persistent user configuration feature. Consider adding a brief comment to explain why an empty configuration is appropriate in this test context.

    logger,
    context: nodeSchemaParserContext,
+   // Empty user config for testing purposes
    userConfig: {},
  });
packages/project-builder-server/src/user-config/index.ts (1)

1-1: LGTM: Clean module re-export pattern

This simple re-export pattern is a good practice for organizing and centralizing exports, making it easy to import all user configuration related code from a single entry point.

.changeset/loud-pandas-create.md (1)

2-4: LGTM: Proper patch version bumps

Appropriate patch version updates for both packages, as this adds functionality in a backward compatible way.

packages/project-builder-cli/src/commands/build.ts (3)

6-6: LGTM: Clean import of new user config functionality

Good addition of the getUserConfig import from the new user-config service.


26-26: LGTM: Proper async user config retrieval

Correctly retrieves user configuration asynchronously before passing it to the build function.


31-31: LGTM: Proper integration of user config

The userConfig is correctly passed to the buildProjectForDirectory function, completing the integration of persistent user configuration.

packages/project-builder-server/src/server/builder-service-manager.ts (3)

5-5: LGTM: Clean type import

Correctly imports the BaseplateUserConfig type from the user-config-schema module.


17-17: LGTM: Required userConfig parameter in constructor

Good practice to require the userConfig as part of the constructor options object, ensuring it's provided when creating a BuilderServiceManager instance.


37-37: LGTM: Proper propagation of user config

The userConfig is correctly passed down to the ProjectBuilderService, ensuring consistent configuration throughout the system.

packages/project-builder-server/src/runner/index.ts (2)

47-52: Making userConfig a required property ensures consistent configuration throughout the system.

The addition of the required userConfig property to the BuildProjectForDirectoryOptions interface is appropriate. It ensures that configuration is consistently passed through the build process.


64-71: Proper propagation of userConfig to downstream functions.

The userConfig is correctly passed to the generateForDirectory function for each application, ensuring that user preferences are applied consistently.

packages/project-builder-server/src/service/builder-service.ts (3)

65-71: Adding userConfig as a required option is consistent with the project's design.

Making userConfig a required property in ProjectBuilderServiceOptions ensures that it's always provided and propagated through the system.


99-100: Proper storage and initialization of the userConfig property.

The userConfig is correctly stored as a private property and initialized in the constructor, following the class's pattern for managing configuration.

Also applies to: 106-107, 117-118


235-241: Correctly passing userConfig to the buildProjectForDirectory function.

The userConfig is properly passed to the buildProjectForDirectory function, ensuring that user preferences are applied during the build process.

packages/project-builder-server/src/sync/index.ts (3)

23-23: Well-formed import of BaseplateUserConfig type

The change introduces the BaseplateUserConfig type import to replace the previous environmentFlags import, aligning with the new persistent configuration approach.


38-38: Interface correctly extended with userConfig

The GenerateForDirectoryOptions interface now properly includes the required userConfig property of type BaseplateUserConfig, ensuring type safety for the new configuration pattern.


110-110: Parameter correctly updated in function signature

The generateForDirectory function parameter destructuring now includes the userConfig parameter, consistent with the updated interface definition.

Comment thread .changeset/loud-pandas-create.md Outdated
@kingston kingston merged commit 8b04342 into main Apr 21, 2025
@kingston kingston deleted the kingston/eng-641-add-baseplate-user-settings-file-to-allow-global-user branch April 21, 2025 10:21
@github-actions github-actions Bot mentioned this pull request Apr 21, 2025
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