Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1353399
feat(sdk-ts): add TypeScript SDK source
macalbert Apr 25, 2026
dbe00a2
test(sdk-ts): add unit test suite
macalbert Apr 25, 2026
35a5be6
docs(sdk-ts): update website, docs, and roadmap
macalbert Apr 25, 2026
d1a64b8
style(sdk-ts): apply biome formatting to JSON configs
macalbert Apr 25, 2026
856060b
Merge branch 'main' into feat/sdk-typescript
macalbert Apr 25, 2026
d526582
fix(sdk-ts): correct vitest coverage config to resolve source files
macalbert Apr 25, 2026
74aaed2
style(sdk-ts): apply biome useOptionalChain auto-fix
macalbert Apr 25, 2026
fd8843f
test(sdk-ts): fix AAA conventions and add resolveFile env routing test
macalbert Apr 25, 2026
978dfb3
ci(sdk-ts): add TypeScript SDK to coverage report workflow
macalbert Apr 25, 2026
c64ad8c
ci(sdk-ts): add test and publish workflows for TypeScript SDK
macalbert Apr 25, 2026
91fb8fc
ci(sdk-ts): use OIDC provenance for npm publish
macalbert Apr 25, 2026
9415e30
style(sdk-ts): add trailing newline to coverage config
macalbert Apr 25, 2026
5e1cd36
chore: auto-fix formatting on pre-push hook
macalbert Apr 25, 2026
1663f9d
style(coverage): Add trailing newline to coverage config
macalbert Apr 25, 2026
2e39230
revert: restore read-only pre-push format check
macalbert Apr 25, 2026
90dbe70
fix(sdk-ts): use absolute path for test-results.xml output
macalbert Apr 25, 2026
1731f30
fix(sdk-ts): use absolute path for coverage reportsDirectory
macalbert Apr 25, 2026
4dc4ef4
refactor(sdk-ts): change ISecretProvider to batch getSecrets API
macalbert Apr 26, 2026
63ce58d
fix: add biome format to pre-commit hook
macalbert Apr 26, 2026
44e4a98
fix(sdk-ts): add type module, publishConfig, and test scripts to pack…
macalbert Apr 26, 2026
f54133a
test(sdk-ts): fix test conventions and strengthen assertions
macalbert Apr 26, 2026
f756120
fix(sdk-ts): improve parser validation and facade consistency
macalbert Apr 26, 2026
7b2d54d
style(sdk-ts): reorganize exports, fix indentation, and reorder i18n …
macalbert Apr 26, 2026
b54faa6
fix(website): render TypeScript SDK changelog on all locale pages
macalbert Apr 26, 2026
d5c9131
style: fix CRLF line endings and biome formatting
macalbert Apr 26, 2026
282e451
fix: align lefthook pre-commit and pre-push hooks to prevent format d…
macalbert Apr 26, 2026
d198197
fix: make pnpm format auto-fix check and formatting issues
macalbert Apr 26, 2026
b8605d2
fix: add jsonc to lefthook pre-commit glob
macalbert Apr 26, 2026
bd9c882
fix: remove broken format:staged script and restore direct pnpx in pr…
macalbert Apr 26, 2026
85b6e58
fix(package): Add missing newline at end of file
macalbert Apr 26, 2026
bfed2f2
test(sdk-ts): add acceptance tests and missing unit test coverage
macalbert Apr 26, 2026
588f27f
feat(container): Add names to LocalStack and LowkeyVault containers
macalbert Apr 26, 2026
984983e
fix(sdk-ts): throw when env key not found in environment mapping
macalbert Apr 26, 2026
438cee7
fix(sdk-ts): fix ESM compat, tsconfig paths, and test isolation
macalbert Apr 26, 2026
9715cc9
fix(sdk-ts): remove trailing periods from error messages
macalbert Apr 26, 2026
642dfa9
fix(sdk-ts): add exports field to restrict public entrypoints
macalbert Apr 26, 2026
3e60dbe
docs(sdk-ts): clarify createSecretProvider is not re-exported from ba…
macalbert Apr 26, 2026
35057db
feat(docs): Add SDK release checklist and update version handling
macalbert Apr 26, 2026
79037fa
refactor(sdk): rename TypeScript SDK to Node.js SDK
macalbert Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/agents/website-designer.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ For each breakpoint:
|---------|-------------|-----|
| After any component edit with user-visible text | `@i18n Reviewer` | Verify translations are complete and correct |
| Website changes affect documented features or CLI usage | `@Document Maintainer` | Keep docs in sync |
| Adding or updating an SDK on the website | `sdk-release-checklist` skill | Ensures version badge, changelog, and i18n are all wired |
| Website code needs quality review | `@Code Reviewer` | Multi-perspective read-only analysis |
| Website JS/TS logic has a bug | `@Bug Hunter` | Reproduce and fix via TDD |
| CSS or layout needs structural cleanup | `@Code Refactorer` | Safe incremental improvements |
Expand All @@ -293,6 +294,7 @@ After all work complete: "Run `/smart-commit` to commit, then `/pr-sync` to open
design system
- DO NOT hardcode colors — always use CSS variables from `global.css`
- DO NOT hardcode user-visible text — always use the i18n system
- DO NOT hardcode version numbers — use `__SDK_*_VERSION__` / `__APP_VERSION__` globals from `astro.config.mjs`
- DO NOT use fixed pixel font sizes — use `clamp()` or relative units
- DO NOT break existing responsive layouts when adding new sections
- DO NOT add JavaScript frameworks (React, Vue, etc.) — use Astro components
Expand Down
35 changes: 35 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,38 @@ AAA pattern with comment markers.
- `make check-sdk-python` (black + isort + mypy strict)
- `make format-sdk-python` (auto-format)
- `make test-sdk-python` (all tests, requires Docker for acceptance)

