Skip to content

feat: Add Baseplate Development Server with MCP Integration#646

Merged
kingston merged 51 commits into
mainfrom
kingston/eng-858-implement-baseplate-development-server-with-mcp-integration
Sep 3, 2025
Merged

feat: Add Baseplate Development Server with MCP Integration#646
kingston merged 51 commits into
mainfrom
kingston/eng-858-implement-baseplate-development-server-with-mcp-integration

Conversation

@kingston

@kingston kingston commented Aug 31, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Interactive Dev Server with authenticated TRPC API and MCP support (HTTP & stdio) plus CLI commands to run Dev Server and MCP.
    • Server-side actions for diff, sync, template extraction/generation, and snapshot management; CLI integrations to invoke them.
    • Template tooling: configure (TS/Text/Raw), list, delete, and show template metadata.
  • Refactor
    • Centralized service action context and improved project discovery for consistent CLI/server behavior.
    • Evented structured logger with richer error serialization and message formats.
  • Documentation
    • Expanded Dev Server / MCP usage docs.
  • Chores
    • Zod bumped to 3.25.76 and assorted workspace/script/config updates.

@vercel

vercel Bot commented Aug 31, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
baseplate-project-builder-web Ready Ready Preview Comment Sep 3, 2025 4:56pm

@changeset-bot

changeset-bot Bot commented Aug 31, 2025

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cd86c61

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

This PR includes changesets to release 18 packages
Name Type
@baseplate-dev/sync Patch
@baseplate-dev/fastify-generators Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-storage Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/create-project 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 Aug 31, 2025

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a typed service-action framework with worker execution, a Fastify dev server (TRPC + MCP), new CLI commands (dev-server, mcp), many service actions (diff, sync, snapshots, template management), template utilities, plugin-discovery API change, structured evented logger, and assorted tooling/config updates (Zod bumps, exports, tests).

Changes

Cohort / File(s) Summary
CLI & wiring
packages/project-builder-cli/src/index.ts, packages/project-builder-cli/src/commands/{dev-server.ts,mcp.ts,diff.ts,sync.ts,server.ts}, packages/project-builder-cli/package.json, packages/project-builder-cli/src/utils/*
Adds dev-server and mcp commands; refactors diff/sync to invoke service actions; adds project discovery, service-action context creation, env config, list/resolve project helpers, and dev:dev-server script.
Service-action types & runtime
packages/project-builder-server/src/actions/types.ts, .../actions/index.ts, .../actions/registry.ts, .../actions/utils/{cli.ts,run-in-worker.ts,worker-script.ts,project-discovery.ts,project-id.ts,projects.ts,trpc.ts,index.ts}
Introduces ServiceAction types, ServiceActionContext/Project types, createServiceAction helper, invokeServiceActionAsCli, worker execution (worker-script + runActionInWorker), project discovery/loading, project ID generation, TRPC adapter, and centralized ALL_SERVICE_ACTIONS registry.
Dev server & MCP
packages/project-builder-server/src/dev-server/{api/router.ts,api/trpc.ts,auth-plugin.ts,get-or-create-config.ts,index.ts,server.ts}, .../dev-server/mcp/{fastify.ts,server.ts,stdio.ts}
New Fastify-based dev server class, auth plugin, deterministic per-root dev-server config, TRPC router bindings to actions, MCP server (HTTP + STDIO) and MCP Fastify plugin.
Actions: diff, sync, snapshot, templates, template-extractor
packages/project-builder-server/src/actions/{diff,sync,snapshot,template-extractor,templates}/**
Adds diffProjectAction, syncProjectAction, snapshot add/remove/save/show actions, template actions (configure/list/delete/show/generate/extract/discover), plus barrel exports and CLI writers.
Diff & sync internals
packages/project-builder-server/src/diff/diff-project.ts, packages/project-builder-server/src/actions/diff/*, packages/sync/src/**
Refactors diffProject to return structured DiffProjectResult (context-driven); sync action wiring to server; sync package: new structured evented logger, error serialization, tests and test utils updates.
Template utilities & metadata
packages/project-builder-server/src/templates/{configure,delete,list,show,utils}/**
New utilities for extractor.json management, template metadata (.templates-info.json), extractor config helpers, TS export inference (ts-morph), resolveFilePath/resolveGenerator, and corresponding unit tests.
Builder service & server wiring
packages/project-builder-server/src/server/builder-service-manager.ts, .../service/builder-service.ts, .../server/server.ts, packages/project-builder-test/src/commands/serve.ts
Injects ServiceActionContext into BuilderServiceManager/ProjectBuilderService; changes project ID generation; adjusts service initialization to use context.projects.
Plugin loader API & Vite configs
packages/project-builder-lib/src/plugin-tools/plugin-loader.ts, plugins/*/vite.config.ts
Replaces rewritePluginDirectory with overridePluginGlobs option; updates plugin Vite configs to use ['src/*/plugin.json'].
Utils & deps
packages/utils/src/string/{find-closest-match.ts,index.ts,find-closest-match.unit.test.ts}, packages/utils/src/fs/write-stable-pretty-json.ts, packages/utils/package.json
Adds findClosestMatch utility + tests; broadens writeStablePrettyJson param type; adds fastest-levenshtein dependency.
Sync extractor constants & path layout
packages/sync/src/templates/constants.ts, packages/sync/src/templates/extractor/utils/**
Adds EXTRACTOR/PROVIDERS filename constants and migrates extractor discovery/paths to /src layout; updates related tests and messages.
Workspace, tooling & metadata
.vscode/tasks.json, package.json, .gitignore, .prettierignore, .changeset/*, pnpm-workspace.yaml, .mcp.json, .claude/settings.json, CLAUDE.md
Watch build problemMatcher improvements; dev:serve parallel script pattern change; ignore .baseplate; prettier ignore tweak; changesets and pnpm catalog Zod bump; MCP and Claude config; docs updated (MCP section).
Package exports & deps
packages/project-builder-server/package.json
Adds exports for ./actions, ./dev-server, ./user-config; adds runtime deps @modelcontextprotocol/sdk and ts-morph.
Generator constants & examples
packages/{fastify-generators,react-generators}/src/constants/*.ts, examples/**/package.json, various example project files
Bumps Zod references to 3.25.76 in generator constants and example package.json files; minor example renames/README updates.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as CLI User
  participant CLI as project-builder-cli
  participant Ctx as ServiceActionContext
  participant SA as ServiceAction
  participant W as Worker

  U->>CLI: bp diff --package foo --include "src/**"
  CLI->>CLI: resolveProject() & createServiceActionContext()
  CLI->>SA: invokeServiceActionAsCli(diffProjectAction, input, Ctx)
  SA->>W: runActionInWorker(actionName, input, context)
  W-->>SA: success/result
  SA-->>CLI: action.writeCliOutput(result)
  CLI-->>U: printed summary
