Skip to content

feat(sdk-node): add Node.js SDK#168

Closed
macalbert wants to merge 39 commits intomainfrom
feat/sdk-typescript
Closed

feat(sdk-node): add Node.js SDK#168
macalbert wants to merge 39 commits intomainfrom
feat/sdk-typescript

Conversation

@macalbert
Copy link
Copy Markdown
Owner

@macalbert macalbert commented Apr 25, 2026

Summary

Adds the TypeScript SDK (@envilder/sdk) — an independent, async-first library for loading secrets from AWS SSM Parameter Store or Azure Key Vault directly into Node.js processes at startup.

SDK Architecture

src/sdks/typescript/src/
├── domain/          # Types, interfaces (ISecretProvider, MapFileConfig, EnvilderOptions)
├── application/     # Envilder facade, EnvilderClient, MapFileParser, validateSecrets
├── infrastructure/  # AwsSsmSecretProvider, AzureKeyVaultSecretProvider, factory
└── index.ts         # Barrel exports
  • Async-first — all provider and facade methods return Promise
  • No DI framework — lightweight, simple functions
  • Fluent builderfromMapFile().withProvider().withVaultUrl().inject()
  • Environment routingload('production', { ... }) selects env-specific map files
  • Opt-in validationvalidateSecrets() throws for empty/missing values

Public API

import { Envilder } from '@envilder/sdk';

// One-liner: load + inject into process.env
await Envilder.load('./secrets-map.json');

// Resolve without injecting
const secrets = await Envilder.resolveFile('./secrets-map.json');

// Fluent builder with overrides
await Envilder.fromMapFile('./secrets-map.json')
  .withProvider('azure')
  .withVaultUrl('https://my-vault.vault.azure.net')
  .inject();

Tests

46 unit tests covering all layers:

  • MapFileParser (6), EnvilderClient (5), SecretValidation (5)
  • AwsSsmSecretProvider (5), AzureKeyVaultSecretProvider (5), SecretProviderFactory (7)
  • Envilder facade (13) — including env routing and fluent builder

Website & Docs

  • Landing page SDK tab updated (TypeScript: planned → available)
  • Docs page: new #sdk-typescript section with install, quick start, resolve, fluent, env routing, and validation examples
  • i18n translations added for all 3 locales (en, ca, es)
  • Changelog infrastructure (docs/changelogs/sdk-typescript.md)
  • ROADMAP.md updated (TypeScript SDK → Shipped)
  • copilot-instructions.md updated with TypeScript SDK section

Commits

  1. feat(sdk-ts): add TypeScript SDK source — domain, application, infrastructure layers
  2. test(sdk-ts): add unit test suite — 46 tests with Vitest
  3. docs(sdk-ts): update website, docs, and roadmap — landing page, docs, i18n, changelog
  4. style(sdk-ts): apply biome formatting to JSON configs — formatting fix

Open in Devin Review

Summary by CodeRabbit

  • New Features

    • Launched Node.js SDK (@envilder/sdk) with AWS SSM Parameter Store and Azure Key Vault support.
    • Added fluent API for loading secrets into process.env and resolving from map files.
    • Added secret validation and environment-based routing capabilities.
  • Documentation

    • Published Node.js SDK documentation with examples in multiple languages.
    • Added SDK changelog and architecture decision records for testing infrastructure.
    • Updated website to showcase the available Node.js SDK.

Domain types (ISecretProvider, MapFileConfig, EnvilderOptions,
ParsedMapFile, SecretProviderType), application layer (Envilder
facade, EnvilderClient, MapFileParser, validateSecrets), and
infrastructure (AwsSsmSecretProvider, AzureKeyVaultSecretProvider,
createSecretProvider factory).

Async-first API with fluent builder pattern. Supports AWS SSM
Parameter Store and Azure Key Vault. Published as @envilder/sdk.
46 tests covering MapFileParser (6), EnvilderClient (5),
SecretValidation (5), AwsSsmSecretProvider (5),
AzureKeyVaultSecretProvider (5), SecretProviderFactory (7),
and Envilder facade (13) with environment routing and fluent
builder. Vitest with vi.fn() mocks at port boundaries.
- Add TypeScript SDK to landing page (available status with code
  example and npm package link)
- Add #sdk-typescript docs section with install, quick start,
  resolve, fluent builder, env routing, and validation examples
- Add i18n translations for TypeScript SDK (en, ca, es)
- Add changelog infrastructure (sdk-typescript.md, astro config,
  env.d.ts type declaration)
- Move TypeScript SDK from Up Next to Shipped in ROADMAP.md
- Add TypeScript SDK section to copilot-instructions.md
Copilot AI review requested due to automatic review settings April 25, 2026 18:10
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This pull request introduces a complete Node.js SDK implementation for Envilder that loads secrets from AWS SSM or Azure Key Vault into process.env, includes full website documentation across multiple languages with SDK status marked as available, and provides comprehensive test coverage with container-based acceptance testing for both cloud providers.

Changes

Cohort / File(s) Summary
SDK Implementation
src/sdks/nodejs/src/application/envilder.ts, src/sdks/nodejs/src/application/envilder-client.ts, src/sdks/nodejs/src/application/map-file-parser.ts, src/sdks/nodejs/src/application/secret-validation.ts
Core SDK classes implementing secret loading with fluent builder pattern, map file parsing with $config support, and secret validation with SecretValidationError.
SDK Domain Types
src/sdks/nodejs/src/domain/...
Interface definitions for ISecretProvider, SecretProviderType enum, and configuration objects (EnvilderOptions, MapFileConfig, ParsedMapFile).
SDK Infrastructure
src/sdks/nodejs/src/infrastructure/aws/aws-ssm-secret-provider.ts, src/sdks/nodejs/src/infrastructure/azure/azure-key-vault-secret-provider.ts, src/sdks/nodejs/src/infrastructure/secret-provider-factory.ts
Cloud-specific provider implementations for AWS SSM and Azure Key Vault with factory pattern for instantiation and configuration merging.
SDK Configuration
src/sdks/nodejs/package.json, src/sdks/nodejs/tsconfig.json, src/sdks/nodejs/src/index.ts
Package manifest, TypeScript config, and public API entrypoint re-exporting all SDK components.
Website Build Configuration
src/website/astro.config.mjs, src/website/src/env.d.ts
Updates Astro build to extract SDK versions from source files and changelog content; adds ambient type declarations for new build-time globals (__CHANGELOG_SDK_NODEJS__, __SDK_*_VERSION__).
Website Documentation
src/website/src/components/DocsContent.astro, src/website/src/components/Sdks.astro
Adds Node.js SDK section to docs with TypeScript examples; replaces planned TypeScript tab with available Node.js SDK details and quick-start code.
Website Translations
src/website/src/i18n/types.ts, src/website/src/i18n/en.ts, src/website/src/i18n/es.ts, src/website/src/i18n/ca.ts
Updates translation type definitions and adds Node.js SDK content across English, Spanish, and Catalan; updates roadmap status from next (TypeScript) to done (Node.js) with emoji change.
Changelog Pages
src/website/src/pages/changelog.astro, src/website/src/pages/es/changelog.astro, src/website/src/pages/ca/changelog.astro
Adds Node.js SDK changelog product to build pipeline with version links; shifts GHA references due to new product array index.
SDK Unit Tests
tests/sdks/nodejs/application/*.test.ts, tests/sdks/nodejs/infrastructure/**/*.test.ts
Comprehensive test suites for Envilder, EnvilderClient, MapFileParser, SecretValidationError, and both provider implementations with mocked clients.
SDK Acceptance Tests
tests/sdks/nodejs/acceptance/aws-ssm.acceptance.test.ts, tests/sdks/nodejs/acceptance/azure-key-vault.acceptance.test.ts
Integration tests against containerized LocalStack and Lowkey Vault with actual secret provider flows.
Test Infrastructure
tests/sdks/nodejs/containers/localstack-container.ts, tests/sdks/nodejs/containers/lowkey-vault-container.ts, tests/sdks/nodejs/package.json, tests/sdks/nodejs/tsconfig.json, tests/sdks/nodejs/vitest.config.ts
Test container wrappers for AWS/Azure cloud services with lifecycle management, environment variable restoration, and Vitest configuration.
Documentation
docs/changelogs/sdk-nodejs.md, docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md
Node.js SDK v0.1.0 changelog listing initial features; ADR standardizing cross-SDK acceptance test infrastructure patterns with container usage.
Cleanup
src/sdks/typescript/.gitkeep, tests/sdks/typescript/.gitkeep
Removes placeholder .gitkeep files.

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Envilder as Envilder
    participant Parser as MapFileParser
    participant Factory as ProviderFactory
    participant Provider as ISecretProvider<br/>(AWS/Azure)
    participant Client as EnvilderClient
    
    App->>Envilder: load(filePath)
    Envilder->>Envilder: readFile(filePath)
    Envilder->>Parser: parse(json)
    Parser-->>Envilder: {config, mappings}
    Envilder->>Factory: createSecretProvider(config)
    Factory->>Provider: new AwsSsmSecretProvider()<br/>or new AzureKeyVaultSecretProvider()
    Provider-->>Factory: provider instance
    Factory-->>Envilder: provider
    Envilder->>Client: new EnvilderClient(provider)
    Client-->>Envilder: client
    Envilder->>Client: resolveSecrets(mappings)
    Client->>Provider: getSecrets(secretPaths)
    Provider-->>Client: Map<name, value>
    Client-->>Envilder: resolved secrets Map
    Envilder->>Envilder: injectIntoEnvironment(secrets)
    Envilder-->>App: Promise<Map>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #146: Adds .NET SDK with identical architecture (EnvilderClient, MapFileParser, secret providers, provider factory, and test patterns)
  • #47: Implements the foundational ISecretProvider interface and AwsSsmSecretProvider that this PR reuses for Node.js
  • #134: Modifies astro.config.mjs and env.d.ts to inject build-time globals and changelog content (same pattern used here)

Suggested labels

documentation, dependencies

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete and misleading. It claims to add a TypeScript SDK but the PR actually adds a Node.js SDK; the architecture path shows 'src/sdks/typescript' when it should be 'src/sdks/nodejs'; and critical sections from the template are missing. Update description: correct all references from 'TypeScript SDK' to 'Node.js SDK', fix architecture paths to 'src/sdks/nodejs', and fill in missing template sections (Related issues, Type of change checklist, and Notes for reviewer).
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a Node.js SDK to the project.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sdk-typescript

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