### Node.js SDK (`src/sdks/nodejs/`)

**Architecture**: Layered (Domain → Application → Infrastructure), no DI framework.

- **Domain** (`src/domain/`): `ISecretProvider` interface (async `getSecrets` batch method), `MapFileConfig`, `EnvilderOptions`, `ParsedMapFile` types, `SecretProviderType` enum
- **Application** (`src/application/`):
- `Envilder` — Async facade (`load`, `resolveFile`, `fromMapFile` + env-routing overloads)
- `EnvilderClient` — Core resolver (`resolveSecrets`, `injectIntoEnvironment` static method sets `process.env`)
- `MapFileParser` — Parses `$config` + variable mappings from JSON
- `validateSecrets` — Opt-in validation throws `SecretValidationError` for empty/missing values
- **Infrastructure** (`src/infrastructure/`):
- `createSecretProvider` (not re-exported from barrel) — Creates provider from `MapFileConfig` + optional `EnvilderOptions` overrides
- `AwsSsmSecretProvider` — `GetParametersCommand` (batch, up to 10 per request) with `WithDecryption: true`, missing parameters silently omitted via `InvalidParameters`
- `AzureKeyVaultSecretProvider` — `Promise.all` over `SecretClient.getSecret()` calls, catches 404 → omitted

Comment thread
macalbert marked this conversation as resolved.
**Key patterns**:

- Async-first — all provider and facade methods return `Promise`
- Interface-based ports — TypeScript `interface` for `ISecretProvider`
- `createSecretProvider` is not re-exported from the public barrel (`index.ts`) — consumers use the `Envilder` facade
- `Envilder` facade is the primary public API (fluent: `fromMapFile().withProvider().withVaultUrl().inject()`)
- `ISecretProvider.getSecrets(names[])` returns `Map<string, string>` — missing secrets silently omitted
- `EnvilderClient.resolveSecrets()` delegates to `getSecrets()` in a single call
- `validateSecrets()` — opt-in post-resolution validation
- Cross-provider validation: profile + Azure → error, vaultUrl + AWS → error
- `Map<string, string>` used for mappings and resolved secrets

**Tests** (`tests/sdks/nodejs/`): Vitest with `Should_<Expected>_When_<Condition>` naming.
AAA pattern with comment markers. `vi.fn()` for mocks.