Loading
sequenceDiagram
  autonumber
  participant Client as Browser/Tool
  participant Dev as Fastify Dev Server
  participant Auth as Auth Plugin
  participant TRPC as TRPC Router
  participant Act as ServiceAction
  participant W as Worker

  Client->>Dev: POST /dev/trpc (Bearer token)
  Dev->>Auth: onRequest validation (host/origin/token)
  Auth-->>Dev: allowed
  Dev->>TRPC: route handler
  TRPC->>Act: invoke corresponding action
  Act->>W: runActionInWorker(...)
  W-->>Act: result / error
  Act-->>TRPC: return result
  TRPC-->>Client: HTTP JSON response
Loading
sequenceDiagram
  autonumber
  participant Editor as MCP Client
  participant Stdio as Stdio Transport
  participant MCP as MCP Server
  participant MCPsrv as MCP Server (actions)
  participant W as Worker

  Editor->>Stdio: initialize + requests
  Stdio->>MCP: transport connect
  MCP->>MCPsrv: invoke tool (e.g., sync-project)
  MCPsrv->>W: runActionInWorker(action, input, context)
  W-->>MCPsrv: action result
  MCPsrv-->>MCP: tool result
  MCP-->>Stdio: send response
  Stdio-->>Editor: deliver result
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kingston/eng-858-implement-baseplate-development-server-with-mcp-integration

🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit 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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@socket-security

socket-security Bot commented Aug 31, 2025

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedfastest-levenshtein@​1.0.161001008777100
Updated@​types/​node@​22.13.17 ⏵ 22.18.010010080 +196100
Added@​modelcontextprotocol/​sdk@​1.17.39910010097100

View full report

@kingston kingston merged commit 67dba69 into main Sep 3, 2025
16 of 17 checks passed
@kingston kingston deleted the kingston/eng-858-implement-baseplate-development-server-with-mcp-integration branch September 3, 2025 17:00
@github-actions github-actions Bot mentioned this pull request Aug 21, 2025

@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: 23

Caution

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

⚠️ Outside diff range comments (1)
CLAUDE.md (1)

326-873: Duplicate MCP section — deduplicate to avoid conflicting guidance.

There appear to be two “Baseplate Development Server MCP” sections. Keep a single canonical section.

♻️ Duplicate comments (2)
packages/project-builder-cli/src/utils/list-projects.ts (1)

126-135: Cross‑platform path detection: check isAbsolute on the tilde‑expanded path.

Expand first, then run path.isAbsolute(...) so Windows/posix home expansion is handled uniformly. Keeps the existing ~/. checks.

