Skip to content

Add client steps implementation to support existing functionalities#6966

Open
alfonso-noriega wants to merge 3 commits intomainfrom
03-10-add_client_steps_implementation_to_support_existing_functionalities
Open

Add client steps implementation to support existing functionalities#6966
alfonso-noriega wants to merge 3 commits intomainfrom
03-10-add_client_steps_implementation_to_support_existing_functionalities

Conversation

@alfonso-noriega
Copy link
Contributor

@alfonso-noriega alfonso-noriega commented Mar 10, 2026

Add build step infrastructure for extension asset management

WHY are these changes introduced?

This introduces a flexible build step system to handle various asset copying and processing needs for different extension types, replacing hardcoded build logic with configurable steps.

WHAT is this pull request doing?

  • Adds 'admin' to the CONFIG_EXTENSION_IDS array for admin extensions
  • Creates new build step executors:
    • executeIncludeAssetsStep - handles pattern-based, static, and config-key-driven asset copying with comprehensive test coverage
    • executeCopyStaticAssetsStep - copies static assets defined in extension build manifests
    • executeCreateTaxStubStep - generates minimal JavaScript stub files for tax calculation extensions
  • Implements a centralized step router (executeStepByType) that dispatches to appropriate handlers based on step type
  • Supports multiple inclusion strategies:
    • Pattern-based file selection using glob patterns
    • Static file/directory copying with optional destination paths
    • Configuration key resolution for dynamic path discovery
    • Configurable structure preservation and flattening options

How to test your changes?

  1. Create an extension with various asset types (static files, pattern-matched files, config-driven paths)
  2. Configure build steps using the new include_assets step type with different inclusion strategies
  3. Run the build process and verify assets are copied correctly to output directories
  4. Test with admin extensions to ensure they're recognized as config extensions
  5. Run the comprehensive test suite for include-assets-step.test.ts

Measuring impact

How do we know this change was effective? Please choose one:

  • n/a - this doesn't need measurement, e.g. a linting rule or a bug-fix

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes

Copy link
Contributor Author

alfonso-noriega commented Mar 10, 2026

@alfonso-noriega alfonso-noriega marked this pull request as ready for review March 10, 2026 08:35
@alfonso-noriega alfonso-noriega requested a review from a team as a code owner March 10, 2026 08:35
@github-actions
Copy link
Contributor

We detected some changes at packages/*/src and there are no updates in the .changeset.
If the changes are user-facing, run pnpm changeset add to track your changes and include them in the next release CHANGELOG.

Caution

DO NOT create changesets for features which you do not wish to be included in the public changelog of the next CLI release.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 10, 2026

Coverage report

St.
Category Percentage Covered / Total
🟢 Statements 82.07% 15105/18404
🟡 Branches 74.61% 7480/10025
🟢 Functions 81.17% 3798/4679
🟢 Lines 82.48% 14286/17320

Test suite run success

3951 tests passing in 1518 suites.

Report generated by 🧪jest coverage report action from b32f3a9

@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch 2 times, most recently from 9c2e1fd to 7e48497 Compare March 10, 2026 09:04
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 16768f3 to 59f0139 Compare March 10, 2026 09:59
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 7e48497 to 159bb73 Compare March 10, 2026 09:59
@alfonso-noriega alfonso-noriega changed the base branch from 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations to graphite-base/6966 March 10, 2026 10:29
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 159bb73 to e6f3577 Compare March 10, 2026 10:29
@alfonso-noriega alfonso-noriega changed the base branch from graphite-base/6966 to 03-10-clean_up_module_outputfile_calculation_and_move_it_to_the_specifications March 10, 2026 10:29
config.inclusions.map(async (entry) => {
if (entry.type === 'pattern') {
const sourceDir = entry.baseDir ? joinPath(extension.directory, entry.baseDir) : extension.directory
const destinationDir = entry.destination ? joinPath(outputDir, entry.destination) : outputDir

Choose a reason for hiding this comment

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

Path traversal: include_assets can write outside outputDir

joinPath(outputDir, entry.destination) is used directly with user-controlled config (destination). If destination is absolute (/etc) or contains ../.., it can escape outputDir and overwrite arbitrary files on the machine running the build (CI runner, developer machine). Same class of issue exists for entry.baseDir (controls sourceDir), copySourceEntry(...destination) (static destination), copyConfigKeyEntry(...destination) (configKey destination), and copyConfigKeyEntry where the config value itself can be ../../something.

Because build manifests/config are extension-provided, a malicious extension (or compromised repo) could cause the CLI to overwrite sensitive files or poison other build artifacts.

@binks-code-reviewer
Copy link

binks-code-reviewer bot commented Mar 10, 2026

🤖 Code Review · #projects-dev-ai for questions
React with 👍/👎 or reply — all feedback helps improve the agent.

Complete - 2 findings

📋 History

❌ Failed → ✅ 1 findings → ❌ Failed → ❌ Failed → ✅ 1 findings → ✅ 2 findings

@alfonso-noriega alfonso-noriega force-pushed the 03-10-clean_up_module_outputfile_calculation_and_move_it_to_the_specifications branch from bbc0817 to 7b68eb7 Compare March 10, 2026 11:05
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from e6f3577 to 3f28156 Compare March 10, 2026 11:05
@alfonso-noriega alfonso-noriega changed the base branch from 03-10-clean_up_module_outputfile_calculation_and_move_it_to_the_specifications to graphite-base/6966 March 10, 2026 11:11
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 3f28156 to 44034c5 Compare March 10, 2026 11:11
@alfonso-noriega alfonso-noriega changed the base branch from graphite-base/6966 to 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations March 10, 2026 11:12
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from f9f9e63 to 59dcaa7 Compare March 10, 2026 11:24
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 44034c5 to 094a68c Compare March 10, 2026 11:24
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 59dcaa7 to fb4c01d Compare March 10, 2026 11:35
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch 2 times, most recently from 0478573 to a8b39c0 Compare March 10, 2026 12:15
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from dcbe359 to e5c297b Compare March 16, 2026 10:53
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from ba771b8 to ac63c6b Compare March 16, 2026 10:53
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from e5c297b to 63f01da Compare March 16, 2026 11:17
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from ac63c6b to 1a2349b Compare March 16, 2026 11:17
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 1a2349b to 54efb29 Compare March 17, 2026 10:08
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 63f01da to 576d4ea Compare March 17, 2026 10:08
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 576d4ea to 70b51d3 Compare March 17, 2026 11:02
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 54efb29 to 14298db Compare March 17, 2026 11:03
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 70b51d3 to f140406 Compare March 17, 2026 11:07
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 14298db to ac808f8 Compare March 17, 2026 11:07
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from f140406 to 6b7f418 Compare March 17, 2026 11:19
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch 2 times, most recently from 4ac5668 to eb9a421 Compare March 17, 2026 11:20
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 6b7f418 to 3c9a3bb Compare March 17, 2026 11:20
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 4feb5ae to a872825 Compare March 18, 2026 10:42
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from 3c9a3bb to bc8f04a Compare March 18, 2026 10:42
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from a872825 to 9688bf3 Compare March 18, 2026 17:50
@alfonso-noriega alfonso-noriega force-pushed the 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations branch from bc8f04a to 35aaabb Compare March 18, 2026 17:50
@alfonso-noriega alfonso-noriega changed the base branch from 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations to graphite-base/6966 March 19, 2026 10:19
@alfonso-noriega alfonso-noriega force-pushed the 03-10-add_client_steps_implementation_to_support_existing_functionalities branch from 9688bf3 to d42767e Compare March 19, 2026 10:19
@alfonso-noriega alfonso-noriega changed the base branch from graphite-base/6966 to 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations March 19, 2026 10:19
@alfonso-noriega alfonso-noriega changed the base branch from 03-10-create_abstract_client_steps_infrastracture_to_externalize_the_ap_modules_client_configurations to graphite-base/6966 March 19, 2026 11:24
alfonso-noriega and others added 2 commits March 23, 2026 11:13
…pattern copy

- Add sanitizeDestination() that strips '..' segments from destination
  fields and emits a warning when any are removed
- Sanitize entry.destination for all three inclusion types (pattern,
  static, configKey) before it reaches any path join
- Add copy-time bounds check in copyByPattern: skip any file whose
  resolved destPath escapes outputDir and warn

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

@isaacroldan isaacroldan left a comment

Choose a reason for hiding this comment

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


Review assisted by pair-review

await mkdir(dirname(destPath))
await copyFile(sourcePath, destPath)
options.stdout.write(`Copied ${source} to ${destination}\n`)
return 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Human: I guess the AI is right, but not sure if the fix is necessary? how important is this count?

🐛 Bug: When a static entry has an explicit destination (lines 177-183), copyFile is called and the count is hardcoded to 1. Since copyFile wraps fs-extra's copy which handles directories recursively, this works functionally — but if the source is a directory containing many files, the reported count is still 1. This is inconsistent with the no-destination path (lines 193-200) which correctly counts files via glob.

Suggestion: Add an isDirectory branch in the destination-provided path: if the source is a directory, count the actual files after copy (via glob on the destination), otherwise return 1. This mirrors the logic in the no-destination path below.

return 0
}
const destDir = preserveStructure ? joinPath(effectiveOutputDir, basename(fullPath)) : effectiveOutputDir
await copyDirectoryContents(fullPath, destDir)
Copy link
Contributor

Choose a reason for hiding this comment

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

🐛 Bug: copyConfigKeyEntry unconditionally calls copyDirectoryContents(fullPath, destDir) at line 246 without checking whether the resolved path is actually a directory. If a config key resolves to a file (e.g., tools: 'tools-a.js'), copyDirectoryContents internally globs srcDir/**/* which matches nothing for a file — the copy silently becomes a no-op. The copySourceEntry function in this same PR correctly handles this with an isDirectory check (line 185), but the same fix wasn't applied here. Tests pass only because copyDirectoryContents is mocked.