**Build & test**:

- `cd src/sdks/nodejs && pnpm build` (TypeScript compilation)
- `cd tests/sdks/nodejs && pnpm vitest run --reporter=verbose` (unit tests)
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ updates:

- package-ecosystem: npm
directories:
- "/src/sdks/typescript"
- "/tests/sdks/typescript"
- "/src/sdks/nodejs"
- "/tests/sdks/nodejs"
pull-request-branch-name:
separator: "/"
schedule:
Expand Down
2 changes: 2 additions & 0 deletions .github/skills/doc-sync/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ Use this matrix to ensure consistency when updating a feature:
| CLI flag in `Cli.ts` | `docs/pull-command.md` or `docs/push-command.md`, root README, DocsContent, i18n |
| GHA input in `action.yml` | `github-action/README.md`, `docs/github-action.md`, website DocsContent, i18n |
| SDK public API | SDK README, examples README, website DocsContent + Sdks.astro, i18n |
| New SDK added | Run full `sdk-release-checklist` skill (version badge, changelog, i18n, docs) |
| SDK version bump | Bump canonical source file; changelog entry; website picks up version at build time |
| New provider | All provider listings: root README, website Providers.astro, DocsContent, SDK READMEs |
| Map-file format | Root README, all SDK READMEs, website DocsContent |
| ROADMAP status | `ROADMAP.md`, website Roadmap.astro |
Expand Down
196 changes: 196 additions & 0 deletions .github/skills/sdk-acceptance-testing/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
---
name: sdk-acceptance-testing
description: >-
Acceptance testing patterns for Envilder SDKs using TestContainers
(LocalStack for AWS SSM, Lowkey Vault for Azure Key Vault). Use when
adding acceptance tests to any SDK, creating container wrappers, or
updating CI workflows for SDK test infrastructure.
---

# SDK Acceptance Testing

Patterns for acceptance testing Envilder SDKs against real cloud provider
emulators. Applies to all SDKs (.NET, Python, TypeScript, Go, Java).

See [ADR-0001](../../../docs/architecture/adr/0001-sdk-acceptance-test-infrastructure.md)
for the architectural decision behind these patterns.

## When to Use

- Adding acceptance tests to a new or existing SDK
- Creating container wrappers for LocalStack or Lowkey Vault
- Updating CI workflows to support SDK acceptance tests
- Adding a new SDK and need to replicate test infrastructure

## Directory Structure

Every SDK test directory follows this layout:

```txt
tests/sdks/{lang}/
├── containers/ ← Container wrapper modules
│ ├── localstack-container.*
│ └── lowkey-vault-container.*
├── acceptance/ ← Acceptance test files
│ ├── aws-ssm.acceptance.*
│ └── azure-key-vault.acceptance.*
└── {unit-tests}/ ← Language-specific unit test dirs
```

## secrets-map.json Pattern

All SDKs reference the **root** `secrets-map.json` at the repository root
directly. Container wrappers navigate to it via a relative path — there are no
copies per SDK test directory.

```json
{
"$config": {
"provider": "aws",
"profile": "mac"
},
"LOCALSTACK_AUTH_TOKEN": "/envilder/development/localstack/authToken"
}
```

**Resolution behavior:**

| Environment | How it works |
|-------------|-------------|
| Local dev | `$config.profile` resolves AWS credentials from `~/.aws/credentials` |
| CI (GitHub Actions) | Profile is ignored; OIDC provides credentials via `aws-actions/configure-aws-credentials` |

**Path resolution example (TypeScript):**

```typescript
// From tests/sdks/nodejs/containers/localstack-container.ts
const SECRETS_MAP = path.resolve(__dirname, '../../../../secrets-map.json');
```

**Fallback pattern:** If the configured provider cannot be created (e.g., Azure
credentials missing in a CI environment that only has AWS OIDC), fall back to
AWS provider to resolve the token.

## LocalStack Container Wrapper

### Requirements