gemini-code-assist[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@macalbert macalbert self-assigned this Apr 25, 2026
@macalbert macalbert added the enhancement New feature or request label Apr 25, 2026
Set root to repo root and use relative include patterns so v8 coverage
provider can instrument SDK source files. Also fix the resolve alias
path (was 2 levels up, needed 3).
Replace !x || !x.trim() with !x?.trim() in 7 locations across
facade, validation, providers, and factory modules.
Split all // Act & Assert combined comments into separate // Act and
// Assert phases. Rewrite try/catch test to use expect().toThrow().
Add missing Should_ResolveFromMappedFile_When_EnvMappingProvided test
for resolveFile with environment routing.
Copilot AI review requested due to automatic review settings April 25, 2026 23:43
Add test-typescript-sdk job that runs vitest with v8 coverage and
generates lcov report. Wire into publish-report needs array, failure
check, and coverage-config.json gate (80% line threshold).

This comment was marked as resolved.

Add tests-typescript-sdk.yml: biome check, build, vitest run with
junit reporter, and dorny/test-reporter for PR annotations.
Add publish-npm-sdk.yml: version bump detection against npm registry,
lint + build + test gate, pnpm publish, git tag, GitHub Release.
Replace NPM_TOKEN secret with npm publish --provenance to match
the CLI publish workflow. OIDC is already configured via id-token
permission and registry-url in setup-node.
Change pre-push biome-format from read-only 'pnpm format' to
'pnpm format:write' so formatting issues are fixed automatically
instead of blocking the push.
Copilot AI review requested due to automatic review settings April 25, 2026 23:52
The format:write in pre-push creates an infinite loop: it modifies
files without committing, so the next push triggers the same fix.
Pre-push must be read-only. Auto-fix belongs in pre-commit only.
vitest root is set to repo root in vitest.config.ts, so relative
outputFile paths resolve from there. Use github.workspace to write
test-results.xml at the repo root where dorny/test-reporter can
find it.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new independent TypeScript runtime SDK (@envilder/sdk) to load secrets into Node.js processes from AWS SSM / Azure Key Vault, plus a dedicated unit-test workspace and supporting docs/website/CI plumbing.

Changes:

  • Introduces the TypeScript SDK (domain/application/infrastructure layers) and exports its public API.
  • Adds a separate Vitest-based test workspace for the SDK and wires SDK coverage into the coverage dashboard workflow.
  • Updates website/docs/i18n/roadmap/changelog entries to mark the TypeScript SDK as shipped and documented.

Reviewed changes

Copilot reviewed 47 out of 49 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/sdks/typescript/vitest.config.ts Vitest configuration for the SDK test workspace (root/coverage/alias).
tests/sdks/typescript/tsconfig.json TypeScript config for the SDK tests workspace.
tests/sdks/typescript/package.json Test workspace package manifest (Vitest + TS dev deps).
tests/sdks/typescript/infrastructure/secret-provider-factory.test.ts Unit tests for provider factory behavior and option/config precedence.
tests/sdks/typescript/infrastructure/azure/azure-key-vault-secret-provider.test.ts Unit tests for Azure Key Vault provider behavior.
tests/sdks/typescript/infrastructure/aws/aws-ssm-secret-provider.test.ts Unit tests for AWS SSM provider behavior and batching.
tests/sdks/typescript/application/secret-validation.test.ts Unit tests for opt-in secret validation.
tests/sdks/typescript/application/map-file-parser.test.ts Unit tests for map-file parsing ($config + mappings).
tests/sdks/typescript/application/envilder.test.ts Unit tests for the public Envilder facade (file/env routing + fluent API).
tests/sdks/typescript/application/envilder-client.test.ts Unit tests for core EnvilderClient resolution + injection.
tests/sdks/typescript/.gitkeep Removes placeholder now that the folder contains real tests.
src/website/src/pages/es/changelog.astro Adds TypeScript SDK changelog category to Spanish page.
src/website/src/pages/changelog.astro Adds TypeScript SDK changelog category to default page.
src/website/src/pages/ca/changelog.astro Adds TypeScript SDK changelog category to Catalan page.
src/website/src/i18n/types.ts Adds i18n keys for TypeScript SDK docs + changelog labels.
src/website/src/i18n/es.ts Spanish translations for new TypeScript SDK sections and status.
src/website/src/i18n/en.ts English translations for new TypeScript SDK sections and status.
src/website/src/i18n/ca.ts Catalan translations for new TypeScript SDK sections and status.
src/website/src/env.d.ts Declares the new injected changelog constant for TypeScript SDK.
src/website/src/components/Sdks.astro Marks TypeScript SDK as available and adds install/docs/code snippet.
src/website/src/components/DocsContent.astro Adds full TypeScript SDK docs section + sidebar navigation entry.
src/website/astro.config.mjs Loads and injects the new TypeScript SDK changelog content.
src/sdks/typescript/tsconfig.json TypeScript SDK build config (Node16 ESM output + declarations).
src/sdks/typescript/src/infrastructure/secret-provider-factory.ts Factory creating AWS/Azure providers and enforcing cross-provider config rules.
src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts Azure Key Vault implementation of ISecretProvider.
src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts AWS SSM implementation of ISecretProvider with batching.
src/sdks/typescript/src/index.ts Barrel exports for the SDK public API.
src/sdks/typescript/src/domain/secret-provider-type.ts Provider enum (aws/azure).
src/sdks/typescript/src/domain/ports/secret-provider.ts ISecretProvider interface contract for providers.
src/sdks/typescript/src/domain/parsed-map-file.ts Parsed map-file type (config + mappings).
src/sdks/typescript/src/domain/map-file-config.ts $config schema type for provider configuration.
src/sdks/typescript/src/domain/envilder-options.ts Override options type for fluent/builder usage.
src/sdks/typescript/src/application/secret-validation.ts Opt-in secret validation and error type.
src/sdks/typescript/src/application/map-file-parser.ts JSON map-file parsing into typed config + mappings.
src/sdks/typescript/src/application/envilder.ts Public facade (load/resolveFile/fromMapFile + env routing + fluent overrides).
src/sdks/typescript/src/application/envilder-client.ts Core resolver + environment injection helper.
src/sdks/typescript/package.json NPM package manifest for @envilder/sdk.
src/sdks/typescript/.gitkeep Removes placeholder now that the SDK has real source.
pnpm-workspace.yaml Adds SDK + test workspace packages to the pnpm workspace.
pnpm-lock.yaml Adds dependency graph entries for the new workspaces and SDK deps.
package.json Adjusts root Biome formatting scripts and lint behavior.
lefthook.yml Expands pre-commit Biome coverage and runs check+format on staged files.
docs/changelogs/sdk-typescript.md Adds the TypeScript SDK changelog file.
ROADMAP.md Marks TypeScript SDK as shipped and removes it from “Up Next”.
.github/workflows/tests-typescript-sdk.yml Adds a dedicated CI workflow for SDK formatting/build/tests.
.github/workflows/publish-npm-sdk.yml Adds an NPM publish workflow for the SDK with version bump detection.
.github/workflows/coverage-report/coverage-config.json Adds coverage threshold config for the TypeScript SDK stack.
.github/workflows/coverage-report.yml Adds TypeScript SDK coverage job and includes it in aggregated dashboard.
.github/copilot-instructions.md Documents the TypeScript SDK architecture, patterns, and test conventions.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported


for (const name of names) {
if (!name?.trim()) {
throw new Error('Secret name cannot be null or whitespace.');
Comment on lines +4 to +5
const repoRoot = path.resolve(__dirname, '../../..');

Comment thread package.json
"format": "biome format",
"format:write": "biome format --write",
"lint": "secretlint \"**/*\" && biome check --write && tsc --noEmit",
"format": "biome check --write --unsafe && biome format --write",
Comment on lines +50 to +54

// Assert
expect(actual).toBeDefined();
expect(actual.getSecret).toBeDefined();
});
Comment on lines +21 to +29
if (isAzure && profile) {
throw new Error(
'AWS profile cannot be used with Azure Key Vault provider.',
);
}

if (!isAzure && vaultUrl) {
throw new Error('Vault URL cannot be used with AWS SSM provider.');
}
);

for (const param of response.Parameters ?? []) {
if (param.Name && param.Value) {
Comment thread pnpm-lock.yaml Outdated
Comment on lines +159 to +166
specifier: ^22.0.0
version: 22.19.17
rimraf:
specifier: ^6.0.0
version: 6.1.3
typescript:
specifier: ^5.7.0
version: 5.9.3
Comment on lines +14 to +17
"paths": {
"@envilder/sdk": ["../../src/sdks/typescript/src/index.js"],
"@envilder/sdk/*": ["../../src/sdks/typescript/src/*"]
}

for (const name of names) {
if (!name?.trim()) {
throw new Error('Secret name cannot be null or empty.');
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
src/website/src/pages/ca/changelog.astro (1)

32-166: ⚠️ Potential issue | 🔴 Critical

Same desktop-sidebar index drift as es/changelog.astro.

Inserting sdk-typescript at index 3 makes GHA parsed[4], but the desktop sidebar still hardcodes parsed[3] for the GHA button/version list (Lines 155, 158–166). Result: GHA shows TypeScript counts/links and there is no sidebar entry for TypeScript on desktop. Fix alongside the root cause flagged in src/website/src/pages/es/changelog.astro (and likely src/website/src/pages/changelog.astro) — ideally by addressing the products by id instead of positional index.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/website/src/pages/ca/changelog.astro` around lines 32 - 166, The desktop
sidebar incorrectly indexes into parsed by position (e.g., using parsed[3] for
GHA), causing misaligned product counts/links when products like
'sdk-typescript' are inserted; update the sidebar rendering logic to look up
products by id instead of hardcoded array indices: use parsed.find(p => p.id ===
'gha') (and similarly for 'cli', 'sdk-dotnet', 'sdk-python', etc.) to compute
versions/counts and to map versions for the <ul class="version-list"
data-versions="..."> and corresponding <button> elements (references: parsed,
data-product attributes, data-versions values, product-select-mobile, and the
GHA button/version-list sections) so the desktop sidebar always renders the
correct product regardless of array ordering.
src/website/src/pages/es/changelog.astro (1)

32-166: ⚠️ Potential issue | 🔴 Critical

Critical: desktop sidebar indices are stale after inserting sdk-typescript.

The desktop sidebar hardcodes parsed[3] for the GHA section (Lines 155, 158) under the assumption that the products array order is [cli, sdk-dotnet, sdk-python, gha]. With sdk-typescript inserted at index 3, GHA is now at parsed[4], so:

  • The GHA button shows the TypeScript version count (Line 155).
  • The GHA <ul> lists TypeScript versions linked with data-product="gha" (Lines 158–166), so version anchors go to the wrong product and break navigation.
  • There is no desktop sidebar button or version list for sdk-typescript at all — only the mobile selector renders it.

The same bug exists in src/website/src/pages/ca/changelog.astro and likely in src/website/src/pages/changelog.astro. Consider rendering the sidebar from parsed by id (lookup map) rather than positional indices, so future additions don't desynchronize.

🛠️ Suggested approach (sketch)
+const byId = Object.fromEntries(parsed.map((p) => [p.id, p]));
...
-              <span class="nav-product-count">{parsed[3].versions.length}</span>
+              <span class="nav-product-count">{byId['gha'].versions.length}</span>
...
-              {parsed[3].versions.map((v) => (
+              {byId['gha'].versions.map((v) => (

And add a TypeScript button/list block between the Python and GHA blocks (or render the SDK group from parsed.filter(p => p.group === 'sdks')).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/website/src/pages/es/changelog.astro` around lines 32 - 166, The desktop
sidebar uses fixed positional indices into the parsed array (e.g., parsed[0],
parsed[1], parsed[2], parsed[3]) which became stale after inserting
sdk-typescript; update the sidebar rendering logic (the product-nav-desktop
block that builds the buttons and <ul class="version-list">) to look up products
by id or iterate parsed instead of using hardcoded indices—e.g., build a lookup
like parsedById or loop parsed.filter(p => p.group === 'sdks') and render each
product's button and versions using p.id, p.versions, and p.label so
data-product attributes and href anchors (e.g., `#${p.id}-${v.id}`) stay correct
and sdk-typescript is included.
🧹 Nitpick comments (3)
src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts (1)

27-29: Minor: error message inconsistency with AWS provider.

The AWS provider throws 'Secret name cannot be null or whitespace.' while this throws 'Secret name cannot be null or empty.' for the same !name?.trim() check. Aligning the wording avoids surprising consumers depending on which provider is selected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts`
around lines 27 - 29, The Azure provider's validation in
azure-key-vault-secret-provider.ts uses if (!name?.trim()) in the method that
validates secret names and throws "Secret name cannot be null or empty."; update
that Error message to match the AWS provider wording ("Secret name cannot be
null or whitespace.") so both providers are consistent—locate the check in the
AzureKeyVaultSecretProvider (or the method performing name validation) and
replace the thrown message accordingly.
src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts (1)

37-51: Optional: parallelize batches for many secrets.

Batches are fetched sequentially via await in the loop. For map files with >10 secrets this serializes round-trips to SSM. Since the Azure provider uses Promise.all for concurrency and ISecretProvider does not prescribe ordering, you could collect each batch's promise and await Promise.all(...) to halve latency on larger maps.

♻️ Proposed parallelization
-    for (let i = 0; i < names.length; i += SSM_BATCH_SIZE) {
-      const batch = names.slice(i, i + SSM_BATCH_SIZE);
-      const response = await this.ssmClient.send(
-        new GetParametersCommand({
-          Names: batch,
-          WithDecryption: true,
-        }),
-      );
-
-      for (const param of response.Parameters ?? []) {
-        if (param.Name && param.Value) {
-          result.set(param.Name, param.Value);
-        }
-      }
-    }
+    const batches: Promise<Awaited<ReturnType<typeof this.ssmClient.send>>>[] = [];
+    for (let i = 0; i < names.length; i += SSM_BATCH_SIZE) {
+      const batch = names.slice(i, i + SSM_BATCH_SIZE);
+      batches.push(
+        this.ssmClient.send(
+          new GetParametersCommand({
+            Names: batch,
+            WithDecryption: true,
+          }),
+        ),
+      );
+    }
+    const responses = await Promise.all(batches);
+    for (const response of responses) {
+      for (const param of response.Parameters ?? []) {
+        if (param.Name && param.Value) {
+          result.set(param.Name, param.Value);
+        }
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts` around
lines 37 - 51, The current loop fetches SSM batches serially; change it to fire
all batch requests concurrently by collecting promises from
this.ssmClient.send(new GetParametersCommand({ Names: batch, WithDecryption:
true })) for each batch (using SSM_BATCH_SIZE and names as before), then await
Promise.all on that array; after Promise.all resolves, iterate each
response.Parameters (as you currently do) to populate result.set(param.Name,
param.Value). Keep use of SSM_BATCH_SIZE, GetParametersCommand, ssmClient.send
and result intact and ensure you still handle response.Parameters ?? [] for each
resolved response.
src/sdks/typescript/src/application/envilder.ts (1)

56-93: Static load/resolveFile cannot apply provider overrides.

The static load(...) and resolveFile(...) paths construct new Envilder(source) directly, bypassing withProvider/withVaultUrl/withProfile. This is by design — overrides require the fluent builder — but worth noting in JSDoc so callers understand they must use Envilder.fromMapFile(...) (and re-implement env routing) when they need overrides. Otherwise, on Azure-configured maps, users could be surprised that load('production', envMapping) works only when the map file's $config already declares everything needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/typescript/src/application/envilder.ts` around lines 56 - 93, The
static methods load(...) and resolveFile(...) construct new Envilder(source)
directly and therefore bypass any provider overrides set via the fluent builder
(e.g., withProvider, withVaultUrl, withProfile), so update the JSDoc for both
Envilder.load and Envilder.resolveFile to explicitly state that
provider/profile/vault overrides are not applied when using these static
convenience paths and that callers who need overrides should use the fluent
builder (e.g.,
Envilder.fromMapFile(...).withProvider(...).withVaultUrl(...).withProfile(...))
or re-run env routing via resolveEnvSource before calling the builder; reference
the symbols load, resolveFile, Envilder, withProvider, withVaultUrl,
withProfile, fromMapFile, and resolveEnvSource in the comment so consumers are
guided to the correct alternative.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sdks/typescript/src/infrastructure/secret-provider-factory.ts`:
- Around line 12-36: The createSecretProvider function currently treats any
provider value other than SecretProviderType.Azure as AWS; add explicit
validation: if options?.provider or config.provider is present but not equal to
a known SecretProviderType value, throw a clear error before branching to
isAzure—e.g., validate provider against SecretProviderType enum, then proceed to
use createAzureProvider(vaultUrl) when provider === SecretProviderType.Azure and
createAwsProvider(profile) when provider === SecretProviderType.AWS; this
ensures unknown strings (like "gcp" or "AWS" typo) fail fast with a descriptive
message referencing createSecretProvider and SecretProviderType.

In `@src/website/src/pages/changelog.astro`:
- Around line 32-37: The desktop sidebar is using hardcoded indices on parsed
causing the GHA block to read parsed[3] and miss the newly inserted
sdk-typescript; update the hand-rolled desktop sidebar to either (A) insert a
dedicated TypeScript SDK block that reads the sdk-typescript entry from parsed
(id 'sdk-typescript') and change the GHA block to use parsed[4] (id 'gha'), or
(B — preferred) replace the index lookups with lookups by id (e.g.,
parsed.find(p => p.id === 'sdk-typescript') and parsed.find(p => p.id ===
'gha')) so the GHA button/version list and the new TypeScript sidebar entry
always reference the correct product regardless of array order.
- Around line 32-37: The locale changelog pages use hardcoded
parsed[0]..parsed[3] references which breaks if the categories order changes;
update each locale changelog.astro to locate categories dynamically instead of
using parsed[0]/parsed[1]/parsed[2]/parsed[3] — e.g., replace direct indexed
access of parsed with a lookup like parsed.find(p => p.id === 'sdk-typescript'
|| p.group === 'sdks') (or find by label using t.changelogPage keys) and fall
back to __CHANGELOG_SDK_TYPESCRIPT__ || fallback when the lookup returns
undefined; ensure you update all occurrences of parsed[0..3] in the locale files
so the sidebar renders correctly regardless of array order.

---

Outside diff comments:
In `@src/website/src/pages/ca/changelog.astro`:
- Around line 32-166: The desktop sidebar incorrectly indexes into parsed by
position (e.g., using parsed[3] for GHA), causing misaligned product
counts/links when products like 'sdk-typescript' are inserted; update the
sidebar rendering logic to look up products by id instead of hardcoded array
indices: use parsed.find(p => p.id === 'gha') (and similarly for 'cli',
'sdk-dotnet', 'sdk-python', etc.) to compute versions/counts and to map versions
for the <ul class="version-list" data-versions="..."> and corresponding <button>
elements (references: parsed, data-product attributes, data-versions values,
product-select-mobile, and the GHA button/version-list sections) so the desktop
sidebar always renders the correct product regardless of array ordering.

In `@src/website/src/pages/es/changelog.astro`:
- Around line 32-166: The desktop sidebar uses fixed positional indices into the
parsed array (e.g., parsed[0], parsed[1], parsed[2], parsed[3]) which became
stale after inserting sdk-typescript; update the sidebar rendering logic (the
product-nav-desktop block that builds the buttons and <ul class="version-list">)
to look up products by id or iterate parsed instead of using hardcoded
indices—e.g., build a lookup like parsedById or loop parsed.filter(p => p.group
=== 'sdks') and render each product's button and versions using p.id,
p.versions, and p.label so data-product attributes and href anchors (e.g.,
`#${p.id}-${v.id}`) stay correct and sdk-typescript is included.

---

Nitpick comments:
In `@src/sdks/typescript/src/application/envilder.ts`:
- Around line 56-93: The static methods load(...) and resolveFile(...) construct
new Envilder(source) directly and therefore bypass any provider overrides set
via the fluent builder (e.g., withProvider, withVaultUrl, withProfile), so
update the JSDoc for both Envilder.load and Envilder.resolveFile to explicitly
state that provider/profile/vault overrides are not applied when using these
static convenience paths and that callers who need overrides should use the
fluent builder (e.g.,
Envilder.fromMapFile(...).withProvider(...).withVaultUrl(...).withProfile(...))
or re-run env routing via resolveEnvSource before calling the builder; reference
the symbols load, resolveFile, Envilder, withProvider, withVaultUrl,
withProfile, fromMapFile, and resolveEnvSource in the comment so consumers are
guided to the correct alternative.

In `@src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts`:
- Around line 37-51: The current loop fetches SSM batches serially; change it to
fire all batch requests concurrently by collecting promises from
this.ssmClient.send(new GetParametersCommand({ Names: batch, WithDecryption:
true })) for each batch (using SSM_BATCH_SIZE and names as before), then await
Promise.all on that array; after Promise.all resolves, iterate each
response.Parameters (as you currently do) to populate result.set(param.Name,
param.Value). Keep use of SSM_BATCH_SIZE, GetParametersCommand, ssmClient.send
and result intact and ensure you still handle response.Parameters ?? [] for each
resolved response.

In
`@src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts`:
- Around line 27-29: The Azure provider's validation in
azure-key-vault-secret-provider.ts uses if (!name?.trim()) in the method that
validates secret names and throws "Secret name cannot be null or empty."; update
that Error message to match the AWS provider wording ("Secret name cannot be
null or whitespace.") so both providers are consistent—locate the check in the
AzureKeyVaultSecretProvider (or the method performing name validation) and
replace the thrown message accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f7fa5df1-b647-4b69-8428-f561bde7628b

📥 Commits

Reviewing files that changed from the base of the PR and between d1a64b8 and 85b6e58.

⛔ Files ignored due to path filters (7)
  • .github/copilot-instructions.md is excluded by none and included by none
  • .github/workflows/coverage-report.yml is excluded by none and included by none
  • .github/workflows/coverage-report/coverage-config.json is excluded by none and included by none
  • .github/workflows/publish-npm-sdk.yml is excluded by none and included by none
  • .github/workflows/tests-typescript-sdk.yml is excluded by none and included by none
  • lefthook.yml is excluded by none and included by none
  • package.json is excluded by none and included by none
📒 Files selected for processing (26)
  • src/sdks/typescript/package.json
  • src/sdks/typescript/src/application/envilder-client.ts
  • src/sdks/typescript/src/application/envilder.ts
  • src/sdks/typescript/src/application/map-file-parser.ts
  • src/sdks/typescript/src/application/secret-validation.ts
  • src/sdks/typescript/src/domain/ports/secret-provider.ts
  • src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts
  • src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts
  • src/sdks/typescript/src/infrastructure/secret-provider-factory.ts
  • src/website/src/components/DocsContent.astro
  • src/website/src/components/Sdks.astro
  • src/website/src/i18n/ca.ts
  • src/website/src/i18n/en.ts
  • src/website/src/i18n/es.ts
  • src/website/src/i18n/types.ts
  • src/website/src/pages/ca/changelog.astro
  • src/website/src/pages/changelog.astro
  • src/website/src/pages/es/changelog.astro
  • tests/sdks/typescript/application/envilder-client.test.ts
  • tests/sdks/typescript/application/envilder.test.ts
  • tests/sdks/typescript/application/secret-validation.test.ts
  • tests/sdks/typescript/infrastructure/aws/aws-ssm-secret-provider.test.ts
  • tests/sdks/typescript/infrastructure/azure/azure-key-vault-secret-provider.test.ts
  • tests/sdks/typescript/infrastructure/secret-provider-factory.test.ts
  • tests/sdks/typescript/package.json
  • tests/sdks/typescript/vitest.config.ts
✅ Files skipped from review due to trivial changes (4)
  • tests/sdks/typescript/package.json
  • tests/sdks/typescript/application/secret-validation.test.ts
  • src/sdks/typescript/package.json
  • src/sdks/typescript/src/application/envilder-client.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/sdks/typescript/src/domain/ports/secret-provider.ts
  • tests/sdks/typescript/vitest.config.ts
  • tests/sdks/typescript/infrastructure/aws/aws-ssm-secret-provider.test.ts
  • tests/sdks/typescript/infrastructure/azure/azure-key-vault-secret-provider.test.ts
  • src/website/src/i18n/types.ts
  • src/sdks/typescript/src/application/map-file-parser.ts
  • src/sdks/typescript/src/application/secret-validation.ts
  • tests/sdks/typescript/application/envilder-client.test.ts
  • src/website/src/i18n/ca.ts
  • tests/sdks/typescript/infrastructure/secret-provider-factory.test.ts

Comment on lines +12 to +36
export function createSecretProvider(
config: MapFileConfig,
options?: EnvilderOptions,
): ISecretProvider {
const provider = options?.provider ?? config.provider;
const profile = options?.profile ?? config.profile;
const vaultUrl = options?.vaultUrl ?? config.vaultUrl;
const isAzure = provider === SecretProviderType.Azure;

if (isAzure && profile) {
throw new Error(
'AWS profile cannot be used with Azure Key Vault provider.',
);
}

if (!isAzure && vaultUrl) {
throw new Error('Vault URL cannot be used with AWS SSM provider.');
}

if (isAzure) {
return createAzureProvider(vaultUrl);
}

return createAwsProvider(profile);
}
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.

⚠️ Potential issue | 🟡 Minor

Unknown provider values silently fall through to AWS.

isAzure is a strict equality check, so any provider value other than SecretProviderType.Azure (including a malformed string from a hand-written $config, e.g. "gcp" or a typo like "AWS") is silently treated as AWS. Consider failing fast with a clear error when provider is set but not a known SecretProviderType value, so users get a useful diagnostic instead of unexpected AWS calls.

🛡️ Proposed validation
   const provider = options?.provider ?? config.provider;
   const profile = options?.profile ?? config.profile;
   const vaultUrl = options?.vaultUrl ?? config.vaultUrl;
+  if (
+    provider !== undefined &&
+    provider !== SecretProviderType.Aws &&
+    provider !== SecretProviderType.Azure
+  ) {
+    throw new Error(`Unknown secret provider: '${provider}'.`);
+  }
   const isAzure = provider === SecretProviderType.Azure;
📝 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
export function createSecretProvider(
config: MapFileConfig,
options?: EnvilderOptions,
): ISecretProvider {
const provider = options?.provider ?? config.provider;
const profile = options?.profile ?? config.profile;
const vaultUrl = options?.vaultUrl ?? config.vaultUrl;
const isAzure = provider === SecretProviderType.Azure;
if (isAzure && profile) {
throw new Error(
'AWS profile cannot be used with Azure Key Vault provider.',
);
}
if (!isAzure && vaultUrl) {
throw new Error('Vault URL cannot be used with AWS SSM provider.');
}
if (isAzure) {
return createAzureProvider(vaultUrl);
}
return createAwsProvider(profile);
}
export function createSecretProvider(
config: MapFileConfig,
options?: EnvilderOptions,
): ISecretProvider {
const provider = options?.provider ?? config.provider;
const profile = options?.profile ?? config.profile;
const vaultUrl = options?.vaultUrl ?? config.vaultUrl;
if (
provider !== undefined &&
provider !== SecretProviderType.Aws &&
provider !== SecretProviderType.Azure
) {
throw new Error(`Unknown secret provider: '${provider}'.`);
}
const isAzure = provider === SecretProviderType.Azure;
if (isAzure && profile) {
throw new Error(
'AWS profile cannot be used with Azure Key Vault provider.',
);
}
if (!isAzure && vaultUrl) {
throw new Error('Vault URL cannot be used with AWS SSM provider.');
}
if (isAzure) {
return createAzureProvider(vaultUrl);
}
return createAwsProvider(profile);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/typescript/src/infrastructure/secret-provider-factory.ts` around
lines 12 - 36, The createSecretProvider function currently treats any provider
value other than SecretProviderType.Azure as AWS; add explicit validation: if
options?.provider or config.provider is present but not equal to a known
SecretProviderType value, throw a clear error before branching to isAzure—e.g.,
validate provider against SecretProviderType enum, then proceed to use
createAzureProvider(vaultUrl) when provider === SecretProviderType.Azure and
createAwsProvider(profile) when provider === SecretProviderType.AWS; this
ensures unknown strings (like "gcp" or "AWS" typo) fail fast with a descriptive
message referencing createSecretProvider and SecretProviderType.

Comment thread src/website/src/pages/changelog.astro
- Add LocalStack container wrapper for AWS SSM acceptance tests
- Add Lowkey Vault container wrapper for Azure Key Vault acceptance tests
- Add 2 AWS SSM acceptance tests (resolve + missing parameter)
- Add 2 Azure Key Vault acceptance tests (resolve + missing secret)
- Add 2 unit tests for MapFileParser (invalid root, unknown provider)
- Add 2 unit tests for Envilder facade (null env mapping, missing env)
- Fix toThrowError deprecation warnings (use toThrow)
- Update CI workflows with OIDC credentials and Docker env for TestContainers
- Add ADR-0001 documenting SDK acceptance test infrastructure decisions
- Add sdk-acceptance-testing Copilot skill for cross-SDK test patterns
Co-authored-by: Copilot <copilot@github.com>
Copilot AI review requested due to automatic review settings April 26, 2026 18:40
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (8)
tests/sdks/typescript/application/envilder.test.ts (3)

99-110: Inconsistent assertion coverage for process.env.

Other resolve-only tests (e.g., line 95-96) assert both DB_URL and API_KEY are undefined, but this test only checks DB_URL. For symmetry and to guard against partial-injection regressions, assert API_KEY is also undefined.

♻️ Proposed addition
       expect(actual.size).toBe(2);
       expect(process.env.DB_URL).toBeUndefined();
+      expect(process.env.API_KEY).toBeUndefined();
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/application/envilder.test.ts` around lines 99 - 110,
Update the test Should_ResolveFromMappedFile_When_EnvMappingProvided to assert
both environment variables are not injected: after calling
Envilder.resolveFile('production', ...) and verifying mockReadFile was called,
add an assertion that process.env.API_KEY is undefined in addition to the
existing process.env.DB_URL check to mirror other resolve-only tests and prevent
partial-injection regressions; reference Envilder.resolveFile and mockReadFile
in your change.

51-56: Redundant restoreAllMocks alongside clearAllMocks.

vi.restoreAllMocks() is intended for spies created via vi.spyOn() and restores their original implementations. The module mocks here are defined via vi.mock() factories, so clearAllMocks() (which resets call history) is sufficient. Calling restoreAllMocks() on vi.fn(impl) instances like mockProvider.getSecrets will revert their implementation — the next beforeEach resets mockReadFile and mockCreateProvider, but mockProvider.getSecrets's impl is not re-set, so subsequent tests rely on Vitest preserving vi.fn(impl) behavior across restore. Simpler and more predictable to drop restoreAllMocks().

♻️ Proposed cleanup
   afterEach(() => {
     vi.clearAllMocks();
-    vi.restoreAllMocks();
     delete process.env.DB_URL;
     delete process.env.API_KEY;
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/application/envilder.test.ts` around lines 51 - 56,
Remove the redundant vi.restoreAllMocks() call from the afterEach block in the
test file: keep vi.clearAllMocks() and the environment cleanup but delete
vi.restoreAllMocks() because mocks are created via vi.mock() and individual
vi.fn() implementations such as mockProvider.getSecrets (and other mocks like
mockReadFile and mockCreateProvider) should not have their implementations
reverted by restoreAllMocks; this ensures each beforeEach can reliably set mock
implementations without unexpected resets.

87-187: Routing tests are split across two describe blocks with duplicate names.

resolveFile (lines 99-134) and environment routing (lines 137-187) both cover env-mapping behavior, with two identically named tests (Should_ReturnEmpty_When_EnvMapsToNull, Should_ReturnEmpty_When_EnvNotInMapping). The split makes it harder to see routing coverage at a glance, and environment routing only exercises load while routing for resolveFile lives in the other block.

Consider consolidating routing tests under a single describe('environment routing') parameterized over load/resolveFile, or moving the resolveFile env-mapping cases into the routing block with distinct names (e.g., ..._When_ResolveFileCalled vs ..._When_LoadCalled).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/application/envilder.test.ts` around lines 87 - 187,
Tests for env-mapping behavior are split between describe('resolveFile') and
describe('environment routing'), causing duplicate test names and scattered
coverage; consolidate routing tests by moving the env-mapping cases from
Envilder.resolveFile (the tests named Should_ReturnEmpty_When_EnvMapsToNull and
Should_ReturnEmpty_When_EnvNotInMapping) into the environment routing describe
block or refactor both blocks into one describe('environment routing') that
parameterizes over the two entry points (Envilder.resolveFile and
Envilder.load), and rename duplicated tests to indicate which method is under
test (e.g., ..._When_ResolveFileCalled vs ..._When_LoadCalled) so routing
behavior for both resolveFile and load is colocated and uniquely identified.
tests/sdks/typescript/containers/lowkey-vault-container.ts (2)

25-29: Hardcoded port 8443 duplicates HTTPS_PORT constant.

If HTTPS_PORT is ever changed, the --server.port=8443 arg silently desynchronizes from the container's exposed port.

🛠️ Proposed fix
       this.container = await new GenericContainer(LOWKEY_VAULT_IMAGE)
         .withExposedPorts(HTTPS_PORT, HTTP_PORT)
         .withEnvironment({
-          LOWKEY_ARGS: '--server.port=8443 --LOWKEY_VAULT_RELAXED_PORTS=true',
+          LOWKEY_ARGS: `--server.port=${HTTPS_PORT} --LOWKEY_VAULT_RELAXED_PORTS=true`,
         })
         .start();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/containers/lowkey-vault-container.ts` around lines 25 -
29, The LOWKEY_ARGS environment value hardcodes --server.port=8443 which can
drift from the HTTPS_PORT constant; update the withEnvironment call that sets
LOWKEY_ARGS to build the server.port value from the HTTPS_PORT constant (e.g.
use a template/string concatenation) so the value stays in sync with
withExposedPorts(HTTPS_PORT, HTTP_PORT); adjust any necessary string conversion
so LOWKEY_ARGS contains the numeric HTTPS_PORT at runtime.

91-108: Outer try/catch is unreachable — Promise never rejects.

The inner new Promise<boolean> always calls resolve(...) (on success, on error, on timeout); it never rejects. The wrapping try { ... } catch {} is dead code.

🛠️ Proposed fix
     for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
-      try {
-        const ok = await new Promise<boolean>((resolve) => {
-          const req = https.get(url, { agent }, (res) => {
-            resolve(res.statusCode === 200);
-          });
-          req.on('error', () => resolve(false));
-          req.setTimeout(2000, () => {
-            req.destroy();
-            resolve(false);
-          });
-        });
-        if (ok) {
-          return;
-        }
-      } catch {
-        // retry
-      }
+      const ok = await new Promise<boolean>((resolve) => {
+        const req = https.get(url, { agent }, (res) => {
+          resolve(res.statusCode === 200);
+        });
+        req.on('error', () => resolve(false));
+        req.setTimeout(2000, () => {
+          req.destroy();
+          resolve(false);
+        });
+      });
+      if (ok) {
+        return;
+      }
 
       if (attempt < MAX_RETRIES - 1) {
         await new Promise((r) => setTimeout(r, RETRY_DELAY_MS));
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/containers/lowkey-vault-container.ts` around lines 91 -
108, The outer try/catch around the await new Promise<boolean> is dead because
the promise always resolves, so remove the try/catch and rely on the resolved
boolean to decide retries: keep the loop using MAX_RETRIES, await the Promise
created around https.get (the new Promise<boolean> that resolves via req,
req.on('error'), and req.setTimeout), check the returned ok value and
continue/retry if false, and only return when ok is true; or alternatively
change the inner promise to reject on unexpected errors/timeouts instead of
resolving so the try/catch around the await becomes meaningful—update references
to MAX_RETRIES, the new Promise<boolean>, req, url, and agent accordingly.
docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md (1)

75-75: TypeScript API version not documented.

The "API version" row lists 7.2 for .NET, 7.6 for Python but omits TypeScript, even though this PR ships the TypeScript implementation. Either add the TS-pinned version or note that TS uses the SDK default.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md` at line 75,
The API version table row currently lists ".NET" and "Python" but omits
TypeScript; update the "API version" row text to either include the pinned
TypeScript version (e.g., add "TypeScript: X.Y" matching the shipped
implementation) or explicitly state that TypeScript uses the SDK default (e.g.,
"TypeScript: uses SDK default") so the "API version" row is complete and
unambiguous.
tests/sdks/typescript/containers/localstack-container.ts (1)

25-30: Cascading failure with afterAll in the consuming test.

When this throws (no LOCALSTACK_AUTH_TOKEN resolvable, e.g. forks/local without credentials), aws-ssm.acceptance.test.ts's beforeAll fails and its afterAll then dereferences an undefined localstack (see the related comment in that file). The error here is the actionable one and gets buried.

Consider also documenting in a brief comment that this hard-fails by design when the token can't be resolved, so contributors know to set up secrets-map.json or skip the suite.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/containers/localstack-container.ts` around lines 25 -
30, The hard throw when LOCALSTACK_AUTH_TOKEN is missing causes downstream
afterAll to dereference an undefined localstack; change the behavior in
loadEnvironment / the block that checks environment.get('LOCALSTACK_AUTH_TOKEN')
so it does not throw: instead return null/undefined (or a sentinel) from the
function that creates the Localstack container (referenced in
localstack-container.ts) and add a brief comment above the check explaining this
is an intentional hard-fail/skip decision and that callers should handle a
missing localstack (e.g., tests should skip when the returned value is falsy).
Update callers to guard on the returned value (e.g., aws-ssm.acceptance.test.ts
beforeAll/afterAll) so afterAll won’t dereference localstack when setup failed.
tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts (1)

8-10: Module-level shared state across test files.

localstack, ssmClient, provider are declared at module scope. If Vitest ever runs files in the same worker context, this is fine, but consider scoping inside describe (or using a small fixture object) to avoid accidental leakage and simplify future parallelization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts` around lines 8 -
10, The tests declare module-level shared state (localstack, ssmClient,
provider) which can leak between test files; move these variables into the
describe block (or into a per-test fixture object) so each test suite gets its
own instance, e.g. declare them inside the describe callback and
initialize/cleanup in beforeAll/afterAll or beforeEach/afterEach using the
existing setup/teardown logic for LocalStackTestContainer, SSMClient, and
AwsSsmSecretProvider to avoid cross-file contamination and enable
parallelization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md`:
- Around line 90-99: The fenced code block showing the directory tree (the
triple backticks before "tests/sdks/{lang}/" through the closing backticks)
lacks a language identifier and triggers MD040; update that fence (the opening
``` line) to include a language token such as "text" (e.g., change "```" to
"```text") so markdownlint accepts the block while preserving the directory-tree
content in the file
`docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md`.

In `@tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts`:
- Around line 19-21: The afterAll teardown currently assumes localstack was
created and calls localstack.stop(), which throws if beforeAll failed and
localstack is undefined; change the afterAll handler (and the module-level
localstack declaration if needed) to guard the shutdown call — e.g., only call
stop when localstack is truthy (or use optional chaining) so the real error from
LocalStackTestContainer.start() surfaces instead of a TypeError; update
references to LocalStackTestContainer, beforeAll, and afterAll accordingly.

In `@tests/sdks/typescript/acceptance/azure-key-vault.acceptance.test.ts`:
- Around line 20-22: The afterAll teardown unconditionally calls
lowkeyVault.stop() which throws if LowkeyVaultTestContainer.start() failed and
lowkeyVault is undefined; update the afterAll to guard the call (e.g., if
(lowkeyVault) await lowkeyVault.stop();) or wrap in try/catch so teardown only
runs when lowkeyVault was successfully assigned; locate the lowkeyVault symbol
and the afterAll closure to apply this defensive check.

In `@tests/sdks/typescript/containers/lowkey-vault-container.ts`:
- Around line 47-50: The code captures
prevIdentityEndpoint/prevIdentityHeader/prevTlsRejectUnauthorized too late so a
thrown error from GenericContainer.start or waitUntilReady can cause this.stop()
to call restoreEnv and delete real env vars; fix by capturing those previous env
values before entering the try (or introduce an envOverridden boolean) and only
call restoreEnv for IDENTITY_ENDPOINT/IDENTITY_HEADER/TLS_REJECT_UNAUTHORIZED
when you actually set/overrode them; update the start logic around
GenericContainer.start and waitUntilReady to set envOverridden = true when
applying overrides and change this.stop to skip restoreEnv calls unless
envOverridden is true.

---

Nitpick comments:
In `@docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md`:
- Line 75: The API version table row currently lists ".NET" and "Python" but
omits TypeScript; update the "API version" row text to either include the pinned
TypeScript version (e.g., add "TypeScript: X.Y" matching the shipped
implementation) or explicitly state that TypeScript uses the SDK default (e.g.,
"TypeScript: uses SDK default") so the "API version" row is complete and
unambiguous.

In `@tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts`:
- Around line 8-10: The tests declare module-level shared state (localstack,
ssmClient, provider) which can leak between test files; move these variables
into the describe block (or into a per-test fixture object) so each test suite
gets its own instance, e.g. declare them inside the describe callback and
initialize/cleanup in beforeAll/afterAll or beforeEach/afterEach using the
existing setup/teardown logic for LocalStackTestContainer, SSMClient, and
AwsSsmSecretProvider to avoid cross-file contamination and enable
parallelization.

In `@tests/sdks/typescript/application/envilder.test.ts`:
- Around line 99-110: Update the test
Should_ResolveFromMappedFile_When_EnvMappingProvided to assert both environment
variables are not injected: after calling Envilder.resolveFile('production',
...) and verifying mockReadFile was called, add an assertion that
process.env.API_KEY is undefined in addition to the existing process.env.DB_URL
check to mirror other resolve-only tests and prevent partial-injection
regressions; reference Envilder.resolveFile and mockReadFile in your change.
- Around line 51-56: Remove the redundant vi.restoreAllMocks() call from the
afterEach block in the test file: keep vi.clearAllMocks() and the environment
cleanup but delete vi.restoreAllMocks() because mocks are created via vi.mock()
and individual vi.fn() implementations such as mockProvider.getSecrets (and
other mocks like mockReadFile and mockCreateProvider) should not have their
implementations reverted by restoreAllMocks; this ensures each beforeEach can
reliably set mock implementations without unexpected resets.
- Around line 87-187: Tests for env-mapping behavior are split between
describe('resolveFile') and describe('environment routing'), causing duplicate
test names and scattered coverage; consolidate routing tests by moving the
env-mapping cases from Envilder.resolveFile (the tests named
Should_ReturnEmpty_When_EnvMapsToNull and
Should_ReturnEmpty_When_EnvNotInMapping) into the environment routing describe
block or refactor both blocks into one describe('environment routing') that
parameterizes over the two entry points (Envilder.resolveFile and
Envilder.load), and rename duplicated tests to indicate which method is under
test (e.g., ..._When_ResolveFileCalled vs ..._When_LoadCalled) so routing
behavior for both resolveFile and load is colocated and uniquely identified.

In `@tests/sdks/typescript/containers/localstack-container.ts`:
- Around line 25-30: The hard throw when LOCALSTACK_AUTH_TOKEN is missing causes
downstream afterAll to dereference an undefined localstack; change the behavior
in loadEnvironment / the block that checks
environment.get('LOCALSTACK_AUTH_TOKEN') so it does not throw: instead return
null/undefined (or a sentinel) from the function that creates the Localstack
container (referenced in localstack-container.ts) and add a brief comment above
the check explaining this is an intentional hard-fail/skip decision and that
callers should handle a missing localstack (e.g., tests should skip when the
returned value is falsy). Update callers to guard on the returned value (e.g.,
aws-ssm.acceptance.test.ts beforeAll/afterAll) so afterAll won’t dereference
localstack when setup failed.

In `@tests/sdks/typescript/containers/lowkey-vault-container.ts`:
- Around line 25-29: The LOWKEY_ARGS environment value hardcodes
--server.port=8443 which can drift from the HTTPS_PORT constant; update the
withEnvironment call that sets LOWKEY_ARGS to build the server.port value from
the HTTPS_PORT constant (e.g. use a template/string concatenation) so the value
stays in sync with withExposedPorts(HTTPS_PORT, HTTP_PORT); adjust any necessary
string conversion so LOWKEY_ARGS contains the numeric HTTPS_PORT at runtime.
- Around line 91-108: The outer try/catch around the await new Promise<boolean>
is dead because the promise always resolves, so remove the try/catch and rely on
the resolved boolean to decide retries: keep the loop using MAX_RETRIES, await
the Promise created around https.get (the new Promise<boolean> that resolves via
req, req.on('error'), and req.setTimeout), check the returned ok value and
continue/retry if false, and only return when ok is true; or alternatively
change the inner promise to reject on unexpected errors/timeouts instead of
resolving so the try/catch around the await becomes meaningful—update references
to MAX_RETRIES, the new Promise<boolean>, req, url, and agent accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d5c06f69-8550-471b-af95-c347db72ef3b

📥 Commits

Reviewing files that changed from the base of the PR and between 85b6e58 and bfed2f2.

⛔ Files ignored due to path filters (5)
  • .github/skills/sdk-acceptance-testing/SKILL.md is excluded by none and included by none
  • .github/workflows/coverage-report.yml is excluded by none and included by none
  • .github/workflows/publish-npm-sdk.yml is excluded by none and included by none
  • .github/workflows/tests-typescript-sdk.yml is excluded by none and included by none
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml and included by none
📒 Files selected for processing (8)
  • docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md
  • tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts
  • tests/sdks/typescript/acceptance/azure-key-vault.acceptance.test.ts
  • tests/sdks/typescript/application/envilder.test.ts
  • tests/sdks/typescript/application/map-file-parser.test.ts
  • tests/sdks/typescript/containers/localstack-container.ts
  • tests/sdks/typescript/containers/lowkey-vault-container.ts
  • tests/sdks/typescript/package.json
✅ Files skipped from review due to trivial changes (1)
  • tests/sdks/typescript/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/sdks/typescript/application/map-file-parser.test.ts

Comment on lines +90 to +99
```
tests/sdks/{lang}/
├── containers/ ← Container wrappers
│ ├── localstack-container
│ └── lowkey-vault-container
├── acceptance/ ← Acceptance tests
│ ├── aws-ssm.acceptance.*
│ └── azure-key-vault.acceptance.*
└── {unit tests}/
```
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.

⚠️ Potential issue | 🟡 Minor

Fix MD040: fenced code block needs a language identifier.

This is the failure reported by markdownlint in the Rainbow Road Lint job.

🛠️ Proposed fix
-```
+```text
 tests/sdks/{lang}/
 ├── containers/                   ← Container wrappers
 │   ├── localstack-container
 │   └── lowkey-vault-container
 ├── acceptance/                   ← Acceptance tests
 │   ├── aws-ssm.acceptance.*
 │   └── azure-key-vault.acceptance.*
 └── {unit tests}/
 ```
🧰 Tools
🪛 GitHub Actions: 🏁 Rainbow Road Lint

[error] 90-90: markdownlint failed (MD040/fenced-code-language): Fenced code blocks should have a language specified [Context: "```"].

🪛 GitHub Check: markdown-lint

[failure] 90-90:
MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]

🪛 markdownlint-cli2 (0.22.1)

[warning] 90-90: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md` around
lines 90 - 99, The fenced code block showing the directory tree (the triple
backticks before "tests/sdks/{lang}/" through the closing backticks) lacks a
language identifier and triggers MD040; update that fence (the opening ``` line)
to include a language token such as "text" (e.g., change "```" to "```text") so
markdownlint accepts the block while preserving the directory-tree content in
the file `docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md`.

Comment on lines +19 to +21
afterAll(async () => {
await localstack.stop();
});
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.

⚠️ Potential issue | 🔴 Critical

afterAll crashes when beforeAll fails — masks the real error.

The pipeline is failing here with TypeError: Cannot read properties of undefined (reading 'stop'). When LocalStackTestContainer.start() throws (e.g. LOCALSTACK_AUTH_TOKEN not resolvable in CI), the module-level localstack stays undefined, and the dereference in afterAll masks the original failure cause and reports a confusing error.

Guard the teardown so the underlying setup error surfaces.

🛠️ Proposed fix
   afterAll(async () => {
-    await localstack.stop();
+    await localstack?.stop();
   });
📝 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
afterAll(async () => {
await localstack.stop();
});
afterAll(async () => {
await localstack?.stop();
});
🧰 Tools
🪛 GitHub Actions: 🟦 TypeScript SDK Tests

[error] 20-20: TypeError: Cannot read properties of undefined (reading 'stop'). at tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts:20:22 (afterAll calls await localstack.stop()).


[error] Vitest failed suite: Failed Suites 1 (tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts > AWS SSM Acceptance).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts` around lines 19
- 21, The afterAll teardown currently assumes localstack was created and calls
localstack.stop(), which throws if beforeAll failed and localstack is undefined;
change the afterAll handler (and the module-level localstack declaration if
needed) to guard the shutdown call — e.g., only call stop when localstack is
truthy (or use optional chaining) so the real error from
LocalStackTestContainer.start() surfaces instead of a TypeError; update
references to LocalStackTestContainer, beforeAll, and afterAll accordingly.

Comment on lines +20 to +22
afterAll(async () => {
await lowkeyVault.stop();
});
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.

⚠️ Potential issue | 🟠 Major

Same teardown hazard as aws-ssm.acceptance.test.ts.

If LowkeyVaultTestContainer.start() fails (Docker unavailable, image pull failure, readiness timeout), lowkeyVault stays undefined and afterAll will throw TypeError: Cannot read properties of undefined (reading 'stop'), hiding the real setup error.

🛠️ Proposed fix
   afterAll(async () => {
-    await lowkeyVault.stop();
+    await lowkeyVault?.stop();
   });
📝 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
afterAll(async () => {
await lowkeyVault.stop();
});
afterAll(async () => {
await lowkeyVault?.stop();
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/acceptance/azure-key-vault.acceptance.test.ts` around
lines 20 - 22, The afterAll teardown unconditionally calls lowkeyVault.stop()
which throws if LowkeyVaultTestContainer.start() failed and lowkeyVault is
undefined; update the afterAll to guard the call (e.g., if (lowkeyVault) await
lowkeyVault.stop();) or wrap in try/catch so teardown only runs when lowkeyVault
was successfully assigned; locate the lowkeyVault symbol and the afterAll
closure to apply this defensive check.

Comment on lines +47 to +50
} catch (error) {
await this.stop();
throw error;
}
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.

⚠️ Potential issue | 🟠 Major

Failure path can clobber pre-existing env vars.

prevIdentityEndpoint / prevIdentityHeader / prevTlsRejectUnauthorized are captured on lines 40–42, immediately before the overrides. If GenericContainer.start() (line 24) or waitUntilReady() (line 38) throws, control jumps to the catch and calls this.stop(), whose finally runs restoreEnv('IDENTITY_ENDPOINT', undefined) etc. with previous still undefined. restoreEnv then deletes those env vars from process.env — even though we never overrode them — wiping out any pre-existing values the user had set (e.g., real Azure identity config in a dev environment).

Capture the previous values before the try (or only restore when an override was actually applied).

🛠️ Proposed fix
   async start(): Promise<LowkeyVaultTestContainer> {
     console.log('\n[LowkeyVault] Starting container...');
 
+    this.prevIdentityEndpoint = process.env.IDENTITY_ENDPOINT;
+    this.prevIdentityHeader = process.env.IDENTITY_HEADER;
+    this.prevTlsRejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
+    let envOverridden = false;
+
     try {
       this.container = await new GenericContainer(LOWKEY_VAULT_IMAGE)
         ...
       await this.waitUntilReady();
 
-      this.prevIdentityEndpoint = process.env.IDENTITY_ENDPOINT;
-      this.prevIdentityHeader = process.env.IDENTITY_HEADER;
-      this.prevTlsRejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
-
       process.env.IDENTITY_ENDPOINT = tokenUrl;
       process.env.IDENTITY_HEADER = 'dummy';
       process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
+      envOverridden = true;
     } catch (error) {
-      await this.stop();
+      await this.stop(envOverridden);
       throw error;
     }

(and have stop skip restoreEnv calls when envOverridden === false).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/containers/lowkey-vault-container.ts` around lines 47 -
50, The code captures
prevIdentityEndpoint/prevIdentityHeader/prevTlsRejectUnauthorized too late so a
thrown error from GenericContainer.start or waitUntilReady can cause this.stop()
to call restoreEnv and delete real env vars; fix by capturing those previous env
values before entering the try (or introduce an envOverridden boolean) and only
call restoreEnv for IDENTITY_ENDPOINT/IDENTITY_HEADER/TLS_REJECT_UNAUTHORIZED
when you actually set/overrode them; update the start logic around
GenericContainer.start and waitUntilReady to set envOverridden = true when
applying overrides and change this.stop to skip restoreEnv calls unless
envOverridden is true.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new TypeScript runtime SDK (@envilder/sdk) to the monorepo (layered domain/application/infrastructure), along with a dedicated Vitest test workspace (unit + acceptance tests), CI coverage integration, publish workflow, and website/docs updates to announce/support the SDK.

Changes:

  • Add TypeScript SDK source package (src/sdks/typescript) with AWS SSM + Azure Key Vault providers, factory, facade/builder, parser, and validation.
  • Add TypeScript SDK test workspace (tests/sdks/typescript) with Vitest config, unit tests, and TestContainers-based acceptance tests (LocalStack + Lowkey Vault).
  • Update website/docs/i18n + changelog infra, CI workflows (tests, coverage-report integration), pnpm workspace, and repo tooling scripts/hooks.

Reviewed changes

Copilot reviewed 53 out of 55 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/sdks/typescript/vitest.config.ts Adds Vitest config for the TS SDK test workspace (coverage + alias).
tests/sdks/typescript/tsconfig.json Adds TS config for the TS SDK test workspace.
tests/sdks/typescript/package.json Adds test workspace package + Vitest scripts/deps.
tests/sdks/typescript/infrastructure/secret-provider-factory.test.ts Unit tests for provider factory behavior.
tests/sdks/typescript/infrastructure/azure/azure-key-vault-secret-provider.test.ts Unit tests for Azure Key Vault provider.
tests/sdks/typescript/infrastructure/aws/aws-ssm-secret-provider.test.ts Unit tests for AWS SSM provider.
tests/sdks/typescript/containers/lowkey-vault-container.ts Lowkey Vault container wrapper for acceptance tests.
tests/sdks/typescript/containers/localstack-container.ts LocalStack container wrapper for acceptance tests (resolves LocalStack token).
tests/sdks/typescript/application/secret-validation.test.ts Unit tests for validateSecrets() and SecretValidationError.
tests/sdks/typescript/application/map-file-parser.test.ts Unit tests for map-file parsing (mappings + $config).
tests/sdks/typescript/application/envilder.test.ts Unit tests for facade overloads, env routing, and fluent builder overrides.
tests/sdks/typescript/application/envilder-client.test.ts Unit tests for core resolver + environment injection.
tests/sdks/typescript/acceptance/azure-key-vault.acceptance.test.ts Acceptance tests against Lowkey Vault (real SDK client/provider flow).
tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts Acceptance tests against LocalStack SSM (real provider calls).
tests/sdks/typescript/.gitkeep Removes placeholder file for the TS SDK tests folder.
src/website/src/pages/es/changelog.astro Adds TypeScript SDK changelog category to Spanish page.
src/website/src/pages/changelog.astro Adds TypeScript SDK changelog category to default page.
src/website/src/pages/ca/changelog.astro Adds TypeScript SDK changelog category to Catalan page.
src/website/src/i18n/types.ts Adds translation keys for TypeScript SDK docs/changelog/sidebar entries.
src/website/src/i18n/es.ts Updates TS SDK status + adds docs/changelog translations (ES).
src/website/src/i18n/en.ts Updates TS SDK status + adds docs/changelog translations (EN).
src/website/src/i18n/ca.ts Updates TS SDK status + adds docs/changelog translations (CA).
src/website/src/env.d.ts Adds global changelog constant for TypeScript SDK.
src/website/src/components/Sdks.astro Marks TS SDK as available and adds install/docs/code snippet on landing page.
src/website/src/components/DocsContent.astro Adds a full TypeScript SDK docs section (install, quickstart, fluent, env routing, validation).
src/website/astro.config.mjs Reads + injects TypeScript SDK changelog content into build defines.
src/sdks/typescript/tsconfig.json Adds TS SDK build config (Node16 module resolution, declarations, sourcemaps).
src/sdks/typescript/src/infrastructure/secret-provider-factory.ts Adds provider factory (createSecretProvider) with cross-provider option validation.
src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts Adds Azure Key Vault provider implementation (parallel fetch, 404 omission).
src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts Adds AWS SSM provider implementation (GetParameters batching + decryption).
src/sdks/typescript/src/index.ts Adds public barrel exports for the SDK (facade, client, parser, validation, types, providers).
src/sdks/typescript/src/domain/secret-provider-type.ts Adds provider type enum (aws/azure).
src/sdks/typescript/src/domain/ports/secret-provider.ts Adds provider port interface (getSecrets).
src/sdks/typescript/src/domain/parsed-map-file.ts Adds parsed map-file type (config + mappings).
src/sdks/typescript/src/domain/map-file-config.ts Adds map-file config type (provider/vaultUrl/profile).
src/sdks/typescript/src/domain/envilder-options.ts Adds runtime override options type (provider/vaultUrl/profile).
src/sdks/typescript/src/application/secret-validation.ts Adds validateSecrets() and SecretValidationError.
src/sdks/typescript/src/application/map-file-parser.ts Adds JSON map-file parser supporting $config and provider validation.
src/sdks/typescript/src/application/envilder.ts Adds main facade + fluent builder + env routing overloads.
src/sdks/typescript/src/application/envilder-client.ts Adds core resolver and process.env injector.
src/sdks/typescript/package.json Adds publishable npm package manifest for @envilder/sdk.
src/sdks/typescript/.gitkeep Removes placeholder file for the TS SDK source folder.
pnpm-workspace.yaml Adds TS SDK source + tests workspaces to pnpm workspace.
pnpm-lock.yaml Locks dependencies for the new workspaces.
package.json Updates root scripts (format, format:check, lint) behavior.
lefthook.yml Expands Biome file glob and runs both check+format on staged files.
docs/changelogs/sdk-typescript.md Adds TypeScript SDK changelog file for website integration.
docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md Documents acceptance test infra pattern across SDKs (ADR).
ROADMAP.md Moves TypeScript SDK from “Up Next” to shipped list.
.github/workflows/tests-typescript-sdk.yml Adds dedicated CI workflow for TS SDK tests.
.github/workflows/publish-npm-sdk.yml Adds npm publish workflow for the TypeScript SDK package.
.github/workflows/coverage-report/coverage-config.json Adds coverage thresholds entry for “typescript-sdk”.
.github/workflows/coverage-report.yml Adds a TypeScript SDK coverage job and includes it in aggregated dashboard.
.github/skills/sdk-acceptance-testing/SKILL.md Adds a skill doc describing SDK acceptance testing patterns.
.github/copilot-instructions.md Documents TypeScript SDK architecture/testing conventions for contributors.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +21 to +29
if (isAzure && profile) {
throw new Error(
'AWS profile cannot be used with Azure Key Vault provider.',
);
}

if (!isAzure && vaultUrl) {
throw new Error('Vault URL cannot be used with AWS SSM provider.');
}

for (const name of names) {
if (!name?.trim()) {
throw new Error('Secret name cannot be null or empty.');

for (const name of names) {
if (!name?.trim()) {
throw new Error('Secret name cannot be null or whitespace.');
Comment on lines +49 to +54
const actual = createSecretProvider(config);

// Assert
expect(actual).toBeDefined();
expect(actual.getSecret).toBeDefined();
});
Comment on lines +14 to +17
"paths": {
"@envilder/sdk": ["../../src/sdks/typescript/src/index.js"],
"@envilder/sdk/*": ["../../src/sdks/typescript/src/*"]
}
Comment on lines +12 to +16
super(
missingKeys.length === 0
? 'No secrets were resolved.'
: `The following secrets have empty or missing values: ${missingKeys.join(', ')}`,
);
vaultUrl: string | undefined,
): AzureKeyVaultSecretProvider {
if (!vaultUrl?.trim()) {
throw new Error('Vault URL must be provided for Azure Key Vault provider.');
);

for (const param of response.Parameters ?? []) {
if (param.Name && param.Value) {
Comment thread package.json
"lint": "secretlint \"**/*\" && biome check --write && tsc --noEmit",
"format": "biome check --write --unsafe && biome format --write",
"format:check": "biome check && biome format",
"lint": "secretlint \"**/*\" && biome check --write --unsafe && tsc --noEmit",
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/sdks/typescript/src/application/envilder.ts (1)

95-111: Minor: fluent setters mutate shared per-instance options.

withProvider/withVaultUrl/withProfile mutate this.options on the same Envilder instance, so reusing an instance across calls (e.g. resolving twice with different overrides) carries state. This matches the “fluent builder” style documented in the JSDoc, but if you ever want to support immutable composition in the future it would be cleaner to either return a new Envilder per call or freeze options after the first resolve(). Not blocking — just flagging for the public API contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/typescript/src/application/envilder.ts` around lines 95 - 111, The
fluent setters withProvider, withVaultUrl, and withProfile currently mutate
this.options on the same Envilder instance which causes shared mutable state;
change each setter to return a new Envilder with a cloned options object (e.g.
shallow copy of this.options plus the updated field) instead of mutating
this.options, or alternatively implement a copy constructor used by the setters
that builds a new instance with copied options; additionally, consider freezing
options after resolve() to prevent further mutation if immutability is desired.
src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts (1)

37-51: Optional: fetch SSM batches in parallel.

Batches are awaited sequentially, so 30 names → 3 round-trips serialized. For larger map files this latency adds up. Consider issuing the GetParametersCommand calls concurrently with Promise.all while preserving the merge order, since the result is keyed by param.Name.

♻️ Proposed refactor
-    for (let i = 0; i < names.length; i += SSM_BATCH_SIZE) {
-      const batch = names.slice(i, i + SSM_BATCH_SIZE);
-      const response = await this.ssmClient.send(
-        new GetParametersCommand({
-          Names: batch,
-          WithDecryption: true,
-        }),
-      );
-
-      for (const param of response.Parameters ?? []) {
-        if (param.Name && param.Value) {
-          result.set(param.Name, param.Value);
-        }
-      }
-    }
+    const batches: string[][] = [];
+    for (let i = 0; i < names.length; i += SSM_BATCH_SIZE) {
+      batches.push(names.slice(i, i + SSM_BATCH_SIZE));
+    }
+    const responses = await Promise.all(
+      batches.map((batch) =>
+        this.ssmClient.send(
+          new GetParametersCommand({ Names: batch, WithDecryption: true }),
+        ),
+      ),
+    );
+    for (const response of responses) {
+      for (const param of response.Parameters ?? []) {
+        if (param.Name && param.Value) {
+          result.set(param.Name, param.Value);
+        }
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts` around
lines 37 - 51, The batch GetParametersCommand calls in
aws-ssm-secret-provider.ts are executed sequentially; change the logic in the
method that uses SSM_BATCH_SIZE to build an array of promises calling
this.ssmClient.send(new GetParametersCommand({ Names: batch, WithDecryption:
true })) for each batch, await them with Promise.all, then iterate the combined
responses and populate the result Map (using param.Name and param.Value) — keep
using SSM_BATCH_SIZE, this.ssmClient.send, GetParametersCommand, and the
existing result.set(...) merging by param.Name so concurrency doesn’t change
final contents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/sdks/typescript/src/application/envilder.ts`:
- Around line 56-93: The TypeScript SDK currently throws in resolveEnvSource
when an environment key is missing; update resolveEnvSource so that when the
normalized env key is not found in the provided envMapping it returns null (not
throw) to preserve the "missing/null mapping → empty dict" behavior used by load
and resolveFile; locate the resolveEnvSource function and replace the exception
path with a return null, keeping any normalization/trimming logic intact so
callers (Envilder.load and Envilder.resolveFile) can detect null and return an
empty Map.

In `@src/sdks/typescript/src/infrastructure/secret-provider-factory.ts`:
- Around line 21-25: The multi-line throw inside the if (isAzure && profile)
block should be collapsed to a single line to satisfy Biome formatting; replace
the current block's body with a single-line throw: throw new Error('AWS profile
cannot be used with Azure Key Vault provider'); ensuring single quotes and a
terminating semicolon, leaving the if condition (isAzure && profile) intact.

In `@tests/sdks/typescript/containers/localstack-container.ts`:
- Around line 37-40: The test hardcodes the Docker container name by calling
.withName('localstack') on the LocalstackContainer so repeated runs can fail
with "container name already in use"; remove the explicit .withName call (or
replace it with a unique name generator / suffix) where the LocalstackContainer
is created so Testcontainers can assign a unique name (or the name is suffixed
with a random/UUID value) before calling .start(); update the
LocalstackContainer instantiation site accordingly (the expression creating
this.container) to avoid fixed container names.

---

Nitpick comments:
In `@src/sdks/typescript/src/application/envilder.ts`:
- Around line 95-111: The fluent setters withProvider, withVaultUrl, and
withProfile currently mutate this.options on the same Envilder instance which
causes shared mutable state; change each setter to return a new Envilder with a
cloned options object (e.g. shallow copy of this.options plus the updated field)
instead of mutating this.options, or alternatively implement a copy constructor
used by the setters that builds a new instance with copied options;
additionally, consider freezing options after resolve() to prevent further
mutation if immutability is desired.

In `@src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts`:
- Around line 37-51: The batch GetParametersCommand calls in
aws-ssm-secret-provider.ts are executed sequentially; change the logic in the
method that uses SSM_BATCH_SIZE to build an array of promises calling
this.ssmClient.send(new GetParametersCommand({ Names: batch, WithDecryption:
true })) for each batch, await them with Promise.all, then iterate the combined
responses and populate the result Map (using param.Name and param.Value) — keep
using SSM_BATCH_SIZE, this.ssmClient.send, GetParametersCommand, and the
existing result.set(...) merging by param.Name so concurrency doesn’t change
final contents.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ebb99252-a67d-4d5c-b93f-c01b0ef50d1b

📥 Commits

Reviewing files that changed from the base of the PR and between bfed2f2 and 3e60dbe.

⛔ Files ignored due to path filters (1)
  • .github/copilot-instructions.md is excluded by none and included by none
📒 Files selected for processing (11)
  • src/sdks/typescript/package.json
  • src/sdks/typescript/src/application/envilder.ts
  • src/sdks/typescript/src/application/secret-validation.ts
  • src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts
  • src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts
  • src/sdks/typescript/src/infrastructure/secret-provider-factory.ts
  • tests/sdks/typescript/application/envilder.test.ts
  • tests/sdks/typescript/containers/localstack-container.ts
  • tests/sdks/typescript/containers/lowkey-vault-container.ts
  • tests/sdks/typescript/tsconfig.json
  • tests/sdks/typescript/vitest.config.ts
✅ Files skipped from review due to trivial changes (5)
  • tests/sdks/typescript/tsconfig.json
  • src/sdks/typescript/package.json
  • tests/sdks/typescript/vitest.config.ts
  • tests/sdks/typescript/application/envilder.test.ts
  • src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/sdks/typescript/src/application/secret-validation.ts
  • tests/sdks/typescript/containers/lowkey-vault-container.ts

Comment on lines +56 to +93
static async load(
filePathOrEnv: string,
envMapping?: Record<string, string | null>,
): Promise<Map<string, string>> {
if (envMapping !== undefined) {
const source = resolveEnvSource(filePathOrEnv, envMapping);
if (source === null) {
return new Map();
}
return new Envilder(source).inject();
}

validateFilePath(filePathOrEnv);
return new Envilder(filePathOrEnv.trim()).inject();
}

/**
* Resolves secrets without injecting into `process.env`.
*
* Can be called in two ways:
* - `resolveFile(filePath)` — resolve from a single map file
* - `resolveFile(env, envMapping)` — look up env in the mapping
*/
static async resolveFile(
filePathOrEnv: string,
envMapping?: Record<string, string | null>,
): Promise<Map<string, string>> {
if (envMapping !== undefined) {
const source = resolveEnvSource(filePathOrEnv, envMapping);
if (source === null) {
return new Map();
}
return new Envilder(source).resolve();
}

validateFilePath(filePathOrEnv);
return new Envilder(filePathOrEnv.trim()).resolve();
}
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare env-routing semantics across SDKs.
fd -t f 'Envilder.cs' src/sdks/dotnet | xargs -I{} sed -n '1,260p' {}
fd -t f 'envilder.py' src/sdks/python | xargs -I{} sed -n '1,260p' {}

Repository: macalbert/envilder

Length of output: 11848


🏁 Script executed:

fd -t f 'envilder.ts' src/sdks/typescript | head -1 | xargs cat

Repository: macalbert/envilder

Length of output: 5219


🏁 Script executed:

fd -t f 'envilder.py' src/sdks/python | head -1 | xargs cat

Repository: macalbert/envilder

Length of output: 44


🏁 Script executed:

fd -t f '__init__.py' src/sdks/python/application | head -1 | xargs cat

Repository: macalbert/envilder

Length of output: 177


🏁 Script executed:

find src/sdks/python -name '*.py' -type f | grep -i envilder | head -5

Repository: macalbert/envilder

Length of output: 316


🏁 Script executed:

find src/sdks/python -name '*.py' -type f | head -10

Repository: macalbert/envilder

Length of output: 572


🏁 Script executed:

cat src/sdks/python/envilder/application/envilder_facade.py

Repository: macalbert/envilder

Length of output: 8524


TypeScript SDK does not maintain behavioral parity with .NET and Python SDKs for missing environment keys.

The TypeScript resolveEnvSource throws an error when the environment key is not found in the mapping (Environment '${normalized}' not found in environment mapping.), while both .NET and Python return null to allow the caller to handle missing environments as intentional skips. This breaks the designed "missing/null mapping → empty dict" pattern. Update TypeScript's resolveEnvSource to return null (instead of throwing) when the environment key is not found, matching the behavior of the other SDKs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/sdks/typescript/src/application/envilder.ts` around lines 56 - 93, The
TypeScript SDK currently throws in resolveEnvSource when an environment key is
missing; update resolveEnvSource so that when the normalized env key is not
found in the provided envMapping it returns null (not throw) to preserve the
"missing/null mapping → empty dict" behavior used by load and resolveFile;
locate the resolveEnvSource function and replace the exception path with a
return null, keeping any normalization/trimming logic intact so callers
(Envilder.load and Envilder.resolveFile) can detect null and return an empty
Map.

Comment thread src/sdks/nodejs/src/infrastructure/secret-provider-factory.ts
Comment on lines +37 to +40
this.container = await new LocalstackContainer(LOCALSTACK_IMAGE)
.withName('localstack')
.withEnvironment(envRecord)
.start();
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.

⚠️ Potential issue | 🟡 Minor

Hardcoded container name may break re-runs.

.withName('localstack') assigns a fixed Docker container name. If a previous run was interrupted (or another acceptance suite reuses the name on the same host/CI runner), the next start() will fail with "container name already in use". Either drop the explicit name (Testcontainers generates a unique name and still cleans up via Ryuk) or scope it with a random suffix.

🛠 Proposed fix
-    this.container = await new LocalstackContainer(LOCALSTACK_IMAGE)
-      .withName('localstack')
-      .withEnvironment(envRecord)
-      .start();
+    this.container = await new LocalstackContainer(LOCALSTACK_IMAGE)
+      .withEnvironment(envRecord)
+      .start();
📝 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
this.container = await new LocalstackContainer(LOCALSTACK_IMAGE)
.withName('localstack')
.withEnvironment(envRecord)
.start();
this.container = await new LocalstackContainer(LOCALSTACK_IMAGE)
.withEnvironment(envRecord)
.start();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/sdks/typescript/containers/localstack-container.ts` around lines 37 -
40, The test hardcodes the Docker container name by calling
.withName('localstack') on the LocalstackContainer so repeated runs can fail
with "container name already in use"; remove the explicit .withName call (or
replace it with a unique name generator / suffix) where the LocalstackContainer
is created so Testcontainers can assign a unique name (or the name is suffixed
with a random/UUID value) before calling .start(); update the
LocalstackContainer instantiation site accordingly (the expression creating
this.container) to avoid fixed container names.

Copilot AI review requested due to automatic review settings April 26, 2026 22:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new standalone TypeScript runtime SDK (@envilder/sdk) to Envilder, plus test infrastructure, CI workflows, and website/docs wiring so the SDK is shipped and visible alongside the .NET and Python SDKs.

Changes:

  • Introduces the TypeScript SDK (domain/application/infrastructure layers) with AWS SSM + Azure Key Vault providers, fluent facade, map parsing, and optional validation.
  • Adds a dedicated Vitest test workspace including unit + acceptance tests (LocalStack + Lowkey Vault container wrappers).
  • Wires the SDK into the website (docs, changelog, i18n, version badges) and CI (test + coverage + npm publish).

Reviewed changes

Copilot reviewed 56 out of 58 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/sdks/typescript/vitest.config.ts Vitest config for TS SDK tests (root, coverage, alias).
tests/sdks/typescript/tsconfig.json TS config for the TS SDK test workspace (paths + strict).
tests/sdks/typescript/package.json Test workspace package and dev dependencies.
tests/sdks/typescript/infrastructure/secret-provider-factory.test.ts Unit tests for provider factory behavior.
tests/sdks/typescript/infrastructure/azure/azure-key-vault-secret-provider.test.ts Unit tests for Azure provider behavior.
tests/sdks/typescript/infrastructure/aws/aws-ssm-secret-provider.test.ts Unit tests for AWS provider behavior.
tests/sdks/typescript/containers/lowkey-vault-container.ts Lowkey Vault testcontainer wrapper for acceptance tests.
tests/sdks/typescript/containers/localstack-container.ts LocalStack testcontainer wrapper + token resolution.
tests/sdks/typescript/application/secret-validation.test.ts Unit tests for validateSecrets.
tests/sdks/typescript/application/map-file-parser.test.ts Unit tests for map-file parsing.
tests/sdks/typescript/application/envilder.test.ts Unit tests for the Envilder facade + fluent builder.
tests/sdks/typescript/application/envilder-client.test.ts Unit tests for the core resolver/injector.
tests/sdks/typescript/acceptance/azure-key-vault.acceptance.test.ts Acceptance tests against Lowkey Vault.
tests/sdks/typescript/acceptance/aws-ssm.acceptance.test.ts Acceptance tests against LocalStack SSM.
tests/sdks/typescript/.gitkeep Removes placeholder file now that tests exist.
src/website/src/pages/es/changelog.astro Adds TS SDK to Spanish changelog navigation/products.
src/website/src/pages/changelog.astro Adds TS SDK to default (en) changelog navigation/products.
src/website/src/pages/ca/changelog.astro Adds TS SDK to Catalan changelog navigation/products.
src/website/src/i18n/types.ts Adds i18n keys for TS SDK docs + changelog category.
src/website/src/i18n/es.ts Spanish translations + roadmap status update for TS SDK.
src/website/src/i18n/en.ts English translations + roadmap status update for TS SDK.
src/website/src/i18n/ca.ts Catalan translations + roadmap status update for TS SDK.
src/website/src/env.d.ts Declares new changelog/version build-time globals.
src/website/src/components/Sdks.astro Marks TS SDK as available + adds install/docs/code sample.
src/website/src/components/DocsContent.astro Adds TS SDK docs section + dynamic version badges.
src/website/astro.config.mjs Reads TS SDK package version + defines new globals.
src/sdks/typescript/tsconfig.json TS SDK build config (Node16 ESM, declarations, dist).
src/sdks/typescript/src/infrastructure/secret-provider-factory.ts Provider factory (AWS/Azure selection + validation).
src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.ts Azure Key Vault provider implementation.
src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.ts AWS SSM provider implementation (batch GetParameters).
src/sdks/typescript/src/index.ts Public barrel exports for the TS SDK package.
src/sdks/typescript/src/domain/secret-provider-type.ts Provider enum for AWS/Azure.
src/sdks/typescript/src/domain/ports/secret-provider.ts ISecretProvider port definition.
src/sdks/typescript/src/domain/parsed-map-file.ts Parsed map-file type.
src/sdks/typescript/src/domain/map-file-config.ts $config shape for map files.
src/sdks/typescript/src/domain/envilder-options.ts Runtime override options shape.
src/sdks/typescript/src/application/secret-validation.ts validateSecrets + SecretValidationError.
src/sdks/typescript/src/application/map-file-parser.ts JSON map-file parser ($config + mappings).
src/sdks/typescript/src/application/envilder.ts Facade API (load/resolve/env routing/fluent builder).
src/sdks/typescript/src/application/envilder-client.ts Core resolver and environment injector.
src/sdks/typescript/package.json TS SDK package metadata, deps, build scripts, exports.
src/sdks/typescript/.gitkeep Removes placeholder file now that SDK exists.
pnpm-workspace.yaml Adds TS SDK + TS SDK tests as workspace packages.
pnpm-lock.yaml Locks new workspace deps (AWS/Azure SDKs, Vitest, etc.).
package.json Updates root formatting/lint scripts (Biome check+format).
lefthook.yml Expands Biome pre-commit glob + runs format after check.
docs/changelogs/sdk-typescript.md Adds TS SDK changelog file for website integration.
docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md ADR documenting SDK acceptance test infra patterns.
ROADMAP.md Moves TS SDK from “Up Next” to “Shipped” section.
.github/workflows/tests-typescript-sdk.yml New CI workflow to build + test the TS SDK (Vitest).
.github/workflows/publish-npm-sdk.yml New CI workflow to publish TS SDK to npm + tag/release.
.github/workflows/coverage-report/coverage-config.json Adds TS SDK to coverage dashboard config.
.github/workflows/coverage-report.yml Adds TS SDK coverage job + aggregates into dashboard.
.github/skills/sdk-release-checklist/SKILL.md New checklist skill for wiring new SDK releases end-to-end.
.github/skills/sdk-acceptance-testing/SKILL.md New skill documenting acceptance testing patterns for SDKs.
.github/skills/doc-sync/SKILL.md Adds doc-sync guidance for new SDKs/version bumps.
.github/copilot-instructions.md Documents TypeScript SDK architecture and commands.
.github/agents/website-designer.agent.md Updates website agent guidance re SDK wiring/version globals.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread package.json
"format": "biome format",
"format:write": "biome format --write",
"lint": "secretlint \"**/*\" && biome check --write && tsc --noEmit",
"format": "biome check --write --unsafe && biome format --write",
Comment on lines +15 to +17
const LOCALSTACK_IMAGE = 'localstack/localstack:stable';
const SECRETS_MAP = path.resolve(__dirname, '../../../../secrets-map.json');

Comment on lines +37 to +40
this.container = await new LocalstackContainer(LOCALSTACK_IMAGE)
.withName('localstack')
.withEnvironment(envRecord)
.start();
Comment on lines +24 to +27
this.container = await new GenericContainer(LOWKEY_VAULT_IMAGE)
.withName('lowkey-vault')
.withExposedPorts(HTTPS_PORT, HTTP_PORT)
.withEnvironment({
Comment on lines +6 to +54
// Mock the AWS and Azure provider modules so we don't need real cloud SDKs
vi.mock(
'../../../../src/sdks/typescript/src/infrastructure/aws/aws-ssm-secret-provider.js',
() => ({
AwsSsmSecretProvider: class {
getSecret = vi.fn();
},
}),
);

vi.mock(
'../../../../src/sdks/typescript/src/infrastructure/azure/azure-key-vault-secret-provider.js',
() => ({
AzureKeyVaultSecretProvider: class {
getSecret = vi.fn();
},
}),
);

// Mock AWS SDK
vi.mock('@aws-sdk/client-ssm', () => ({
SSMClient: class {},
}));

vi.mock('@aws-sdk/credential-providers', () => ({
fromIni: vi.fn().mockReturnValue({}),
}));

// Mock Azure SDK
vi.mock('@azure/identity', () => ({
DefaultAzureCredential: class {},
}));

vi.mock('@azure/keyvault-secrets', () => ({
SecretClient: class {},
}));

describe('SecretProviderFactory', () => {
it('Should_CreateAwsProvider_When_NoProviderSpecified', () => {
// Arrange
const config = {};

// Act
const actual = createSecretProvider(config);

// Assert
expect(actual).toBeDefined();
expect(actual.getSecret).toBeDefined();
});
);

for (const param of response.Parameters ?? []) {
if (param.Name && param.Value) {
Comment on lines +48 to +51
function createAwsProvider(profile: string | undefined): AwsSsmSecretProvider {
const clientOptions = profile ? { credentials: fromIni({ profile }) } : {};
const client = new SSMClient(clientOptions);
return new AwsSsmSecretProvider(client);

if (!source.trim()) {
throw new Error(
`env_mapping contains an empty file path for environment '${normalized}'.`,
BREAKING CHANGE: sdk-typescript renamed to sdk-nodejs across all paths,
CI workflows, documentation, website i18n, changelogs, and configs.

- src/sdks/typescript/ -> src/sdks/nodejs/
- tests/sdks/typescript/ -> tests/sdks/nodejs/
- .github/workflows/tests-typescript-sdk.yml -> tests-nodejs-sdk.yml
- docs/changelogs/sdk-typescript.md -> sdk-nodejs.md
- Commit scope: sdk-ts -> sdk-node
- Git tags: sdk-typescript/vX.Y.Z -> sdk-nodejs/vX.Y.Z
- Website i18n: sdkTypescript* -> sdkNodejs*
- Logo: sdk-typescript.svg -> sdk-logo.svg
@macalbert macalbert changed the title feat(sdk-ts): add TypeScript SDK feat(sdk-node): add Node.js SDK Apr 27, 2026
@macalbert
Copy link
Copy Markdown
Owner Author

Replaced by new PR with correct branch name (feat/sdk-nodejs).

@macalbert macalbert closed this Apr 27, 2026
@macalbert macalbert deleted the feat/sdk-typescript branch April 27, 2026 07:00
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 15 additional findings in Devin Review.

Open in Devin Review

Comment on lines +104 to +106
- name: 🚩 Publish to npm
if: steps.version-check.outputs.version_changed == 'true'
run: npm publish --provenance --access public
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Missing NODE_AUTH_TOKEN environment variable causes npm publish to fail

The npm publish --provenance --access public step at line 106 lacks the NODE_AUTH_TOKEN environment variable needed to authenticate with the npm registry. The actions/setup-node step (line 41-44) with registry-url: "https://registry.npmjs.org" generates an .npmrc containing //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}, but since NODE_AUTH_TOKEN is never set, npm will fail with an authentication error when attempting to publish. Unless the @envilder/sdk npm package is specifically configured for GitHub Actions OIDC trusted publishing (which bypasses the need for a token), this step will fail every time the publish workflow triggers.

Expected pattern per GitHub Actions docs
- name: 🚩 Publish to npm
  if: steps.version-check.outputs.version_changed == 'true'
  run: npm publish --provenance --access public
  env:
    NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Note: the existing publish-npm.yml has the same missing env pattern, so this may be intentional if npm trusted publishers (OIDC) is configured for the package.

Suggested change
- name: 🚩 Publish to npm
if: steps.version-check.outputs.version_changed == 'true'
run: npm publish --provenance --access public
- name: 🚩 Publish to npm
if: steps.version-check.outputs.version_changed == 'true'
run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants