3 Commits
0.1.3 ... 0.1.6

Author SHA1 Message Date
Morten Olsen
ef32edcb29 chore: bump AWS client version 2025-08-08 20:13:54 +02:00
Morten Olsen
e2dfb3491d feat: use bundled version 2025-08-08 20:11:04 +02:00
Morten Olsen
3ec6612167 docs: add compare section 2025-08-08 16:34:58 +02:00
7 changed files with 584 additions and 520 deletions

View File

@@ -1 +0,0 @@
PASSWORD=SSM:/test/hfd/rds/DB_USER

View File

@@ -25,7 +25,7 @@ that get resolved at runtime.
## Installation ## Installation
```bash ```bash
npm install -g @0morten-olsen/with-ssm npm install -g @morten-olsen/with-ssm
``` ```
## Quick Start ## Quick Start
@@ -173,3 +173,50 @@ with-ssm -- docker-compose up
# Deploy with production secrets # Deploy with production secrets
with-ssm --profile production -- npm run deploy with-ssm --profile production -- npm run deploy
``` ```
## How does `with-ssm` compare to...?
`with-ssm`'s philosophy is to be a lightweight utility that enhances your
existing workflow, not a heavy framework that replaces it. Heres how it
compares to other tools.
### vs. `.env` files & `dotenv`
`with-ssm` is a security upgrade for the `dotenv` pattern. Instead of storing
secrets in plaintext `.env` files, you store secure `SSM:` references that are
safe to commit to version control. Your app gets the secrets it needs at
runtime, but they never live on your disk, giving you the same simple developer
experience with a major security boost.
### vs. `aws-vault`
These tools are complementary and solve different problems. `aws-vault` securely
manages your local AWS _credentials_, while `with-ssm` uses those credentials to
fetch and inject application _secrets_. They work perfectly together—use
`aws-vault` to handle authentication and `with-ssm` to handle secret resolution:
`aws-vault exec my-profile -- with-ssm -- npm start`.
### vs. `chamber`
`chamber` is a more powerful CLI for the full lifecycle of secret management
(reading, writing, listing), while `with-ssm` is a lightweight utility focused
only on resolving secret references from a file. Choose `with-ssm` for its
"drop-in" simplicity and zero-config approach to enhance an existing `.env`
workflow; choose `chamber` if you need a more comprehensive command-line tool
for advanced SSM tasks.
### vs. Cloud-Native Integrations (ECS, Lambda)
`with-ssm` is built for **local development and CI/CD**, allowing your local
environment to securely mirror production. When your code is running _inside_ an
AWS environment like ECS or Lambda, you should always use the native best
practice: grant the service an IAM role to fetch secrets directly via the AWS
SDK.
### vs. Full Secret Management Platforms (HashiCorp Vault, Doppler)
Platforms like Vault or Doppler are comprehensive, often multi-cloud solutions
with their own UIs and infrastructure. `with-ssm` is a focused, AWS-native
utility, not a platform. It's the ideal choice for teams already using AWS who
want a simple, direct way to leverage SSM Parameter Store without the
operational overhead of a separate service.

View File

@@ -1,2 +1,2 @@
#!/usr/bin/env node #!/usr/bin/env node
import '../dist/start.js'; import '../dist/index.js';

View File

@@ -6,8 +6,8 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"scripts": { "scripts": {
"test:lint": "eslint", "test:lint": "eslint",
"build": "tsc --build", "build": "ncc build src/start.ts -o dist",
"build:dev": "tsc --build --watch", "build:dev": "ncc build src/start.ts -o dist --watch",
"test:unit": "vitest --run --passWithNoTests", "test:unit": "vitest --run --passWithNoTests",
"test": "pnpm run \"/^test:/\"" "test": "pnpm run \"/^test:/\""
}, },
@@ -16,27 +16,26 @@
"dist" "dist"
], ],
"devDependencies": { "devDependencies": {
"@aws-sdk/client-ssm": "^3.863.0",
"@eslint/eslintrc": "3.3.1", "@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.32.0", "@eslint/js": "9.32.0",
"@pnpm/find-workspace-packages": "6.0.9", "@pnpm/find-workspace-packages": "6.0.9",
"@types/node": "24.2.0", "@types/node": "24.2.0",
"@types/yargs": "^17.0.33", "@types/yargs": "^17.0.33",
"@vercel/ncc": "^0.38.3",
"@vitest/coverage-v8": "3.2.4", "@vitest/coverage-v8": "3.2.4",
"dotenv": "^17.2.1",
"eslint": "9.32.0", "eslint": "9.32.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0", "eslint-plugin-import": "2.32.0",
"eslint-plugin-prettier": "5.5.4", "eslint-plugin-prettier": "5.5.4",
"execa": "^9.6.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"typescript": "5.9.2", "typescript": "5.9.2",
"typescript-eslint": "8.39.0", "typescript-eslint": "8.39.0",
"vitest": "3.2.4" "vitest": "3.2.4",
"yargs": "^18.0.0"
}, },
"name": "@morten-olsen/with-ssm", "name": "@morten-olsen/with-ssm",
"version": "1.0.0", "version": "1.0.0"
"dependencies": {
"@aws-sdk/client-ssm": "^3.859.0",
"dotenv": "^17.2.1",
"execa": "^9.6.0",
"yargs": "^18.0.0"
}
} }

957
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@ import { exec } from './utils/exec.js';
import { getEnv } from './utils/env.js'; import { getEnv } from './utils/env.js';
import { replaceParams } from './utils/ssm.js'; import { replaceParams } from './utils/ssm.js';
const argv = await yargs(hideBin(process.argv)) const main = async () => {
const argv = await yargs(hideBin(process.argv))
.usage('Usage: $0 [options] -- <command>') .usage('Usage: $0 [options] -- <command>')
.option('region', { .option('region', {
type: 'string', type: 'string',
@@ -26,20 +27,26 @@ const argv = await yargs(hideBin(process.argv))
.epilogue('For more information, check the documentation.') .epilogue('For more information, check the documentation.')
.parse(); .parse();
const command = argv._[0] as string; const command = argv._[0] as string;
const commandArgs = argv._.slice(1).map(String); const commandArgs = argv._.slice(1).map(String);
if (!command) { if (!command) {
console.error('No command provided'); console.error('No command provided');
process.exit(1); process.exit(1);
} }
const files = argv.file && Array.isArray(argv.file) ? argv.file : [argv.file]; const files = argv.file && Array.isArray(argv.file) ? argv.file : [argv.file];
const hostEnv = await getEnv(files); const hostEnv = await getEnv(files);
const env = await replaceParams(hostEnv); const env = await replaceParams(hostEnv);
exec({ exec({
command, command,
env, env,
args: commandArgs, args: commandArgs,
});
};
main().catch((err) => {
console.error(err);
process.exit(1);
}); });

View File

@@ -10,6 +10,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"skipLibCheck": true, "skipLibCheck": true,
"noEmit": true,
"outDir": "dist", "outDir": "dist",
"jsx": "react-jsx", "jsx": "react-jsx",
"isolatedModules": true, "isolatedModules": true,