-  if (
-    projectNameOrDirectory.startsWith('.') ||
-    path.isAbsolute(projectNameOrDirectory) ||
-    projectNameOrDirectory.startsWith('~')
-  ) {
-    const resolvedPath = expandPathWithTilde(projectNameOrDirectory);
-    const projectInfo = await loadProjectFromDirectory(resolvedPath);
+  const expanded = expandPathWithTilde(projectNameOrDirectory);
+  if (
+    projectNameOrDirectory.startsWith('.') ||
+    projectNameOrDirectory.startsWith('~') ||
+    path.isAbsolute(expanded)
+  ) {
+    const projectInfo = await loadProjectFromDirectory(expanded);
     return projectInfo;
   }
packages/project-builder-server/src/actions/utils/run-in-worker.ts (1)

57-63: Validate log level before indexing logger.

Guard against unexpected levels to avoid runtime errors.

-      if (typedMessage.type === 'log') {
-        logger[typedMessage.level](
-          typedMessage.metadata ?? {},
-          typedMessage.message,
-        );
+      if (typedMessage.type === 'log') {
+        const validLevels = ['info', 'warn', 'error', 'debug'] as const;
+        const level = validLevels.includes(typedMessage.level as any)
+          ? typedMessage.level
+          : 'info';
+        logger[level](typedMessage.metadata ?? {}, typedMessage.message);
         return;
       }
🧹 Nitpick comments (47)
packages/project-builder-cli/src/utils/list-projects.ts (6)

28-40: Normalize/resolve directories before de‑duping to avoid duplicates and ensure consistency.

Normalize env, additional, and example directories via path.resolve(...) after tilde expansion so the Set truly de‑dupes and downstream discovery receives absolute paths.

-  const envDirectories = config.PROJECT_DIRECTORIES?.split(',') ?? [];
+  const envDirectories = config.PROJECT_DIRECTORIES?.split(',') ?? [];
   for (const dir of envDirectories) {
-    if (dir.trim()) {
-      allDirectories.add(expandPathWithTilde(dir.trim()));
+    const trimmed = dir.trim();
+    if (trimmed) {
+      const expanded = expandPathWithTilde(trimmed);
+      allDirectories.add(path.resolve(expanded));
     }
   }

   // Add explicitly provided directories
   for (const dir of additionalDirectories ?? []) {
-    allDirectories.add(expandPathWithTilde(dir));
+    const expanded = expandPathWithTilde(dir);
+    allDirectories.add(path.resolve(expanded));
   }

   // Add example directories if requested
   if (includeExamples) {
     const exampleDirs = await findExamplesDirectories();
     for (const dir of exampleDirs) {
-      allDirectories.add(dir);
+      allDirectories.add(path.resolve(dir));
     }
   }

Also applies to: 41-47


54-55: Return directories in a stable order.

Sorting improves determinism for logs/tests and makes behavior reproducible.

-  return [...allDirectories];
+  return [...allDirectories].sort();

15-15: Avoid reading env at import time; fetch per call for testability and late‑bound config.

This prevents stale config when process.env changes during tests/CLI sessions.

-const config = getEnvConfig();
+// Fetch env on demand within functions for freshness.
-  const envDirectories = config.PROJECT_DIRECTORIES?.split(',') ?? [];
+  const { PROJECT_DIRECTORIES } = getEnvConfig();
+  const envDirectories = PROJECT_DIRECTORIES?.split(',') ?? [];
-  const searchDirectories = await getSearchDirectories({
-    additionalDirectories,
-    includeExamples: config.INCLUDE_EXAMPLES ?? false,
-    defaultToCwd: true,
-  });
+  const searchDirectories = await getSearchDirectories({
+    additionalDirectories,
+    includeExamples: getEnvConfig().INCLUDE_EXAMPLES ?? false,
+    defaultToCwd: true,
+  });

Also applies to: 28-30, 86-90


141-143: Improve the not‑found error with available options.

Helps users discover valid names/IDs without re‑running another command.

-  if (!project) {
-    throw new Error(`Project ${projectNameOrDirectory} not found`);
-  }
+  if (!project) {
+    const available = projects.map((p) => `${p.name} (${p.id})`).join(', ');
+    throw new Error(
+      `Project "${projectNameOrDirectory}" not found. Available: ${available}`,
+    );
+  }

9-9: Import alias consistency with guidelines.

Guidelines specify the @src/ alias; this file uses #src/. Verify the configured alias and align for consistency.

-import { logger } from '#src/services/logger.js';
+import { logger } from '@src/services/logger.js';

If @src isn’t configured in this package, keep #src and update the guideline or add the alias to tsconfig/path exports for this package.


97-101: Doc nit: mention Windows/UNC and tilde‑expanded absolute detection.

The comment still references leading /. Clarify it now relies on path.isAbsolute(expanded), which covers Windows and UNC paths.

packages/project-builder-test/src/commands/serve.ts (2)

133-136: Fix incomplete CLI argument description

The help text currently reads “Serve ”.

Apply:

-    .argument('<project-name>', 'Serve ')
+    .argument('<project-name>', 'Project name to serve (will be generated if missing)')

89-96: Unify plugin discovery logging

  • Move the pinoLogger instantiation above the built-in plugin load and call await getDefaultPlugins(pinoLogger) (instead of logger) to consolidate all logs through Pino.
  • No change needed for userConfig: passing {} is valid since sync is optional in BaseplateUserConfig.
packages/utils/package.json (1)

52-52: Add to catalog or pin explicitly for consistency.

Using a caret range here while other deps use exact versions or the repo’s “catalog:” indirection can lead to drift. Prefer moving fastest-levenshtein to the version catalog or pinning it exactly to keep builds reproducible.

-    "fastest-levenshtein": "^1.0.16",
+    "fastest-levenshtein": "catalog:" // or pin "1.0.16"
packages/sync/src/templates/extractor/utils/merge-extractor-template-entries.ts (1)

33-35: Error message consistency improved.

Message now reflects the centralized filename; consider including the expected search location in a follow-up if users still struggle to locate configs.

packages/project-builder-server/src/templates/utils/infer-exports-from-ts-file.ts (2)

17-19: Optional: Reuse a ts-morph Project for performance.

Creating a new Project() per call is expensive. Consider accepting a Project parameter (with a default) or caching one instance.


67-85: Optional: Include enums (and mark const enums as type-only).

If you want parity with TS exportables, handle enum/const enum as well.

for (const enm of sourceFile.getEnums()) {
  if (enm.isExported()) {
    availableExports.set(enm.getName(), {
      name: enm.getName(),
      isTypeOnly: enm.isConstEnum(), // const enums are erased at runtime
    });
  }
}

Also consider sourceFile.getExportSymbols() for a single-pass, re-export-aware approach if you later need to resolve export * from ....

packages/project-builder-server/src/templates/utils/extractor-config.ts (1)

52-58: Tighten templateType to the allowed union.

Type it as TemplateConfig['type'] to prevent invalid values at call sites.

-export async function updateExtractorTemplate(
-  generatorDirectory: string,
-  templateName: string,
-  templateType = 'ts',
+export async function updateExtractorTemplate(
+  generatorDirectory: string,
+  templateName: string,
+  templateType: TemplateConfig['type'] = 'ts',
packages/sync/src/templates/extractor/utils/write-extractor-template-jsons.ts (1)

6-7: Good constant adoption; consider future-proofing the docs.

Replacing hard-coded 'extractor.json' with EXTRACTOR_CONFIG_FILENAME in both the error and write path looks correct.

  • Optional: Update the JSDoc to avoid hard-coding “extractor.json” so it won’t drift if the constant changes (e.g., “Writes extractor config files …”).

Apply this diff if you want to de-couple the doc from the filename:

- * Writes extractor.json files for the specified generators to the file system.
- * This function reads the current configurations from the context and writes them
- * to their respective extractor.json files.
+ * Writes extractor config files for the specified generators to the file system.
+ * This function reads the current configurations from the context and writes them
+ * to their respective config files.

Also applies to: 24-26, 31-33

packages/utils/src/string/find-closest-match.unit.test.ts (1)

1-70: Comprehensive coverage; one minor robustness tweak.

Great set of cases and proper Vitest imports. The “n greater than candidates length” test asserts exact ordering, which could become brittle if the distance function changes. Prefer checking membership and length instead of order for that case.

Apply:

-    expect(result).toEqual(['apple', 'banana']);
+    expect(result).toHaveLength(2);
+    expect(new Set(result)).toEqual(new Set(['apple', 'banana']));
packages/utils/src/string/find-closest-match.ts (1)

31-47: Micro-opt: fast path for n === 1 to avoid full sort.

For large candidate sets, a single scan is cheaper than O(N log N) sort.

   // Calculate distances for all candidates
-  const distances = candidates.map((candidate, index) => ({
+  const distances = candidates.map((candidate, index) => ({
     candidate,
     distance: distance(target, candidate),
     index,
   }));
 
+  // Fast path when only the single closest match is needed
+  if (n === 1) {
+    let best: { candidate: string; distance: number; index: number } | null = null;
+    for (let i = 0; i < candidates.length; i++) {
+      const d = distance(target, candidates[i]);
+      if (!best || d < best.distance) {
+        best = { candidate: candidates[i], distance: d, index: i };
+      }
+    }
+    return best ? [best.candidate] : [];
+  }
+
   // Sort by distance (ascending) and then by original index for stable sorting
   distances.sort((a, b) => {
     if (a.distance !== b.distance) {
       return a.distance - b.distance;
     }
     return a.index - b.index;
   });
packages/project-builder-server/src/actions/snapshot/index.ts (1)

1-4: Prefer barrel re-exports for index files

Align with our index.ts guideline by switching to star re-exports for consistency and easier maintenance.

-export { snapshotAddAction } from './snapshot-add.action.js';
-export { snapshotRemoveAction } from './snapshot-remove.action.js';
-export { snapshotSaveAction } from './snapshot-save.action.js';
-export { snapshotShowAction } from './snapshot-show.action.js';
+export * from './snapshot-add.action.js';
+export * from './snapshot-remove.action.js';
+export * from './snapshot-save.action.js';
+export * from './snapshot-show.action.js';
packages/project-builder-server/src/templates/utils/resolve-generator.ts (2)

6-6: Use tsconfig path alias instead of relative import

Our guideline prefers absolute alias imports. Switch to the #src/ alias for local modules.

-import { discoverGenerators } from '../../template-extractor/discover-generators.js';
+import { discoverGenerators } from '#src/template-extractor/discover-generators.js';

26-37: Improve not-found logic: case-insensitive match and clearer suggestions

Handle case-insensitive names and produce a helpful message when no close matches exist.

-    const generator = generators.find((gen) => gen.name === generatorName);
+    const target = generatorName.toLowerCase();
+    const generator = generators.find((gen) => gen.name.toLowerCase() === target);

     if (!generator) {
-      const availableGenerators = generators.map((g) => g.name);
-      const closestGenerators = findClosestMatch(
-        generatorName,
-        availableGenerators,
-        3,
-      );
-      throw new Error(
-        `Generator '${generatorName}' not found. Did you mean one of these: ${closestGenerators.join(', ')}?`,
-      );
+      const availableGenerators = generators.map((g) => g.name);
+      const closestGenerators = findClosestMatch(generatorName, availableGenerators, 3);
+      const suggestion =
+        closestGenerators.length > 0
+          ? ` Did you mean: ${closestGenerators.join(', ')}?`
+          : ' No similarly named generators were found.';
+      const available =
+        availableGenerators.length > 0
+          ? ` Available generators: ${availableGenerators.join(', ')}.`
+          : ' No generators were discovered.';
+      throw new Error(`Generator '${generatorName}' not found.${suggestion}${available}`);
     }
packages/project-builder-server/src/actions/templates/configure-raw-template.action.ts (2)

5-15: Validate kebab-case for template names at the schema

Enforce the documented kebab-case requirement to catch mistakes early.

-  templateName: z.string().describe('Template name in kebab-case format'),
+  templateName: z
+    .string()
+    .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Use kebab-case, e.g. my-template-name')
+    .describe('Template name in kebab-case format'),

31-37: Optional: export schemas for reuse

If other packages (CLI, dev server validators) need these, consider exporting the schemas to avoid duplication.

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

43-49: Avoid unsafe property reads; guard optional fields with runtime checks.

Use the in-operator and type narrowing instead of casting through Record<string, unknown>.

-      group: (templateConfig as Record<string, unknown>).group as
-        | string
-        | undefined,
-      kind: (templateConfig as Record<string, unknown>).kind as
-        | string
-        | undefined,
+      group:
+        'group' in templateConfig && typeof (templateConfig as any).group === 'string'
+          ? (templateConfig as any).group
+          : undefined,
+      kind:
+        'kind' in templateConfig && typeof (templateConfig as any).kind === 'string'
+          ? (templateConfig as any).kind
+          : undefined,
packages/project-builder-server/src/actions/templates/list-templates.action.ts (2)

5-10: Validate whether createServiceAction expects Zod shapes or Zod objects.

If Zod objects are required elsewhere, wrap these shapes with z.object(...) for uniform validation.

-const listTemplatesInputSchema = {
+const listTemplatesInputSchema = z.object({
   generatorDirectory: z
     .string()
     .describe('The directory path containing the generator'),
-};
+});
 
-const listTemplatesOutputSchema = {
+const listTemplatesOutputSchema = z.object({
   message: z.string().describe('Success message'),
   generatorName: z.string().describe('The name of the generator'),
   generatorDirectory: z.string().describe('The generator directory'),
   templates: z.array(templateInfoSchema).describe('List of templates'),
   templateCount: z.number().describe('Total number of templates'),
-};
+});

Also applies to: 20-26


17-18: Avoid z.any(); prefer z.unknown() for safer typing.

Keeps unknown config opaque while still serializable.

-  config: z.record(z.any()).describe('The full template configuration'),
+  config: z.record(z.unknown()).describe('The full template configuration'),
packages/project-builder-server/src/actions/snapshot/snapshot-add.action.ts (2)

79-83: Return actual added count (if available) vs attempted.

filesAdded: files.length counts inputs, not the net additions (unchanged files may be skipped). Consider plumbing a count from addFilesToSnapshot.


85-93: Include error stack in logs for diagnosis.

Improve observability while keeping user-facing message clean.

-      logger.error(
-        `Failed to add files to snapshot for project ${projectId}: ${String(error)}`,
-      );
+      logger.error(
+        `Failed to add files to snapshot for project ${projectId}: ${
+          error instanceof Error ? error.stack ?? error.message : String(error)
+        }`,
+      );
packages/project-builder-server/src/templates/utils/resolve-file-path.ts (2)

70-72: Permit directory identifier in addition to name/id for parity with getProjectByNameOrId.

-  const matchingProject = projects.find(
-    (proj) => proj.name === projectNameOrId || proj.id === projectNameOrId,
-  );
+  const matchingProject = projects.find(
+    (proj) =>
+      proj.name === projectNameOrId ||
+      proj.id === projectNameOrId ||
+      proj.directory === projectNameOrId,
+  );

75-78: Improve error message with more context (name/id/dir).

-    const availableProjects = projects.map((p) => p.name).join(', ');
+    const availableProjects = projects
+      .map((p) => `${p.name} (${p.id}) @ ${p.directory}`)
+      .join(', ');
packages/project-builder-server/src/actions/snapshot/snapshot-show.action.ts (1)

146-181: CLI output LGTM; consider optionally showing diff filenames.

Small UX improvement: include diffFile alongside modified paths when present.

-        for (const entry of output.files.modified) {
-          console.info(`  ${entry.path}`);
-        }
+        for (const entry of output.files.modified) {
+          const diffInfo = entry.diffFile ? ` (diff: ${entry.diffFile})` : '';
+          console.info(`  ${entry.path}${diffInfo}`);
+        }
packages/project-builder-server/src/templates/utils/template-metadata.unit.test.ts (3)

9-11: Rely on project’s Vitest automocks for fs/memfs.

You don’t need explicit vi.mock calls here; the suite is already configured to automock node:fs and node:fs/promises with memfs. This reduces noise across tests.

-vi.mock('node:fs');
-vi.mock('node:fs/promises');

37-49: Reduce repetition when reading metadata from memfs.

Consider a small helper to read/parse .templates-info.json to DRY the Arrange/Assert sections and improve failure messages.

Example helper (add atop the file):

function readTemplatesInfo(dir = '/project/src/components'): Record<string, unknown> {
  const files = vol.toJSON();
  const fileContent = files[`${dir}/.templates-info.json`];
  return JSON.parse(String(fileContent || '{}'));
}

Also applies to: 80-96, 127-138, 155-166, 195-206, 273-279


141-166: Add coverage for preserving existing instanceData when updating without it.

updateTemplateMetadata should keep prior instanceData for a file when called without instanceData and metadata already exists; add a test to lock this behavior.

Also applies to: 248-281

packages/project-builder-server/src/actions/templates/delete-template.action.ts (1)

39-41: Prefer tsconfig path alias for consistency.

Most actions use the '#src/' alias; consider switching this import to '#src/templates/delete/delete-template.js' for consistency.

-    const { deleteTemplate } = await import(
-      '../../templates/delete/delete-template.js'
-    );
+    const { deleteTemplate } = await import(
+      '#src/templates/delete/delete-template.js'
+    );
packages/project-builder-server/src/actions/registry.ts (1)

23-39: Make registry immutable.

Mark the array as readonly to prevent accidental mutation at runtime.

-export const ALL_SERVICE_ACTIONS = [
+export const ALL_SERVICE_ACTIONS = [
   diffProjectAction,
   syncProjectAction,
   discoverGeneratorsAction,
   extractTemplatesAction,
   generateTemplatesAction,
   configureTsTemplateAction,
   configureTextTemplateAction,
   configureRawTemplateAction,
   deleteTemplateAction,
   listTemplatesAction,
   showTemplateMetadataAction,
   snapshotAddAction,
   snapshotRemoveAction,
   snapshotSaveAction,
   snapshotShowAction,
-];
+] as const;
packages/project-builder-server/src/actions/utils/run-in-worker.ts (1)

47-53: Extract timeout to a constant for easier tuning.

Small readability/maintainability win.

-    const timeout = setTimeout(() => {
+    const ACTION_TIMEOUT_MS = 60_000;
+    const timeout = setTimeout(() => {
       worker.terminate().catch((err: unknown) => {
         context.logger.error(err);
       });
       reject(new Error(`Action execution timed out for ${serviceAction.name}`));
-    }, 60_000); // 60 second timeout
+    }, ACTION_TIMEOUT_MS);
packages/project-builder-server/src/actions/templates/show-template-metadata.action.ts (1)

43-45: Use #src path alias in dynamic import for consistency and to avoid brittle relatives.

-    const { showTemplateMetadata } = await import(
-      '../../templates/show/show-template-metadata.js'
-    );
+    const { showTemplateMetadata } = await import(
+      '#src/templates/show/show-template-metadata.js'
+    );
packages/project-builder-server/src/dev-server/api/router.ts (1)

18-19: Prefer alias import for trpc to match the rest of the file.

-import { devRouter, devTrpcActionBuilder } from './trpc.js';
+import { devRouter, devTrpcActionBuilder } from '#src/dev-server/api/trpc.js';
packages/project-builder-server/src/actions/templates/configure-ts-template.action.ts (1)

21-25: Default projectExports at the schema level; drop handler default.
This simplifies handler typing and keeps defaults centralized.

   projectExports: z
-    .array(z.string())
-    .optional()
+    .array(z.string())
+    .default([])
     .describe('Array of identifiers to expose as exports for other generators'),
-    const {
+    const {
       filePath,
       project,
       generator,
       templateName,
-      projectExports = [],
+      projectExports,
       group,
     } = input;

Also applies to: 49-56

packages/project-builder-server/src/actions/snapshot/snapshot-remove.action.ts (2)

12-15: Require a non-empty files list to avoid no-op calls.

   files: z
-    .array(z.string())
-    .describe('Array of file paths to remove from snapshot.'),
+    .array(z.string())
+    .nonempty('Provide at least one file path')
+    .describe('Array of file paths to remove from snapshot.'),

7-7: Use #src alias for internal imports to align with the rest of the file.

-import { getProjectByNameOrId } from '../utils/projects.js';
+import { getProjectByNameOrId } from '#src/actions/utils/projects.js';
packages/project-builder-server/src/actions/templates/configure-text-template.action.ts (2)

10-11: Trim variable values to avoid accepting whitespace-only strings

Add .trim() before .min(1) so " " is rejected.

-  value: z.string().min(1).describe('The value of the variable'),
+  value: z.string().trim().min(1).describe('The value of the variable'),

22-23: Enforce kebab-case for templateName at the schema level

The description mentions kebab-case but it isn’t validated.

-  templateName: z.string().describe('Template name in kebab-case format'),
+  templateName: z
+    .string()
+    .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Use kebab-case, e.g. my-template-name')
+    .describe('Template name in kebab-case format'),
packages/project-builder-server/src/templates/utils/template-metadata.ts (2)

92-95: Make delete resilient if file disappears between read and unlink

Avoid throwing on ENOENT by handling not-found on unlink.

-  await (Object.keys(updatedMetadata).length === 0
-    ? fs.unlink(metadataPath)
-    : writeStablePrettyJson(metadataPath, updatedMetadata));
+  await (Object.keys(updatedMetadata).length === 0
+    ? fs.unlink(metadataPath).catch(handleFileNotFoundError)
+    : writeStablePrettyJson(metadataPath, updatedMetadata));

Alternatively, use fs.rm(metadataPath, { force: true }).


25-28: Minor DRY: factor repeated path computations

Optional helper to compute { metadataPath, fileName } to simplify maintenance.

Example:

function getPaths(filePath: string) {
  const dirPath = path.dirname(filePath);
  return {
    metadataPath: path.join(dirPath, TEMPLATES_INFO_FILENAME),
    fileName: path.basename(filePath),
  };
}

Also applies to: 53-56, 75-78

packages/project-builder-server/src/templates/delete/delete-template.ts (1)

85-86: Optional: simplify not-found handling with fs.rm({ force: true })

This avoids the .catch(handleFileNotFoundError) pattern.

-  // Delete the generated file itself
-  await fs.unlink(absolutePath).catch(handleFileNotFoundError);
+  // Delete the generated file itself
+  await fs.rm(absolutePath, { force: true });

Note: if you adopt fs.rm here (and above), drop the now-unused handleFileNotFoundError import.

packages/project-builder-server/src/templates/configure/configure-text-template.ts (1)

60-69: Trim and validate variable values

Reject whitespace-only values and use the trimmed value for presence checks to avoid false negatives.

-  for (const [variable, { value }] of Object.entries(variables ?? {})) {
-    if (value === '') {
-      throw new Error(`Variable ${variable} must have a value`);
-    }
-    if (!templateContents.includes(value)) {
-      throw new Error(
-        `Variable ${variable} with value ${value} not found in template ${absolutePath}`,
-      );
-    }
-  }
+  for (const [variable, { value }] of Object.entries(variables ?? {})) {
+    const v = value.trim();
+    if (v.length === 0) {
+      throw new Error(`Variable '${variable}' must have a non-empty value`);
+    }
+    if (!templateContents.includes(v)) {
+      throw new Error(
+        `Variable '${variable}' with value '${v}' not found in template '${absolutePath}'`,
+      );
+    }
+  }
packages/project-builder-server/src/templates/configure/configure-ts-template.ts (1)

39-45: Optional: assert TS file extension early

Quickly fail if a non-TS file is passed to the TS template configurator.

   const { absolutePath, project, projectRelativePath } = resolveFilePath(
     filePath,
     projects,
     projectNameOrId,
   );
+  if (!absolutePath.endsWith('.ts') && !absolutePath.endsWith('.tsx')) {
+    throw new Error(`File must be .ts or .tsx: '${absolutePath}'`);
+  }

Comment thread .mcp.json
Comment on lines +1 to +8
{
"mcpServers": {
"baseplate-dev-server": {
"command": "mise",
"args": ["exec", "--", "pnpm", "--silent", "start", "mcp"]
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify the start script invocation; prefer a dedicated mcp script for clarity.

pnpm start mcp forwards “mcp” as an arg to the start script, which may be ignored depending on the script. Consider invoking a dedicated mcp script to avoid ambiguity.

-      "args": ["exec", "--", "pnpm", "--silent", "start", "mcp"]
+      "args": ["exec", "--", "pnpm", "--silent", "run", "mcp"]

Run to verify scripts exist and intended entrypoints are present:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Root start script:" && jq -r '.scripts.start // "MISSING"' package.json
echo "Root mcp script:" && jq -r '.scripts.mcp // "MISSING"' package.json
echo "CLI MCP command file:" && fd -a 'mcp.ts' -g 'packages/**/src/commands/mcp.ts' -x echo {}

Length of output: 513


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Root package.json mcp script:"
jq -r '.scripts.mcp // "MISSING"' package.json

echo "All package.json mcp scripts in workspace:"
find . -name package.json | while read -r pkg; do
  echo "$pkg:" 
  jq -r '.scripts.mcp // "MISSING"' "$pkg"
done

echo "Finding all mcp.ts files in the repo:"
find . -type f -name 'mcp.ts'

Length of output: 371055


Add a dedicated mcp npm script or keep the explicit filtered start — don’t switch to run mcp unless a script exists

Root package.json start = "pnpm run --filter @baseplate-dev/project-builder-cli start"; no mcp script was found in any package.json.
Action: either add a package.json script named mcp and update .mcp.json to call pnpm run mcp, or keep the current invocation but make the intended target explicit (e.g. run the filtered package start with the mcp arg).
File: .mcp.json (lines 1-8)

🤖 Prompt for AI Agents
.mcp.json lines 1-8: the current command uses "pnpm --silent start mcp" but
there is no package.json script named "mcp", so update either the package(s) or
the .mcp.json to make the target explicit: Option A — add an "mcp" script to the
appropriate package.json (or root package.json) that runs the intended start
command, then change .mcp.json to invoke "pnpm run mcp"; Option B — keep the
explicit filtered invocation and change .mcp.json to call the filtered start
directly (e.g. "pnpm run --filter @baseplate-dev/project-builder-cli start" with
the mcp arg preserved) so it targets the correct package without relying on a
missing "mcp" script. Ensure the command and args in .mcp.json match whichever
approach you choose.

import { addFilesToSnapshot } from '#src/diff/snapshot/snapshot-management.js';
import { createNodeSchemaParserContext } from '#src/plugins/node-plugin-store.js';

import { getProjectByNameOrId } from '../utils/projects.js';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use alias import for internal utility.

Standardize on alias paths per guidelines.

-import { getProjectByNameOrId } from '../utils/projects.js';
+import { getProjectByNameOrId } from '#src/actions/utils/projects.js';
📝 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
import { getProjectByNameOrId } from '../utils/projects.js';
import { getProjectByNameOrId } from '#src/actions/utils/projects.js';
🤖 Prompt for AI Agents
In packages/project-builder-server/src/actions/snapshot/snapshot-add.action.ts
around line 7, replace the relative import "import { getProjectByNameOrId } from
'../utils/projects.js';" with the project's configured alias import (e.g. import
{ getProjectByNameOrId } from '@project-builder-server/utils/projects';) to
conform to alias path guidelines; update the file extension to match the source
(omit .js if aliases point to TS sources) and ensure the alias is defined in
tsconfig/paths or build config if not already.

Comment on lines +83 to +87
return {
success: true,
message: `Snapshot saved successfully for ${project.name}/${app}`,
snapshotPath: `${project.directory}/${app}/${snapshotDirectory}`,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Returned snapshotPath is likely incorrect; avoid fabricating it.

createSnapshotForProject resolves appDirectory internally (not equal to /${app} in general). Returning ${project.directory}/${app}/${snapshotDirectory} can mislead users and tooling.

Minimal safe fix (omit the path until the source of truth returns it):

-      return {
-        success: true,
-        message: `Snapshot saved successfully for ${project.name}/${app}`,
-        snapshotPath: `${project.directory}/${app}/${snapshotDirectory}`,
-      };
+      return {
+        success: true,
+        message: `Snapshot saved successfully for ${project.name}/${app}`,
+      };

Follow-up (recommended): update createSnapshotForProject to return { snapshotPath, fileCount } and plumb it through to populate snapshotPath accurately. I can draft that change if you want.

📝 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
return {
success: true,
message: `Snapshot saved successfully for ${project.name}/${app}`,
snapshotPath: `${project.directory}/${app}/${snapshotDirectory}`,
};
return {
success: true,
message: `Snapshot saved successfully for ${project.name}/${app}`,
};
🤖 Prompt for AI Agents
In packages/project-builder-server/src/actions/snapshot/snapshot-save.action.ts
around lines 83 to 87, the returned snapshotPath is being fabricated as
`${project.directory}/${app}/${snapshotDirectory}` but appDirectory is resolved
inside createSnapshotForProject and may differ; remove the snapshotPath property
from the returned object (or set it to undefined) so we don't return an
incorrect path, update the function's return type/interface accordingly, and
leave a TODO comment to follow up by changing createSnapshotForProject to return
{ snapshotPath, fileCount } and plumb that value through to this response in a
future change.

import { createServiceAction } from '#src/actions/types.js';
import { createNodeSchemaParserContext } from '#src/plugins/node-plugin-store.js';

import { getProjectByNameOrId } from '../utils/projects.js';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use alias import for internal utility.

Align with project-wide import conventions.

-import { getProjectByNameOrId } from '../utils/projects.js';
+import { getProjectByNameOrId } from '#src/actions/utils/projects.js';

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/project-builder-server/src/actions/snapshot/snapshot-show.action.ts
around line 6, replace the relative import "import { getProjectByNameOrId } from
'../utils/projects.js';" with the project-wide alias import (e.g. import {
getProjectByNameOrId } from '@/utils/projects';) so the module follows the
repository's alias import convention; ensure the file extension and path match
your tsconfig/webpack alias settings.

Comment on lines +35 to +63
handler: async (input, context) => {
const { filePath, project } = input;
const { logger } = context;

const { deleteTemplate } = await import(
'../../templates/delete/delete-template.js'
);

const result = await deleteTemplate(
{
filePath,
project,
},
context,
);

logger.info(result.message);

return {
success: true,
message: result.message,
templateName: result.templateName,
absolutePath: result.absolutePath,
generatorDirectory: result.generatorDirectory,
};
},
writeCliOutput: (output) => {
console.info(`✅ ${output.message}`);
},

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

Return structured errors and align CLI output on failure.

Handler always returns success: true and doesn’t catch errors. Wrap in try/catch, return success: false on failures, and print via console.error accordingly.

   handler: async (input, context) => {
     const { filePath, project } = input;
     const { logger } = context;

-    const { deleteTemplate } = await import(
-      '../../templates/delete/delete-template.js'
-    );
-
-    const result = await deleteTemplate(
-      {
-        filePath,
-        project,
-      },
-      context,
-    );
-
-    logger.info(result.message);
-
-    return {
-      success: true,
-      message: result.message,
-      templateName: result.templateName,
-      absolutePath: result.absolutePath,
-      generatorDirectory: result.generatorDirectory,
-    };
+    try {
+      const { deleteTemplate } = await import(
+        '../../templates/delete/delete-template.js'
+      );
+      const result = await deleteTemplate({ filePath, project }, context);
+      logger.info(result.message);
+      return {
+        success: true,
+        message: result.message,
+        templateName: result.templateName,
+        absolutePath: result.absolutePath,
+        generatorDirectory: result.generatorDirectory,
+      };
+    } catch (err) {
+      const message =
+        err instanceof Error ? err.message : `Unknown error: ${String(err)}`;
+      logger.error({ err }, `Failed to delete template for ${filePath}`);
+      return {
+        success: false,
+        message: `Failed to delete template: ${message}`,
+        templateName: '',
+        absolutePath: '',
+        generatorDirectory: '',
+      };
+    }
   },
   writeCliOutput: (output) => {
-    console.info(`✅ ${output.message}`);
+    if (output.success) {
+      console.info(`✅ ${output.message}`);
+    } else {
+      console.error(`✗ ${output.message}`);
+    }
   },
📝 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
handler: async (input, context) => {
const { filePath, project } = input;
const { logger } = context;
const { deleteTemplate } = await import(
'../../templates/delete/delete-template.js'
);
const result = await deleteTemplate(
{
filePath,
project,
},
context,
);
logger.info(result.message);
return {
success: true,
message: result.message,
templateName: result.templateName,
absolutePath: result.absolutePath,
generatorDirectory: result.generatorDirectory,
};
},
writeCliOutput: (output) => {
console.info(`✅ ${output.message}`);
},
handler: async (input, context) => {
const { filePath, project } = input;
const { logger } = context;
try {
const { deleteTemplate } = await import(
'../../templates/delete/delete-template.js'
);
const result = await deleteTemplate({ filePath, project }, context);
logger.info(result.message);
return {
success: true,
message: result.message,
templateName: result.templateName,
absolutePath: result.absolutePath,
generatorDirectory: result.generatorDirectory,
};
} catch (err) {
const message =
err instanceof Error ? err.message : `Unknown error: ${String(err)}`;
logger.error({ err }, `Failed to delete template for ${filePath}`);
return {
success: false,
message: `Failed to delete template: ${message}`,
templateName: '',
absolutePath: '',
generatorDirectory: '',
};
}
},
writeCliOutput: (output) => {
if (output.success) {
console.info(`✅ ${output.message}`);
} else {
console.error(`✗ ${output.message}`);
}
},
🤖 Prompt for AI Agents
In
packages/project-builder-server/src/actions/templates/delete-template.action.ts
around lines 35 to 63, the handler currently always returns success: true and
doesn't handle thrown errors; wrap the main logic in a try/catch, call
deleteTemplate inside the try, and on success return success: true with the
existing fields; in the catch log the error via context.logger.error, return
success: false with a helpful message (and set templateName, absolutePath,
generatorDirectory to null or undefined), and update writeCliOutput to call
console.error when output.success is false (keeping console.info for success).

Comment on lines +87 to +96
// Check for default export
const defaultExportSymbol = sourceFile.getDefaultExportSymbol();
if (defaultExportSymbol) {
const name = defaultExportSymbol.getName();
availableExports.set(name, {
name,
isDefault: true,
isTypeOnly: 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.

🛠️ Refactor suggestion

Normalize default export key to 'default'.

Using defaultExportSymbol.getName() can yield non-'default' values depending on declaration shapes. Downstream importers expect the key 'default'.

-    const defaultExportSymbol = sourceFile.getDefaultExportSymbol();
-    if (defaultExportSymbol) {
-      const name = defaultExportSymbol.getName();
-      availableExports.set(name, {
-        name,
-        isDefault: true,
-        isTypeOnly: false,
-      });
-    }
+    if (sourceFile.getDefaultExportSymbol()) {
+      availableExports.set('default', {
+        name: 'default',
+        isDefault: true,
+        isTypeOnly: false,
+      });
+    }
📝 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
// Check for default export
const defaultExportSymbol = sourceFile.getDefaultExportSymbol();
if (defaultExportSymbol) {
const name = defaultExportSymbol.getName();
availableExports.set(name, {
name,
isDefault: true,
isTypeOnly: false,
});
}
// Check for default export
if (sourceFile.getDefaultExportSymbol()) {
availableExports.set('default', {
name: 'default',
isDefault: true,
isTypeOnly: false,
});
}
🤖 Prompt for AI Agents
In
packages/project-builder-server/src/templates/utils/infer-exports-from-ts-file.ts
around lines 87 to 96, the code uses defaultExportSymbol.getName() as the map
key which can produce non-'default' keys; change it to always use the literal
key 'default' (and set the export object's name to 'default' or otherwise ensure
the map key is 'default') so downstream importers receive the normalized
'default' key while keeping isDefault: true and isTypeOnly: false.

@@ -0,0 +1,86 @@
import path from 'node:path';

import type { ProjectInfo } from '../../api/projects.js';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Adopt alias import for internal types.

-import type { ProjectInfo } from '../../api/projects.js';
+import type { ProjectInfo } from '#src/api/projects.js';
📝 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
import type { ProjectInfo } from '../../api/projects.js';
import type { ProjectInfo } from '#src/api/projects.js';
🤖 Prompt for AI Agents
In packages/project-builder-server/src/templates/utils/resolve-file-path.ts
around line 3, the import currently uses a relative path for the internal
ProjectInfo type; replace the relative import with the project's configured
alias import (e.g. use the internal alias defined in tsconfig/paths such as
@project/api/projects or #api/projects) so the module uses the canonical
internal alias for shared types; update the import statement accordingly and
ensure the project builds by adjusting any path mappings if needed.

Comment on lines +5 to +12
interface ResolvedFilePath {
/** The absolute file path */
absolutePath: string;
/** The relative path from the project directory */
projectRelativePath: string;
/** The project that contains this file */
project: ProjectInfo;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Export the return type to make the function’s API consumable.

This type is part of the public contract of resolveFilePath.

-interface ResolvedFilePath {
+export interface ResolvedFilePath {
📝 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
interface ResolvedFilePath {
/** The absolute file path */
absolutePath: string;
/** The relative path from the project directory */
projectRelativePath: string;
/** The project that contains this file */
project: ProjectInfo;
}
export interface ResolvedFilePath {
/** The absolute file path */
absolutePath: string;
/** The relative path from the project directory */
projectRelativePath: string;
/** The project that contains this file */
project: ProjectInfo;
}
🤖 Prompt for AI Agents
In packages/project-builder-server/src/templates/utils/resolve-file-path.ts
around lines 5 to 12, the ResolvedFilePath interface is declared but not
exported; export it so resolveFilePath's return type is part of the module's
public API. Change the interface declaration to an exported interface (export
interface ResolvedFilePath { ... }) and update any local usages/imports if
necessary so other modules can import and reference this type for the function's
return value.

Comment on lines +43 to +46
const matchingProject = projects.find((proj) =>
absolutePath.startsWith(`${proj.directory}${path.sep}`),
);

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

Fix absolute path containment check (root-equality bug, cross-platform robustness).

startsWith fails when absolutePath === proj.directory. Use path.relative containment logic.

-  const matchingProject = projects.find((proj) =>
-    absolutePath.startsWith(`${proj.directory}${path.sep}`),
-  );
+  const matchingProject = projects.find((proj) => {
+    const rel = path.relative(proj.directory, absolutePath);
+    return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
+  });
📝 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 matchingProject = projects.find((proj) =>
absolutePath.startsWith(`${proj.directory}${path.sep}`),
);
const matchingProject = projects.find((proj) => {
const rel = path.relative(proj.directory, absolutePath);
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
});
🤖 Prompt for AI Agents
In packages/project-builder-server/src/templates/utils/resolve-file-path.ts
around lines 43-46, replace the startsWith containment check (which misses the
case where absolutePath === proj.directory and is fragile across platforms) with
a path.relative-based check: normalize/resolve both proj.directory and
absolutePath, compute path.relative(dir, absolutePath) and treat the path as
contained if the relative result is '' (same path) or does not start with '..'
(and not equal to '..'), taking care to use path.sep comparisons for
cross-platform correctness.

Comment on lines +68 to +71
const extractorJsonPath = path.join(
generatorDirectory,
EXTRACTOR_CONFIG_FILENAME,
);

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

Path join is correct here, but there’s a critical glob bug above.

The extractorJsonPath construction via the constant looks good. However, Line 36 builds the glob with path.join('**', \/${generatorBasename}.generator.ts`). The leading slash makes the second segment absolute on POSIX, discarding the **` and likely breaking discovery. Fix the glob to avoid the leading slash.

-  const searchPattern = path.join('**', `/${generatorBasename}.generator.ts`);
+  // Keep it a glob, not an absolute path
+  const searchPattern = `**/${generatorBasename}.generator.ts`;
📝 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 extractorJsonPath = path.join(
generatorDirectory,
EXTRACTOR_CONFIG_FILENAME,
);
// Keep it a glob, not an absolute path
const searchPattern = `**/${generatorBasename}.generator.ts`;

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