- Image: `localstack/localstack:stable`
- Resolve `LOCALSTACK_AUTH_TOKEN` from `secrets-map.json` before starting
- Throw if token is empty (fail fast)
- Expose: endpoint URL, SSM client, provider instance

### Lifecycle

```txt
1. Parse secrets-map.json with SDK's own MapFileParser
2. Resolve LOCALSTACK_AUTH_TOKEN using SDK's own EnvilderClient
3. Start container with token as environment variable
4. Expose connection URL for SSM client creation
```

## Lowkey Vault Container Wrapper

### Requirements

- Image: `nagyesta/lowkey-vault:7.1.61` (pinned)
- Ports: 8443 (HTTPS vault), 8080 (HTTP token endpoint)
- Args: `--server.port=8443 --LOWKEY_VAULT_RELAXED_PORTS=true`
- Set `IDENTITY_ENDPOINT` and `IDENTITY_HEADER` env vars for
`DefaultAzureCredential`
- Self-signed TLS: disable certificate verification in test clients
- Restore original env vars on teardown

### Lifecycle

```txt
1. Start container with Lowkey Vault args
2. Wait for HTTPS port to be ready (health check /ping)
3. Set IDENTITY_ENDPOINT = http://{host}:{http_port}/metadata/identity/oauth2/token
4. Set IDENTITY_HEADER = "dummy"
5. Create SecretClient with TLS verification disabled
6. On teardown: restore original IDENTITY_ENDPOINT/IDENTITY_HEADER
```

## Acceptance Test Patterns

### Test Naming

Same convention as unit tests: `Should_{Expected}_When_{Condition}`

### Standard Tests per Provider

Every SDK should have at minimum these acceptance tests:

**AWS SSM:**

1. `Should_ResolveSecretFromSsm_When_ParameterExistsInLocalStack`
2. `Should_ReturnEmptyForMissingSsmParameter_When_ParameterDoesNotExist`

**Azure Key Vault:**

1. `Should_ResolveSecretFromKeyVault_When_SecretExistsInLowkeyVault`
2. `Should_ReturnEmptyForMissingKeyVaultSecret_When_SecretDoesNotExist`

### Test Structure (AAA)

```typescript
it('Should_ResolveSecretFromSsm_When_ParameterExistsInLocalStack', async () => {
// Arrange
await ssmClient.send(new PutParameterCommand({
Name: '/Test/MySecret',
Value: 'real-secret-from-localstack',
Type: 'SecureString',
Overwrite: true,
}));
const sut = new EnvilderClient(provider);
const mapFile: ParsedMapFile = {
config: {},
mappings: new Map([['MY_SECRET', '/Test/MySecret']]),
};

// Act
const actual = await sut.resolveSecrets(mapFile);

// Assert
expect(actual.get('MY_SECRET')).toBe('real-secret-from-localstack');
});
```

## CI Workflow Pattern

### Required Steps

```yaml
env:
LOCALSTACK_AUTH_TOKEN: ${{ secrets.LOCALSTACK_AUTH_TOKEN }}

steps:
- uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}

# ... build steps ...

- name: Run tests
run: {test-command}
env:
TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE: /var/run/docker.sock
DOCKER_HOST: unix:///var/run/docker.sock
```

### Key Points

- `LOCALSTACK_AUTH_TOKEN` at job level so container wrapper can read it
- AWS OIDC credentials for resolving the token from SSM
- Docker socket env vars for TestContainers compatibility on GitHub runners
- Acceptance tests run in the same job as unit tests (no separate workflow)

## Existing Implementations

| SDK | Container Wrappers | Acceptance Tests |
| ---------- | -------------------------------------- | ------------------------------------------------------------------------ |
| .NET | `tests/sdks/dotnet/Fixtures/` | `tests/sdks/dotnet/Infrastructure/`, `tests/sdks/dotnet/EndToEnd/` |
| Python | `tests/sdks/python/containers/` | `tests/sdks/python/infrastructure/`, `tests/sdks/python/end_to_end/` |
| Node.js | `tests/sdks/nodejs/containers/` | `tests/sdks/nodejs/acceptance/` |
Loading
Loading