Skip to content

feat(push): Introduce Push Mode Feature for Syncing Local Environment Variables to AWS SSM - #57

Merged
macalbert merged 59 commits into
mainfrom
macalbert/import-to-ssm
Jul 13, 2025
Merged

feat(push): Introduce Push Mode Feature for Syncing Local Environment Variables to AWS SSM#57
macalbert merged 59 commits into
mainfrom
macalbert/import-to-ssm

Conversation

@macalbert

@macalbert macalbert commented Jul 13, 2025

Copy link
Copy Markdown
Owner

Description

This PR introduces the highly requested Push Mode feature to the Envilder CLI, enabling users to securely and efficiently push local environment variables to AWS SSM Parameter Store. Additionally, minor documentation updates have been made to improve usability.

Key Changes:

  1. New Feature: Push Mode (--push):

    • Purpose: Allows users to push local .env files or individual environment variables to AWS SSM Parameter Store.
    • Use Cases:
      • Sync local environment variables with AWS for centralized management.
      • Push individual variables for quick updates without modifying entire files.
    • Options:
      • --push: Enables push mode.
      • --key and --value: Specify a single environment variable to push.
      • --ssm-path: Define the SSM path for the variable.
      • --envfile and --map: Push variables from a .env file using a mapping file.
    • Examples:
      • Push a local .env file to AWS SSM:
        envilder --push --envfile=.env --map=param-map.json
      • Push a single environment variable to AWS SSM:
       envilder --push --key=API_KEY --value=secret123 --ssm-path=/my/path
      • Push a single variable using a specific AWS profile:
        envilder --push --key=API_KEY --value=secret123 --ssm-path=/my/path --profile=dev-account
  2. Documentation Updates:

    • Moved the Push Mode and Pull Mode sections to the Usage section for better organization.
    • Added detailed examples for both modes to guide users effectively.
    • Updated the table of contents to reflect the new structure.

Why This Change?

  • The Push Mode feature addresses a critical need for syncing local environment variables with AWS SSM, providing a secure and centralized solution.
  • The documentation updates ensure users can easily discover and understand how to use the new feature, along with existing functionality.

This PR focuses on delivering the Push Mode feature while also improving the overall user experience through minor documentation

Summary by CodeRabbit

  • New Features
    • Introduced push mode to sync local .env files or single variables to AWS SSM Parameter Store.
    • Enhanced CLI with AWS profile support, single secret push, and refined command workflows.
    • Added command dispatching system for pull and push operations.
    • Introduced domain entities and error classes for validation and error handling.
    • Improved documentation with clearer installation, usage guides, embedded video, and feature status.
  • Bug Fixes
    • Enhanced CLI argument validation and error handling.
  • Tests
    • Added tests for push functionality, command handlers, domain entities, and CLI.
    • Expanded AWS SSM integration tests including secret setting.
  • Chores
    • Updated configs, package metadata, and test inclusion patterns.
    • Refactored project structure with new CLI entrypoint and modular command handlers.

macalbert added 30 commits July 12, 2025 12:11
- Added ExportSsmToEnvCommand and ExportSsmToEnvCommandHandler to handle exporting environment variables from AWS SSM to a local .env file.
- Introduced ImportEnvToSsmCommand and ImportEnvToSsmCommandHandler for importing environment variables from a local .env file to AWS SSM.
- Created PushSingleVariableCommand and PushSingleVariableCommandHandler for pushing individual environment variables to AWS SSM.
- Defined CliOptions type for command line interface options.
- Updated tests for new command handlers and removed obsolete tests.
…xportSsmToEnvCommandHandler and PushSingleVariableCommandHandler
…tructors

refactor(tests): remove empty lines in test files for cleaner code
chore(biome): update schema version to 2.1.1

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/envilder/application/pullSsmToEnv/PullSsmToEnvCommandHandler.ts (1)

51-70: Consider removing unnecessary abstraction layer.

The saveEnvFile method is a simple wrapper that doesn't add value beyond what the envFileManager already provides. Consider calling envFileManager.saveEnvFile directly in the handle method.

-  private async saveEnvFile(
-    envFilePath: string,
-    variables: Record<string, string>,
-  ): Promise<void> {
-    await this.envFileManager.saveEnvFile(envFilePath, variables);
-  }

And update the handle method:

-      await this.saveEnvFile(command.envFilePath, envilded);
+      await this.envFileManager.saveEnvFile(command.envFilePath, envilded);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f2b9257 and dce5a07.

📒 Files selected for processing (2)
  • src/envilder/application/pullSsmToEnv/PullSsmToEnvCommandHandler.ts (1 hunks)
  • src/envilder/application/pushEnvToSsm/PushEnvToSsmCommandHandler.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/envilder/application/pushEnvToSsm/PushEnvToSsmCommandHandler.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: codeql-fortress (typescript)
  • GitHub Check: powerup-test
🔇 Additional comments (3)
src/envilder/application/pullSsmToEnv/PullSsmToEnvCommandHandler.ts (3)

1-6: LGTM - Clean imports and dependency setup.

The imports are well-organized, using proper TypeScript import syntax and maintaining clear separation of concerns between domain models, ports, and commands.


7-23: LGTM - Good use of dependency injection and constants.

The class structure follows solid dependency injection principles with proper interface usage for testability. The static readonly constants provide good maintainability for error messages.


25-49: LGTM - Well-structured orchestration method.

The main handle method provides clear orchestration of the pull operation with proper error handling and logging. The try-catch structure appropriately logs errors before re-throwing them.

Comment thread src/envilder/application/pullSsmToEnv/PullSsmToEnvCommandHandler.ts
Comment thread src/envilder/application/pullSsmToEnv/PullSsmToEnvCommandHandler.ts
macalbert and others added 6 commits July 13, 2025 20:17
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
e2e/e2e.test.ts (1)

189-198: Add error handling for AWS SSM operations.

The GetParameterSsm function lacks error handling for AWS SDK calls, which could cause test failures due to transient AWS issues or missing parameters.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4cda420 and 93975e2.

📒 Files selected for processing (3)
  • e2e/e2e.test.ts (6 hunks)
  • src/envilder/infrastructure/Aws/AwsSsmSecretProvider.ts (1 hunks)
  • tests/apps/cli/Cli.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/apps/cli/Cli.test.ts
  • src/envilder/infrastructure/Aws/AwsSsmSecretProvider.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: codeql-fortress (typescript)
🔇 Additional comments (5)
e2e/e2e.test.ts (5)

6-6: LGTM! AWS SDK integration looks good.

The AWS SDK imports and SSM client initialization are properly implemented for the new push functionality tests.

Also applies to: 14-14


27-28: LGTM! Updated file paths align with new structure.

The file paths have been correctly updated to point to the new e2e/sample directory structure.


86-97: LGTM! Test expectations correctly updated.

The test name and error message expectations have been properly updated to reflect the current CLI behavior.


172-172: LGTM! Cleanup improvements are appropriate.

The cleanup logic has been properly updated to use the newer async/await pattern with better error handling.

Also applies to: 179-179


200-207: LGTM! Parsing robustness issue has been resolved.

The GetSecretFromKey function now correctly uses substring() instead of split(), which properly handles environment values containing = characters.

Comment thread e2e/e2e.test.ts
Comment thread e2e/e2e.test.ts
Comment thread e2e/e2e.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
e2e/e2e.test.ts (2)

201-210: Add error handling for AWS SSM operations.

The GetParameterSsm function lacks error handling for AWS SDK calls, which could cause test failures due to transient AWS issues or missing parameters.

 async function GetParameterSsm(ssmPath: string): Promise<string> {
-  const command = new GetParameterCommand({
-    Name: ssmPath,
-    WithDecryption: true,
-  });
-  const response = await ssmClient.send(command);
-  const value = response.Parameter?.Value || '';
-  console.log(`SSM Value for path ${ssmPath}: ${value}`);
-  return value;
+  try {
+    const command = new GetParameterCommand({
+      Name: ssmPath,
+      WithDecryption: true,
+    });
+    const response = await ssmClient.send(command);
+    const value = response.Parameter?.Value || '';
+    console.log(`SSM Value for path ${ssmPath}: ${value}`);
+    return value;
+  } catch (error) {
+    console.error(`Failed to get SSM parameter ${ssmPath}:`, error);
+    throw error;
+  }
 }

132-153: Improve test isolation with unique SSM paths.

The hardcoded SSM path /Test/SingleVariable could conflict with other tests or environments running concurrently.

   it('Should_PushSingle_When_KeyValueAndSsmPathProvided', async () => {
     // Arrange
     const key = 'SINGLE_VARIABLE';
     const value = 'single-value-test';
-    const ssmPath = '/Test/SingleVariable';
+    const ssmPath = `/Test/E2E/${Date.now()}/SingleVariable`;
     const params = ['--key', key, '--value', value, '--ssm-path', ssmPath];

     // Ensure SSM parameter doesn't exist before the test
     await DeleteParameterSsm(ssmPath);

     // Act
     const actual = await runCommand(envilder, params);

     // Assert
     expect(actual.code).toBe(0);
     expect(actual.output).toContain(key);
     expect(actual.output).toContain(ssmPath);

     // Validate in AWS SSM
     const ssmValue = await GetParameterSsm(ssmPath);
     expect(ssmValue).toBe(value);
+
+    // Cleanup SSM parameter
+    await DeleteParameterSsm(ssmPath);
   });
🧹 Nitpick comments (1)
README.md (1)

112-114: CLI examples expose secrets in shell history

Passing secret values via --value=12345 leaves them in shell history and process lists.
Advise users to pass secrets through stdin or environment variables (e.g., read -s VALUE && envilder … --value="$VALUE").

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 93975e2 and bb371b8.

📒 Files selected for processing (3)
  • .gitignore (0 hunks)
  • README.md (2 hunks)
  • e2e/e2e.test.ts (6 hunks)
💤 Files with no reviewable changes (1)
  • .gitignore
🔇 Additional comments (4)
e2e/e2e.test.ts (4)

212-226: Good error handling implementation in DeleteParameterSsm.

The DeleteParameterSsm function properly handles the ParameterNotFound error case and logs appropriate messages. This addresses the cleanup concerns from previous reviews.


228-235: Good fix for parsing robustness.

The parsing logic now correctly uses substring(key.length + 1) instead of split('=')[1], which properly handles environment values containing = characters.


103-130: Add AWS credentials validation and improve test isolation.

The push tests assume AWS credentials are available and have SSM permissions. Consider adding validation to skip tests gracefully when credentials are unavailable. Additionally, the hardcoded SSM paths could conflict with other test runs.