Suggestion: Add the same isDirectory check that exists in copySourceEntry. When the resolved path is a file, use copyFile instead:

Suggested change
await copyDirectoryContents(fullPath, destDir)
if (!(await isDirectory(fullPath))) {
const destPath = joinPath(effectiveOutputDir, basename(fullPath))
await mkdir(effectiveOutputDir)
await copyFile(fullPath, destPath)
options.stdout.write(`Copied '${sourcePath}' to ${basename(fullPath)}\n`)
return 1
}
const destDir = preserveStructure ? joinPath(effectiveOutputDir, basename(fullPath)) : effectiveOutputDir

if (duplicates.size > 0) {
const colliding = files.filter((fp) => duplicates.has(basename(fp)))
options.stdout.write(
`Warning: filename collision detected when flattening — the following files share a basename and will overwrite each other: ${colliding.join(', ')}\n`,
Copy link
Contributor

Choose a reason for hiding this comment

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

🐛 Bug: When preserveStructure is false and duplicate basenames are detected (lines 282-299), a warning is emitted but Promise.all at line 301 proceeds to copy all files concurrently. Since colliding files target the same destination path, the final file content depends on which copyFile promise resolves last — this is a race condition. The warning correctly identifies the problem but the behavior should be deterministic.

Suggestion: Consider either: (1) processing files sequentially when collisions are detected so the last entry in the array deterministically wins, or (2) deduplicating the file list before copying so only one file per basename is copied (e.g., last-in-array wins), adjusting the count accordingly.

)

options.stdout.write(`Copied ${files.length} file(s) from ${sourceDir} to ${outputDir}\n`)
return {filesCopied: files.length}
Copy link
Contributor

Choose a reason for hiding this comment

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

🐛 Bug: copyByPattern returns files.length as the copy count, but files can be skipped in two places: the path-traversal guard (lines 306-308, early return) and the same-path check (line 311, early return). The returned count overstates actual copies, and this value propagates to stepResults via BuildContext, affecting downstream logging or steps that consume it.

Suggestion: Track actual copies by having each map callback return 1 or 0, then sum:

Suggested change
return {filesCopied: files.length}
const copyResults = await Promise.all(
files.map(async (filepath) => {
const relPath = preserveStructure ? relativePath(sourceDir, filepath) : basename(filepath)
const destPath = joinPath(outputDir, relPath)
if (relativePath(outputDir, destPath).startsWith('..')) {
options.stdout.write(`Warning: skipping '${filepath}' - resolved destination is outside the output directory\n`)
return 0
}
if (filepath === destPath) return 0
await mkdir(dirname(destPath))
await copyFile(filepath, destPath)
return 1
}),
)
const copiedCount = copyResults.reduce((sum, n) => sum + n, 0)
options.stdout.write(`Copied ${copiedCount} file(s) from ${sourceDir} to ${outputDir}\n`)
return {filesCopied: copiedCount}

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

3 participants