+  beforeAll(async () => {
+    if (!process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_SECRET_ACCESS_KEY) {
+      try {
+        // Try to access AWS credentials
+        await ssmClient.send(new GetParameterCommand({ Name: '/dummy/test' }));
+      } catch (error) {
+        if (error.name === 'UnauthorizedOperation' || error.name === 'CredentialsProviderError') {
+          console.warn('Skipping SSM E2E tests: AWS credentials not available');
+          return test.skip();
+        }
+      }
+    }
+  });

   it('Should_PushEnvFileToSSM_When_PushFlagIsUsed', async () => {
     // Arrange
     const params = ['--push', '--envfile', envFilePath, '--map', mapFilePath];

-    const ssmParams = JSON.parse(readFileSync(mapFilePath, 'utf8')) as Record<
-      string,
-      string
-    >;
+    const ssmParams = JSON.parse(readFileSync(mapFilePath, 'utf8')) as Record<string, string>;
+    // Make paths unique to avoid conflicts
+    const uniqueSuffix = Date.now();
+    const uniqueSsmParams: Record<string, string> = {};
+    for (const [key, path] of Object.entries(ssmParams)) {
+      uniqueSsmParams[key] = `${path}-${uniqueSuffix}`;
+    }

This ensures tests won't fail in environments without AWS setup and prevents conflicts between test runs.


98-101: Error message format is correct

Verified that the CLI throws
Missing required arguments: --map and --envfile
in DispatchActionCommandHandler, and the test’s .toContain assertion exactly matches this message. No changes needed.

Comment thread README.md Outdated
Comment thread README.md
Comment thread README.md Outdated
Comment thread README.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
README.md (3)

6-6: Remove the stray closing </p> tag
This duplicate tag is still present and breaks the rendered HTML.

-</p>

51-51: Drop the garbled from the TOC entry
The invalid character still corrupts the Markdown anchor.

-      - [� Method 1: Push from .env File](#-method-1-push-from-env-file)
+      - [Method 1: Push from .env File](#method-1-push-from-env-file)

157-157: Fix the heading – remove the leading
Same encoding glitch as the TOC; leaves an ugly heading and broken anchor.

-#### � Method 1: Push from .env File
+#### Method 1: Push from .env File
🧹 Nitpick comments (1)
README.md (1)

30-35: GitHub won’t render the <video> tag – embed a GIF or link instead
Markdown on GitHub strips <video> elements, so users will just see raw HTML. Convert the demo to a GIF or link to the video.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bb371b8 and a7337f8.

📒 Files selected for processing (1)
  • README.md (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: codeql-fortress (typescript)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
README.md (4)

6-6: Remove stray closing </p> tag
Extraneous closing tag breaks the HTML structure rendered by GitHub; delete it.


48-49: Garbled character in TOC entry
The leading replacement character produces an invalid anchor; drop it.

-      - [� Method 1: Push from .env File](#-method-1-push-from-env-file)
+      - [Method 1: Push from .env File](#method-1-push-from-env-file)

154-154: Same garbled character in section heading
Remove the invalid glyph to keep the heading clean and linkable.

-#### � Method 1: Push from .env File
+#### Method 1: Push from .env File

118-124: Mismatched closing fence (` \) breaks Markdown from here down
Close the earlier code block with exactly three back-ticks and remove the quadruple fence.

-````
+```
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a7337f8 and 50ef9dd.

📒 Files selected for processing (1)
  • README.md (2 hunks)
🧰 Additional context used
🪛 GitHub Check: markdown-lint
README.md

[failure] 28-28:
Multiple headings with the same content [Context: "🎥 Video Demonstration"]

🪛 GitHub Actions: 🏁 Rainbow Road Lint
README.md

[error] 28-28: markdownlint MD024/no-duplicate-heading: Multiple headings with the same content [Context: "🎥 Video Demonstration"]

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: codeql-fortress (typescript)

Comment thread README.md Outdated
@macalbert
macalbert requested a review from Copilot July 13, 2025 21:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

Introduces Push Mode to Envilder CLI for syncing local environment variables to AWS SSM and updates docs and tests.

  • Added Push Mode (--push) functionality: supports pushing entire .env files and individual variables to AWS SSM Parameter Store.
  • Enhanced CLI internals: new command handlers, domain interfaces, and error handling for push operations.
  • Updated tests, configuration, and documentation to reflect Push Mode feature.

Reviewed Changes

Copilot reviewed 44 out of 51 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
vite.config.ts Broadened test glob patterns to include new push mode tests
src/envilder/infrastructure/EnvManager/EnvFileManager.ts Added robust JSON parsing and error handling for map files
src/envilder/infrastructure/Aws/AwsSsmSecretProvider.ts Implemented get/set secret operations against AWS SSM
src/envilder/application/pushEnvToSsm/PushEnvToSsmCommandHandler.ts Core logic for pushing variables from .env to AWS SSM
README.md Extended usage guide with Push Mode examples and updated TOC
Comments suppressed due to low confidence (3)

src/envilder/infrastructure/EnvManager/EnvFileManager.ts:72

  • The escapeEnvValue method that replaces newlines is untested. Consider adding unit tests to verify correct escaping of multi-line values.
  private escapeEnvValue(value: string): string {

README.md:6

  • There is an extra closing </p> tag here which may lead to invalid HTML. Consider removing the duplicated </p> to maintain proper markup.
</p>

src/envilder/application/pushEnvToSsm/PushEnvToSsmCommandHandler.ts:91

  • Running all push operations in parallel may flood AWS SSM if there are many variables. Consider limiting concurrency (e.g., using a queue or p-limit) to avoid throttling.
    await Promise.all(variableProcessingPromises);

Comment thread src/envilder/application/pushEnvToSsm/PushEnvToSsmCommandHandler.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
README.md (1)

3-6: Still one stray </p> — remove to restore valid HTML
The duplicate closing paragraph tag slipped through again and breaks the rendered layout.

-</p>
🧹 Nitpick comments (1)
README.md (1)

345-348: Sample secret trips gitleaks – replace with a placeholder
Even though it’s fake, leaving a JWT-looking string in docs makes scanners fail CI.

-SECRET_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
+SECRET_TOKEN=<SECRET_TOKEN_PLACEHOLDER>
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 50ef9dd and bbd2b29.

📒 Files selected for processing (1)
  • README.md (2 hunks)
🧰 Additional context used
🪛 Gitleaks (8.26.0)
README.md

348-348: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: powerup-test
  • GitHub Check: codeql-fortress (typescript)
🔇 Additional comments (1)
README.md (1)

131-136: Missing closing fence – everything below renders as code
The Step 3 command block is never closed.

 envilder --map=param-map.json --envfile=.env
+```

Add the triple back-tick to terminate the block.

Likely an incorrect or invalid review comment.

Comment thread README.md
@macalbert
macalbert merged commit 321db4a into main Jul 13, 2025
5 checks passed
@macalbert
macalbert deleted the macalbert/import-to-ssm branch July 13, 2025 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request refactor Code restructuring with no behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants