envilder 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Parses CLI arguments and runs the environment file generator.
4
+ *
5
+ * Expects `--map` and `--envfile` options to be provided, with an optional `--profile` for AWS CLI profile selection. Invokes the main process to generate a `.env` file from AWS SSM parameters based on the provided mapping.
6
+ *
7
+ * @throws {Error} If either `--map` or `--envfile` arguments are missing.
8
+ */
9
+ export declare function main(): Promise<void>;
10
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":";AA4CA;;;;;;GAMG;AACH,wBAAsB,IAAI,kBAkBzB"}
package/lib/cli/cli.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { existsSync, readFileSync } from 'node:fs';
12
+ import { dirname, join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { Command } from 'commander';
15
+ import { run } from '../index.js';
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ /**
19
+ * Find the package.json file by traversing up directories
20
+ * @param startDir The directory to start searching from
21
+ * @param maxDepth Maximum number of parent directories to check
22
+ * @returns Path to package.json if found, or null if not found
23
+ */
24
+ function findPackageJson(startDir, maxDepth = 5) {
25
+ let currentDir = startDir;
26
+ let depth = 0;
27
+ while (depth < maxDepth) {
28
+ const packagePath = join(currentDir, 'package.json');
29
+ if (existsSync(packagePath)) {
30
+ return packagePath;
31
+ }
32
+ // Go up one directory
33
+ const parentDir = dirname(currentDir);
34
+ if (parentDir === currentDir) {
35
+ // We've reached the root
36
+ break;
37
+ }
38
+ currentDir = parentDir;
39
+ depth++;
40
+ }
41
+ return null;
42
+ }
43
+ // Get package.json path by searching up from current file
44
+ const packageJsonPath = findPackageJson(__dirname) || join(__dirname, '..', '..', 'package.json');
45
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
46
+ /**
47
+ * Parses CLI arguments and runs the environment file generator.
48
+ *
49
+ * Expects `--map` and `--envfile` options to be provided, with an optional `--profile` for AWS CLI profile selection. Invokes the main process to generate a `.env` file from AWS SSM parameters based on the provided mapping.
50
+ *
51
+ * @throws {Error} If either `--map` or `--envfile` arguments are missing.
52
+ */
53
+ export function main() {
54
+ return __awaiter(this, void 0, void 0, function* () {
55
+ const program = new Command();
56
+ program
57
+ .name('envilder')
58
+ .description('A CLI tool to generate .env files from AWS SSM parameters')
59
+ .version(packageJson.version)
60
+ .requiredOption('--map <path>', 'Path to the JSON file with environment variable mapping')
61
+ .requiredOption('--envfile <path>', 'Path to the .env file to be generated')
62
+ .option('--profile <name>', 'AWS CLI profile to use');
63
+ yield program.parseAsync(process.argv);
64
+ const options = program.opts();
65
+ if (!options.map || !options.envfile) {
66
+ throw new Error('Missing required arguments: --map and --envfile');
67
+ }
68
+ yield run(options.map, options.envfile, options.profile);
69
+ });
70
+ }
71
+ // Execute the CLI
72
+ main().catch((error) => {
73
+ console.error('🚨 Uh-oh! Looks like Mario fell into the wrong pipe! 🍄💥');
74
+ console.error(error);
75
+ });
76
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":";;;;;;;;;;AACA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAElC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC;;;;;GAKG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,QAAQ,GAAG,CAAC;IACrD,IAAI,UAAU,GAAG,QAAQ,CAAC;IAC1B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,KAAK,GAAG,QAAQ,EAAE,CAAC;QACxB,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QACrD,IAAI,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAC5B,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,sBAAsB;QACtB,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QACtC,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC7B,yBAAyB;YACzB,MAAM;QACR,CAAC;QAED,UAAU,GAAG,SAAS,CAAC;QACvB,KAAK,EAAE,CAAC;IACV,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,0DAA0D;AAC1D,MAAM,eAAe,GAAG,eAAe,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;AAClG,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;AAEtE;;;;;;GAMG;AACH,MAAM,UAAgB,IAAI;;QACxB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO;aACJ,IAAI,CAAC,UAAU,CAAC;aAChB,WAAW,CAAC,2DAA2D,CAAC;aACxE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC;aAC5B,cAAc,CAAC,cAAc,EAAE,yDAAyD,CAAC;aACzF,cAAc,CAAC,kBAAkB,EAAE,uCAAuC,CAAC;aAC3E,MAAM,CAAC,kBAAkB,EAAE,wBAAwB,CAAC,CAAC;QAExD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAE/B,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QAED,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,CAAC;CAAA;AAED,kBAAkB;AAClB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC3E,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Orchestrates the process of fetching environment variable values from AWS SSM Parameter Store and writing them to a local environment file.
3
+ *
4
+ * Loads a parameter mapping from a JSON file, retrieves existing environment variables, fetches updated values from SSM (optionally using a specified AWS profile), merges them, and writes the result to the specified environment file.
5
+ *
6
+ * @param mapPath - Path to the JSON file mapping environment variable names to SSM parameter names.
7
+ * @param envFilePath - Path to the local environment file to read and update.
8
+ * @param profile - Optional AWS profile name to use for credentials.
9
+ */
10
+ export declare function run(mapPath: string, envFilePath: string, profile?: string): Promise<void>;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,iBAY/E"}
package/lib/index.js ADDED
@@ -0,0 +1,116 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import * as fs from 'node:fs';
11
+ import { GetParameterCommand, SSM } from '@aws-sdk/client-ssm';
12
+ import { fromIni } from '@aws-sdk/credential-providers';
13
+ import * as dotenv from 'dotenv';
14
+ /**
15
+ * Orchestrates the process of fetching environment variable values from AWS SSM Parameter Store and writing them to a local environment file.
16
+ *
17
+ * Loads a parameter mapping from a JSON file, retrieves existing environment variables, fetches updated values from SSM (optionally using a specified AWS profile), merges them, and writes the result to the specified environment file.
18
+ *
19
+ * @param mapPath - Path to the JSON file mapping environment variable names to SSM parameter names.
20
+ * @param envFilePath - Path to the local environment file to read and update.
21
+ * @param profile - Optional AWS profile name to use for credentials.
22
+ */
23
+ export function run(mapPath, envFilePath, profile) {
24
+ return __awaiter(this, void 0, void 0, function* () {
25
+ const defaultAwsConfig = {};
26
+ const ssmClientConfig = profile ? { credentials: fromIni({ profile }) } : defaultAwsConfig;
27
+ const ssm = new SSM(ssmClientConfig);
28
+ const paramMap = loadParamMap(mapPath);
29
+ const existingEnvVariables = loadExistingEnvVariables(envFilePath);
30
+ const updatedEnvVariables = yield fetchAndUpdateEnvVariables(paramMap, existingEnvVariables, ssm);
31
+ writeEnvFile(envFilePath, updatedEnvVariables);
32
+ console.log(`Environment File generated at '${envFilePath}'`);
33
+ });
34
+ }
35
+ function loadParamMap(mapPath) {
36
+ const content = fs.readFileSync(mapPath, 'utf-8');
37
+ try {
38
+ return JSON.parse(content);
39
+ }
40
+ catch (error) {
41
+ console.error(`Error parsing JSON from ${mapPath}`);
42
+ throw new Error(`Invalid JSON in parameter map file: ${mapPath}`);
43
+ }
44
+ }
45
+ function loadExistingEnvVariables(envFilePath) {
46
+ const envVariables = {};
47
+ if (!fs.existsSync(envFilePath))
48
+ return envVariables;
49
+ const existingEnvContent = fs.readFileSync(envFilePath, 'utf-8');
50
+ const parsedEnv = dotenv.parse(existingEnvContent);
51
+ Object.assign(envVariables, parsedEnv);
52
+ return envVariables;
53
+ }
54
+ /**
55
+ * Fetches parameter values from AWS SSM for each environment variable in the map and updates the existing environment variables record.
56
+ *
57
+ * For each mapping, retrieves the corresponding SSM parameter value and updates the environment variable if found. Logs masked values and warnings for missing parameters. Throws an error if any parameters fail to fetch.
58
+ *
59
+ * @param paramMap - Mapping of environment variable names to SSM parameter names.
60
+ * @param existingEnvVariables - Current environment variables to be updated.
61
+ * @param ssm - AWS SSM client instance used for fetching parameters.
62
+ * @returns The updated environment variables record.
63
+ *
64
+ * @throws {Error} If any SSM parameters cannot be fetched.
65
+ */
66
+ function fetchAndUpdateEnvVariables(paramMap, existingEnvVariables, ssm) {
67
+ return __awaiter(this, void 0, void 0, function* () {
68
+ const errors = [];
69
+ for (const [envVar, ssmName] of Object.entries(paramMap)) {
70
+ try {
71
+ const value = yield fetchSSMParameter(ssmName, ssm);
72
+ if (value) {
73
+ existingEnvVariables[envVar] = value;
74
+ console.log(`${envVar}=${value.length > 3 ? '*'.repeat(value.length - 3) + value.slice(-3) : '*'.repeat(value.length)}`);
75
+ }
76
+ else {
77
+ console.error(`Warning: No value found for: '${ssmName}'`);
78
+ }
79
+ }
80
+ catch (error) {
81
+ console.error(`Error fetching parameter: '${ssmName}'`);
82
+ errors.push(`ParameterNotFound: ${ssmName}`);
83
+ }
84
+ }
85
+ if (errors.length > 0) {
86
+ throw new Error(`Some parameters could not be fetched:\n${errors.join('\n')}`);
87
+ }
88
+ return existingEnvVariables;
89
+ });
90
+ }
91
+ /**
92
+ * Retrieves the value of a parameter from AWS SSM Parameter Store with decryption enabled.
93
+ *
94
+ * @param ssmName - The name of the SSM parameter to retrieve.
95
+ * @returns The decrypted parameter value if found, or undefined if the parameter does not exist.
96
+ */
97
+ function fetchSSMParameter(ssmName, ssm) {
98
+ return __awaiter(this, void 0, void 0, function* () {
99
+ const command = new GetParameterCommand({
100
+ Name: ssmName,
101
+ WithDecryption: true,
102
+ });
103
+ const { Parameter } = yield ssm.send(command);
104
+ return Parameter === null || Parameter === void 0 ? void 0 : Parameter.Value;
105
+ });
106
+ }
107
+ function writeEnvFile(envFilePath, envVariables) {
108
+ const envContent = Object.entries(envVariables)
109
+ .map(([key, value]) => {
110
+ const escapedValue = value.replace(/(\n|\r|\n\r)/g, '\\n').replace(/"/g, '\\"');
111
+ return `${key}=${escapedValue}`;
112
+ })
113
+ .join('\n');
114
+ fs.writeFileSync(envFilePath, envContent);
115
+ }
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,26 +1,29 @@
1
1
  {
2
2
  "name": "envilder",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "A CLI tool to generate .env files from AWS SSM parameters",
5
5
  "exports": {
6
6
  ".": {
7
+ "import": "./lib/index.js",
7
8
  "types": "./lib/index.d.ts"
8
9
  }
9
10
  },
11
+ "main": "./lib/index.js",
10
12
  "bin": {
11
- "envilder": "lib/cli/cliRunner.js"
13
+ "envilder": "lib/cli/cli.js"
12
14
  },
13
15
  "scripts": {
14
16
  "clean": "npx jest --clearCache && yarn cache clean --force && npx rimraf lib && npx rimraf node_modules && npx rimraf coverage && npx rimraf yarn.lock",
15
- "test-run": "yarn build && node lib/cli/cliRunner.js --map=tests/sample/param-map.json --envfile=tests/sample/autogenerated.env",
16
- "build": "tsc -p tsconfig.build.json --sourceMap --declaration",
17
+ "test-run": "yarn build && node lib/cli/cli.js --map=tests/sample/param-map.json --envfile=tests/sample/autogenerated.env",
18
+ "build": "tsc -p tsconfig.build.json --sourceMap --declaration && node --no-warnings scripts/chmod-cli.js",
19
+ "validate-cli": "node --no-warnings --loader ts-node/esm scripts/validate-cli.ts",
17
20
  "format": "npx biome format",
18
21
  "format:write": "npx biome format --write",
19
22
  "lint": "npx secretlint \"**/*\" && biome lint --write && biome format --write && biome check --write && tsc --noEmit",
20
23
  "lint:fix": "npx biome lint --fix",
21
24
  "test": "vitest run --reporter verbose --coverage",
22
25
  "cli-run": "yarn build && node --trace-warnings lib",
23
- "npm-publish": "yarn lint && yarn build && yarn publish",
26
+ "npm-publish": "yarn lint && yarn build && yarn test && npm pack --dry-run && yarn publish",
24
27
  "npm-release-patch": "yarn version --new-version patch",
25
28
  "npm-release-minor": "yarn version --new-version minor",
26
29
  "npm-release-prerelease": "yarn version --new-version prerelease"
@@ -38,30 +41,31 @@
38
41
  "publishConfig": {
39
42
  "access": "public"
40
43
  },
44
+ "files": [
45
+ "lib/**/*",
46
+ "README.md",
47
+ "LICENSE",
48
+ "ROADMAP.md"
49
+ ],
41
50
  "type": "module",
42
51
  "dependencies": {
43
52
  "@aws-sdk/client-ssm": "^3.806.0",
44
53
  "@aws-sdk/credential-providers": "^3.806.0",
45
- "@secretlint/core": "^9.2.1",
46
- "@secretlint/secretlint-rule-preset-recommend": "^9.0.0",
47
54
  "@types/node": "^22.5.5",
48
- "commander": "^12.1.0",
55
+ "commander": "^13.1.0",
49
56
  "dotenv": "^16.4.5",
50
57
  "picocolors": "^1.1.0"
51
58
  },
52
59
  "devDependencies": {
53
60
  "@biomejs/biome": "^1.9.1",
61
+ "@secretlint/secretlint-rule-preset-recommend": "^9.3.2",
54
62
  "@vitest/coverage-v8": "^3.1.1",
55
63
  "rimraf": "^6.0.1",
56
- "secretlint": "^9.0.0",
64
+ "secretlint": "^9.3.2",
57
65
  "ts-node": "^10.9.2",
58
66
  "typescript": "^5.6.2",
59
67
  "vitest": "^3.1.1"
60
68
  },
61
- "resolutions": {
62
- "string-width": "4.2.3",
63
- "strip-ansi": "6.0.1"
64
- },
65
69
  "engines": {
66
70
  "node": ">=20.0.0",
67
71
  "yarn": ">=1.22"
package/.gitattributes DELETED
@@ -1,67 +0,0 @@
1
- ###############################################################################
2
- # Set default behavior to automatically normalize line endings.
3
- ###############################################################################
4
- * text=auto
5
-
6
- # Explicitly declare text files you want to always be normalized and converted
7
- # to native line endings on checkout.
8
- *.sh text eol=lf
9
-
10
- ###############################################################################
11
- # Set default behavior for command prompt diff.
12
- #
13
- # This is need for earlier builds of msysgit that does not have it on by
14
- # default for csharp files.
15
- # Note: This is only used by command line
16
- ###############################################################################
17
- #*.cs diff=csharp
18
-
19
- ###############################################################################
20
- # Set the merge driver for project and solution files
21
- #
22
- # Merging from the command prompt will add diff markers to the files if there
23
- # are conflicts (Merging from VS is not affected by the settings below, in VS
24
- # the diff markers are never inserted). Diff markers may cause the following
25
- # file extensions to fail to load in VS. An alternative would be to treat
26
- # these files as binary and thus will always conflict and require user
27
- # intervention with every merge. To do so, just uncomment the entries below
28
- ###############################################################################
29
- #*.sln merge=binary
30
- #*.csproj merge=binary
31
- #*.vbproj merge=binary
32
- #*.vcxproj merge=binary
33
- #*.vcproj merge=binary
34
- #*.dbproj merge=binary
35
- #*.fsproj merge=binary
36
- #*.lsproj merge=binary
37
- #*.wixproj merge=binary
38
- #*.modelproj merge=binary
39
- #*.sqlproj merge=binary
40
- #*.wwaproj merge=binary
41
-
42
- ###############################################################################
43
- # behavior for image files
44
- #
45
- # image files are treated as binary by default.
46
- ###############################################################################
47
- #*.jpg binary
48
- #*.png binary
49
- #*.gif binary
50
-
51
- ###############################################################################
52
- # diff behavior for common document formats
53
- #
54
- # Convert binary document formats to text before diffing them. This feature
55
- # is only available from the command line. Turn it on by uncommenting the
56
- # entries below.
57
- ###############################################################################
58
- #*.doc diff=astextplain
59
- #*.DOC diff=astextplain
60
- #*.docx diff=astextplain
61
- #*.DOCX diff=astextplain
62
- #*.dot diff=astextplain
63
- #*.DOT diff=astextplain
64
- #*.pdf diff=astextplain
65
- #*.PDF diff=astextplain
66
- #*.rtf diff=astextplain
67
- #*.RTF diff=astextplain
package/.gitconfig DELETED
@@ -1,2 +0,0 @@
1
- [core]
2
- longpaths = true
@@ -1,40 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: npm
9
- directory: "/"
10
- pull-request-branch-name:
11
- separator: "/"
12
- schedule:
13
- interval: monthly
14
- day: monday
15
- time: "10:00"
16
- timezone: Europe/Madrid
17
- labels:
18
- - "npm"
19
- - "dependencies"
20
- reviewers:
21
- - macalbert
22
- assignees:
23
- - macalbert
24
-
25
- - package-ecosystem: github-actions
26
- directory: "/"
27
- pull-request-branch-name:
28
- separator: "/"
29
- schedule:
30
- interval: monthly
31
- day: monday
32
- time: "10:00"
33
- timezone: Europe/Madrid
34
- labels:
35
- - "github-actions"
36
- - "dependencies"
37
- reviewers:
38
- - macalbert
39
- assignees:
40
- - macalbert
@@ -1,20 +0,0 @@
1
- # Description
2
-
3
- _Describe the problem or feature in addition to a link to the issues._
4
-
5
- ## Approach
6
-
7
- _How does this change address the problem?_
8
-
9
- ## Open Questions and Pre-Merge TODOs
10
-
11
- - [ ] Use github checklists. When solved, check the box and explain the answer.
12
-
13
- ## Learning
14
-
15
- _Describe the research stage_
16
- _Links to blog posts, patterns, libraries or addons used to solve this problem_
17
-
18
- ## Blog Posts
19
-
20
- - [How to Pull Request](https://github.com/flexyford/pull-request) Github Repo with Learning focused Pull Request Template.
@@ -1,49 +0,0 @@
1
- name: CodeQL
2
-
3
- on:
4
- workflow_dispatch:
5
-
6
- pull_request:
7
- branches: [main]
8
- paths:
9
- - ".github/workflows/codeql-analysis.yml"
10
-
11
- push:
12
- branches: [main]
13
- paths:
14
- - ".github/workflows/codeql-analysis.yml"
15
- - "src/**"
16
- - "test/**"
17
-
18
- concurrency:
19
- group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
20
- cancel-in-progress: true
21
-
22
- jobs:
23
- analyze:
24
- strategy:
25
- fail-fast: false
26
- matrix:
27
- language: ["javascript"]
28
-
29
- permissions:
30
- security-events: write
31
-
32
- runs-on: ubuntu-22.04
33
-
34
- steps:
35
- - name: Checkout Repository
36
- uses: actions/checkout@v4
37
- with:
38
- lfs: true
39
-
40
- - name: Initialize CodeQL
41
- uses: github/codeql-action/init@v3
42
- with:
43
- languages: ${{ matrix.language }}
44
-
45
- - name: Autobuild
46
- uses: github/codeql-action/autobuild@v3
47
-
48
- - name: Perform CodeQL Analysis
49
- uses: github/codeql-action/analyze@v3
@@ -1,73 +0,0 @@
1
- name: 🔦 code-coverage
2
-
3
- on:
4
- workflow_dispatch:
5
-
6
- pull_request:
7
- paths:
8
- - ".github/workflows/coverage-report.yml"
9
-
10
- push:
11
- branches:
12
- - "main"
13
- paths:
14
- - ".github/workflows/coverage-report.yml"
15
- - "src/**"
16
- - "test/**"
17
-
18
- concurrency:
19
- group: "pages"
20
- cancel-in-progress: false
21
-
22
- permissions:
23
- contents: read
24
- pages: write
25
- id-token: write
26
-
27
- jobs:
28
- build-coverage:
29
- environment:
30
- name: github-pages
31
- url: ${{ steps.deployment.outputs.page_url }}
32
- runs-on: ubuntu-latest
33
- if: ${{ !github.event.pull_request.draft }}
34
- steps:
35
- - name: Check if PR is created by Dependabot
36
- id: dependabot-check
37
- run: |
38
- if [[ "${{ github.actor }}" == *dependabot* ]]; then
39
- echo "is_dependabot=true" >> "$GITHUB_OUTPUT"
40
- else
41
- echo "is_dependabot=false" >> "$GITHUB_OUTPUT"
42
- fi
43
-
44
- - name: Check if the workflow should run
45
- if: ${{ steps.dependabot-check.outputs.is_dependabot == 'false' }}
46
- run: echo "The workflow is allowed to run for this PR"
47
-
48
- - name: 🧲 Checkout
49
- uses: actions/checkout@v4
50
-
51
- - name: 🛠️ Setup Node.js with Cache
52
- uses: actions/setup-node@v4
53
- with:
54
- node-version: "20.x"
55
- cache: "yarn"
56
-
57
- - name: 📦 Install dependencies
58
- run: yarn install
59
-
60
- - name: 🔥 Run tests and collect coverage
61
- run: yarn test
62
-
63
- - name: Setup Pages
64
- uses: actions/configure-pages@v5
65
-
66
- - name: Upload artifact
67
- uses: actions/upload-pages-artifact@v3
68
- with:
69
- path: "./coverage"
70
-
71
- - name: Deploy to GitHub Pages
72
- id: deployment
73
- uses: actions/deploy-pages@v4
@@ -1,80 +0,0 @@
1
- name: 🍄 Power-Up Publisher
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- paths:
8
- - 'src/**'
9
- - 'package.json'
10
- - '.github/workflows/publish.yml'
11
-
12
- # Add explicit permissions for the GITHUB_TOKEN
13
- permissions:
14
- contents: write
15
- packages: write
16
-
17
- jobs:
18
- publish:
19
- runs-on: ubuntu-latest
20
- steps:
21
- - name: 🧱 Checkout repository
22
- uses: actions/checkout@v3
23
- with:
24
- fetch-depth: 0
25
-
26
- - name: 🔧 Setup Node.js
27
- uses: actions/setup-node@v3
28
- with:
29
- node-version: '20'
30
- registry-url: 'https://registry.npmjs.org'
31
- cache: 'yarn'
32
-
33
- - name: 🌟 Install dependencies
34
- run: yarn install --frozen-lockfile
35
-
36
- - name: 🔥 Lint
37
- run: yarn lint
38
-
39
- - name: 🍄 Run tests
40
- run: yarn test
41
-
42
- - name: 👑 Detect version bump
43
- id: version-check
44
- run: |
45
- CURRENT_VERSION=$(node -p "require('./package.json').version")
46
- LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
47
- LAST_TAG_VERSION=${LAST_TAG#v}
48
-
49
- echo "Current version: $CURRENT_VERSION, Last tag version: $LAST_TAG_VERSION"
50
-
51
- # Check if the current version is greater than the last tagged version
52
- if [ "$CURRENT_VERSION" != "$LAST_TAG_VERSION" ]; then
53
- echo "Version has been bumped from $LAST_TAG_VERSION to $CURRENT_VERSION"
54
- echo "version_changed=true" >> $GITHUB_OUTPUT
55
- echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
56
- else
57
- echo "Version not changed. Will not publish."
58
- echo "version_changed=false" >> $GITHUB_OUTPUT
59
- fi
60
-
61
- - name: 🔨 Build package
62
- if: steps.version-check.outputs.version_changed == 'true'
63
- run: yarn build
64
-
65
- - name: 🚩 Publish to npm
66
- if: steps.version-check.outputs.version_changed == 'true'
67
- run: yarn publish --non-interactive
68
- env:
69
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
70
-
71
- - name: 🏰 Create GitHub Release
72
- if: steps.version-check.outputs.version_changed == 'true'
73
- uses: ncipollo/release-action@v1
74
- with:
75
- tag: "v${{ steps.version-check.outputs.current_version }}"
76
- name: "🍄 Level Up Release v${{ steps.version-check.outputs.current_version }}"
77
- generateReleaseNotes: true
78
- token: ${{ secrets.GITHUB_TOKEN }}
79
- makeLatest: legacy
80
- replacesArtifacts: true
@@ -1,51 +0,0 @@
1
- name: 🌱 unit-tests
2
-
3
- on:
4
- workflow_dispatch: {}
5
-
6
- pull_request:
7
- branches:
8
- - "*"
9
- types:
10
- - opened
11
- - reopened
12
- - synchronize
13
- - ready_for_review
14
- paths:
15
- - ".github/workflows/unit-tests.yml"
16
- - "src/**"
17
-
18
- concurrency:
19
- group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
20
- cancel-in-progress: true
21
-
22
- jobs:
23
- envilder-test:
24
- runs-on: ubuntu-24.04
25
- if: ${{ !github.event.pull_request.draft }}
26
- timeout-minutes: 30
27
-
28
- steps:
29
- - name: 🚀 ♂️ Checkout
30
- uses: actions/checkout@v4
31
-
32
- - name: 🛠️ Setup Node.js with Cache
33
- uses: actions/setup-node@v4
34
- with:
35
- node-version: '20.x'
36
- cache: 'yarn'
37
-
38
- - name: 📦 Install packages
39
- run: yarn install --frozen-lockfile
40
-
41
- - name: 🔍 Run formatting checker
42
- run: yarn format
43
-
44
- - name: 🔍 Run code quality checker
45
- run: yarn lint
46
-
47
- - name: 🚧 Build
48
- run: yarn build
49
-
50
- - name: 🚴‍♀️ Run unit tests
51
- run: yarn test
@@ -1,7 +0,0 @@
1
- {
2
- "rules": [
3
- {
4
- "id": "@secretlint/secretlint-rule-preset-recommend"
5
- }
6
- ]
7
- }
package/biome.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
3
- "files": {
4
- "include": ["./src/**", "./tests/**"],
5
- "ignore": ["**/node_modules/**", "**/lib/**", "**/dist/**", "**/coverage/**", "**/.lock", "**/.md"]
6
- },
7
- "organizeImports": {
8
- "enabled": true
9
- },
10
- "linter": {
11
- "enabled": true,
12
- "rules": {
13
- "recommended": true
14
- }
15
- },
16
- "formatter": {
17
- "enabled": true,
18
- "formatWithErrors": false,
19
- "indentStyle": "space",
20
- "indentWidth": 2,
21
- "lineWidth": 120
22
- },
23
- "javascript": {
24
- "formatter": {
25
- "quoteStyle": "single",
26
- "trailingCommas": "all",
27
- "semicolons": "always"
28
- }
29
- }
30
- }
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import { run } from '../index.js';
4
-
5
- /**
6
- * Parses CLI arguments and runs the environment file generator.
7
- *
8
- * Expects `--map` and `--envfile` options to be provided, with an optional `--profile` for AWS CLI profile selection. Invokes the main process to generate a `.env` file from AWS SSM parameters based on the provided mapping.
9
- *
10
- * @throws {Error} If either `--map` or `--envfile` arguments are missing.
11
- */
12
- export async function cliRunner() {
13
- const program = new Command();
14
-
15
- program
16
- .name('envilder')
17
- .description('A CLI tool to generate .env files from AWS SSM parameters')
18
- .version('0.1.0')
19
- .requiredOption('--map <path>', 'Path to the JSON file with environment variable mapping')
20
- .requiredOption('--envfile <path>', 'Path to the .env file to be generated')
21
- .option('--profile <name>', 'AWS CLI profile to use');
22
-
23
- await program.parseAsync(process.argv);
24
- const options = program.opts();
25
-
26
- if (!options.map || !options.envfile) {
27
- throw new Error('Missing required arguments: --map and --envfile');
28
- }
29
-
30
- await run(options.map, options.envfile, options.profile);
31
- }
32
-
33
- cliRunner().catch((error) => {
34
- console.error('🚨 Uh-oh! Looks like Mario fell into the wrong pipe! 🍄💥');
35
- });
package/src/index.ts DELETED
@@ -1,119 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import { GetParameterCommand, SSM } from '@aws-sdk/client-ssm';
3
- import { fromIni } from '@aws-sdk/credential-providers';
4
- import * as dotenv from 'dotenv';
5
-
6
- /**
7
- * Orchestrates the process of fetching environment variable values from AWS SSM Parameter Store and writing them to a local environment file.
8
- *
9
- * Loads a parameter mapping from a JSON file, retrieves existing environment variables, fetches updated values from SSM (optionally using a specified AWS profile), merges them, and writes the result to the specified environment file.
10
- *
11
- * @param mapPath - Path to the JSON file mapping environment variable names to SSM parameter names.
12
- * @param envFilePath - Path to the local environment file to read and update.
13
- * @param profile - Optional AWS profile name to use for credentials.
14
- */
15
- export async function run(mapPath: string, envFilePath: string, profile?: string) {
16
- const defaultAwsConfig = {};
17
- const ssmClientConfig = profile ? { credentials: fromIni({ profile }) } : defaultAwsConfig;
18
- const ssm = new SSM(ssmClientConfig);
19
-
20
- const paramMap = loadParamMap(mapPath);
21
- const existingEnvVariables = loadExistingEnvVariables(envFilePath);
22
-
23
- const updatedEnvVariables = await fetchAndUpdateEnvVariables(paramMap, existingEnvVariables, ssm);
24
-
25
- writeEnvFile(envFilePath, updatedEnvVariables);
26
- console.log(`Environment File generated at '${envFilePath}'`);
27
- }
28
-
29
- function loadParamMap(mapPath: string): Record<string, string> {
30
- const content = fs.readFileSync(mapPath, 'utf-8');
31
- try {
32
- return JSON.parse(content);
33
- } catch (error) {
34
- console.error(`Error parsing JSON from ${mapPath}`);
35
- throw new Error(`Invalid JSON in parameter map file: ${mapPath}`);
36
- }
37
- }
38
-
39
- function loadExistingEnvVariables(envFilePath: string): Record<string, string> {
40
- const envVariables: Record<string, string> = {};
41
-
42
- if (!fs.existsSync(envFilePath)) return envVariables;
43
-
44
- const existingEnvContent = fs.readFileSync(envFilePath, 'utf-8');
45
- const parsedEnv = dotenv.parse(existingEnvContent);
46
- Object.assign(envVariables, parsedEnv);
47
-
48
- return envVariables;
49
- }
50
-
51
- /**
52
- * Fetches parameter values from AWS SSM for each environment variable in the map and updates the existing environment variables record.
53
- *
54
- * For each mapping, retrieves the corresponding SSM parameter value and updates the environment variable if found. Logs masked values and warnings for missing parameters. Throws an error if any parameters fail to fetch.
55
- *
56
- * @param paramMap - Mapping of environment variable names to SSM parameter names.
57
- * @param existingEnvVariables - Current environment variables to be updated.
58
- * @param ssm - AWS SSM client instance used for fetching parameters.
59
- * @returns The updated environment variables record.
60
- *
61
- * @throws {Error} If any SSM parameters cannot be fetched.
62
- */
63
- async function fetchAndUpdateEnvVariables(
64
- paramMap: Record<string, string>,
65
- existingEnvVariables: Record<string, string>,
66
- ssm: SSM,
67
- ): Promise<Record<string, string>> {
68
- const errors: string[] = [];
69
-
70
- for (const [envVar, ssmName] of Object.entries(paramMap)) {
71
- try {
72
- const value = await fetchSSMParameter(ssmName, ssm);
73
- if (value) {
74
- existingEnvVariables[envVar] = value;
75
- console.log(
76
- `${envVar}=${value.length > 3 ? '*'.repeat(value.length - 3) + value.slice(-3) : '*'.repeat(value.length)}`,
77
- );
78
- } else {
79
- console.error(`Warning: No value found for: '${ssmName}'`);
80
- }
81
- } catch (error) {
82
- console.error(`Error fetching parameter: '${ssmName}'`);
83
- errors.push(`ParameterNotFound: ${ssmName}`);
84
- }
85
- }
86
-
87
- if (errors.length > 0) {
88
- throw new Error(`Some parameters could not be fetched:\n${errors.join('\n')}`);
89
- }
90
-
91
- return existingEnvVariables;
92
- }
93
-
94
- /**
95
- * Retrieves the value of a parameter from AWS SSM Parameter Store with decryption enabled.
96
- *
97
- * @param ssmName - The name of the SSM parameter to retrieve.
98
- * @returns The decrypted parameter value if found, or undefined if the parameter does not exist.
99
- */
100
- async function fetchSSMParameter(ssmName: string, ssm: SSM): Promise<string | undefined> {
101
- const command = new GetParameterCommand({
102
- Name: ssmName,
103
- WithDecryption: true,
104
- });
105
-
106
- const { Parameter } = await ssm.send(command);
107
- return Parameter?.Value;
108
- }
109
-
110
- function writeEnvFile(envFilePath: string, envVariables: Record<string, string>): void {
111
- const envContent = Object.entries(envVariables)
112
- .map(([key, value]) => {
113
- const escapedValue = value.replace(/(\n|\r|\n\r)/g, '\\n').replace(/"/g, '\\"');
114
- return `${key}=${escapedValue}`;
115
- })
116
- .join('\n');
117
-
118
- fs.writeFileSync(envFilePath, envContent);
119
- }
@@ -1,59 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { cliRunner } from '../../src/cli/cliRunner';
3
- import { run } from '../../src/index';
4
-
5
- vi.mock('../../src/index', () => ({
6
- run: vi.fn(),
7
- }));
8
-
9
- describe('cliRunner', () => {
10
- const originalArgv = process.argv;
11
-
12
- beforeEach(() => {
13
- process.argv = [...originalArgv.slice(0, 2)];
14
- });
15
- afterEach(() => {
16
- vi.clearAllMocks();
17
- process.argv = originalArgv;
18
- });
19
-
20
- it('Should_CallRunWithCorrectArguments_When_ValidArgumentsAreProvided', async () => {
21
- // Arrange
22
- const mockMapPath = 'path/to/mockMap.json';
23
- const mockEnvFilePath = 'path/to/.env';
24
- process.argv.push('--map', mockMapPath, '--envfile', mockEnvFilePath);
25
-
26
- // Act
27
- await cliRunner();
28
-
29
- // Assert
30
- expect(run).toHaveBeenCalledWith(mockMapPath, mockEnvFilePath, undefined);
31
- });
32
-
33
- it('Should_ThrowError_When_RequiredArgumentsAreMissing', async () => {
34
- // Arrange
35
- vi.spyOn(process, 'exit').mockImplementation(() => {
36
- throw new Error('process.exit called');
37
- });
38
-
39
- // Act
40
- const action = cliRunner();
41
-
42
- // Assert
43
- await expect(action).rejects.toThrow('process.exit called');
44
- });
45
-
46
- it('Should_CallRunWithCorrectArgumentsIncludingProfile_When_ProfileIsProvided', async () => {
47
- // Arrange
48
- const mockMapPath = 'path/to/mockMap.json';
49
- const mockEnvFilePath = 'path/to/.env';
50
- const mockProfile = 'test-profile';
51
- process.argv.push('--map', mockMapPath, '--envfile', mockEnvFilePath, '--profile', mockProfile);
52
-
53
- // Act
54
- await cliRunner();
55
-
56
- // Assert
57
- expect(run).toHaveBeenCalledWith(mockMapPath, mockEnvFilePath, mockProfile);
58
- });
59
- });
@@ -1,150 +0,0 @@
1
- import * as fs from 'node:fs';
2
- import { SSM } from '@aws-sdk/client-ssm';
3
- import { afterEach, describe, expect, it, vi } from 'vitest';
4
- import { run } from '../src/index';
5
-
6
- vi.mock('@aws-sdk/client-ssm', () => {
7
- return {
8
- SSM: vi.fn().mockImplementation(() => ({
9
- send: vi.fn((command) => {
10
- if (command.input.Name === '/path/to/ssm/email') {
11
- return Promise.resolve({
12
- Parameter: { Value: 'mockedEmail@example.com' },
13
- });
14
- }
15
-
16
- if (command.input.Name === '/path/to/ssm/password') {
17
- return Promise.resolve({
18
- Parameter: { Value: 'mockedPassword' },
19
- });
20
- }
21
-
22
- if (command.input.Name === '/path/to/ssm/password_no_value') {
23
- return Promise.resolve({ Parameter: { Value: '' } });
24
- }
25
-
26
- return Promise.reject(new Error(`ParameterNotFound: ${command.input.Name}`));
27
- }),
28
- })),
29
- GetParameterCommand: vi.fn().mockImplementation((input) => ({
30
- input,
31
- })),
32
- };
33
- });
34
-
35
- describe('Envilder CLI', () => {
36
- const mockMapPath = './tests/param-map.json';
37
- const mockEnvFilePath = './tests/.env.test';
38
-
39
- afterEach(() => {
40
- vi.clearAllMocks();
41
-
42
- if (fs.existsSync(mockEnvFilePath)) {
43
- fs.unlinkSync(mockEnvFilePath);
44
- }
45
-
46
- if (fs.existsSync(mockMapPath)) {
47
- fs.unlinkSync(mockMapPath);
48
- }
49
- });
50
-
51
- it('Should_GenerateEnvFileFromSSMParameters_When_ValidSSMParametersAreProvided', async () => {
52
- // Arrange
53
- const paramMapContent = {
54
- NEXT_PUBLIC_CREDENTIAL_EMAIL: '/path/to/ssm/email',
55
- NEXT_PUBLIC_CREDENTIAL_PASSWORD: '/path/to/ssm/password',
56
- };
57
- fs.writeFileSync(mockMapPath, JSON.stringify(paramMapContent));
58
-
59
- // Act
60
- await run(mockMapPath, mockEnvFilePath);
61
-
62
- // Assert
63
- const envFileContent = fs.readFileSync(mockEnvFilePath, 'utf-8');
64
- expect(envFileContent).toContain('NEXT_PUBLIC_CREDENTIAL_EMAIL=mockedEmail@example.com');
65
- expect(envFileContent).toContain('NEXT_PUBLIC_CREDENTIAL_PASSWORD=mockedPassword');
66
- });
67
-
68
- it('Should_ThrowError_When_SSMParameterIsNotFound', async () => {
69
- // Arrange
70
- const paramMapContent = {
71
- NEXT_PUBLIC_CREDENTIAL_EMAIL: 'non-existent parameter',
72
- };
73
- fs.writeFileSync(mockMapPath, JSON.stringify(paramMapContent));
74
-
75
- // Act
76
- const action = run(mockMapPath, mockEnvFilePath);
77
-
78
- // Assert
79
- await expect(action).rejects.toThrow('ParameterNotFound: non-existent parameter');
80
- });
81
-
82
- it('Should_AppendNewSSMParameters_When_EnvFileContainsExistingVariables', async () => {
83
- // Arrange
84
- const existingEnvContent = 'EXISTING_VAR=existingValue';
85
- fs.writeFileSync(mockEnvFilePath, existingEnvContent);
86
- const paramMapContent = {
87
- NEXT_PUBLIC_CREDENTIAL_EMAIL: '/path/to/ssm/email',
88
- NEXT_PUBLIC_CREDENTIAL_PASSWORD: '/path/to/ssm/password',
89
- };
90
- fs.writeFileSync(mockMapPath, JSON.stringify(paramMapContent));
91
-
92
- // Act
93
- await run(mockMapPath, mockEnvFilePath);
94
-
95
- // Assert
96
- const updatedEnvFileContent = fs.readFileSync(mockEnvFilePath, 'utf-8');
97
- expect(updatedEnvFileContent).toContain('EXISTING_VAR=existingValue');
98
- expect(updatedEnvFileContent).toContain('NEXT_PUBLIC_CREDENTIAL_EMAIL=mockedEmail@example.com');
99
- expect(updatedEnvFileContent).toContain('NEXT_PUBLIC_CREDENTIAL_PASSWORD=mockedPassword');
100
- });
101
-
102
- it('Should_OverwriteSSMParameters_When_EnvFileContainsSameVariables', async () => {
103
- // Arrange
104
- const existingEnvContent = 'NEXT_PUBLIC_CREDENTIAL_EMAIL=oldEmail@example.com';
105
- fs.writeFileSync(mockEnvFilePath, existingEnvContent);
106
- const paramMapContent = {
107
- NEXT_PUBLIC_CREDENTIAL_EMAIL: '/path/to/ssm/email',
108
- NEXT_PUBLIC_CREDENTIAL_PASSWORD: '/path/to/ssm/password',
109
- };
110
- fs.writeFileSync(mockMapPath, JSON.stringify(paramMapContent));
111
-
112
- // Act
113
- await run(mockMapPath, mockEnvFilePath);
114
-
115
- // Assert
116
- const updatedEnvFileContent = fs.readFileSync(mockEnvFilePath, 'utf-8');
117
- expect(updatedEnvFileContent).toContain('NEXT_PUBLIC_CREDENTIAL_EMAIL=mockedEmail@example.com');
118
- expect(updatedEnvFileContent).toContain('NEXT_PUBLIC_CREDENTIAL_PASSWORD=mockedPassword');
119
- });
120
-
121
- it('Should_LogWarning_When_SSMParameterHasNoValue', async () => {
122
- // Arrange
123
- const paramMapContent = {
124
- NEXT_PUBLIC_CREDENTIAL_PASSWORD: '/path/to/ssm/password_no_value',
125
- };
126
- fs.writeFileSync(mockMapPath, JSON.stringify(paramMapContent));
127
- const consoleSpy = vi.spyOn(console, 'error');
128
-
129
- // Act
130
- await run(mockMapPath, mockEnvFilePath);
131
-
132
- // Assert
133
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Warning: No value found for'));
134
- });
135
-
136
- it('Should_ConfigureSSMClientWithProfile_When_ProfileIsProvided', async () => {
137
- // Arrange
138
- const mockProfile = 'test-profile';
139
- const paramMapContent = {
140
- NEXT_PUBLIC_CREDENTIAL_EMAIL: '/path/to/ssm/email',
141
- };
142
- fs.writeFileSync(mockMapPath, JSON.stringify(paramMapContent));
143
-
144
- // Act
145
- await run(mockMapPath, mockEnvFilePath, mockProfile);
146
-
147
- // Assert
148
- expect(vi.mocked(SSM).mock.calls[0][0]).toEqual(expect.objectContaining({ credentials: expect.anything() }));
149
- });
150
- });
@@ -1 +0,0 @@
1
- TOKEN_SECRET=this_is_for_test
@@ -1,3 +0,0 @@
1
- {
2
- "TOKEN_SECRET": "/Test/Token"
3
- }
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src"
5
- },
6
- "include": ["./src/**/*"]
7
- }
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compileOnSave": false,
3
- "compilerOptions": {
4
- "module": "ESNext",
5
- "moduleResolution": "Node",
6
- "target": "es2016",
7
- "outDir": "./lib",
8
- "rootDir": ".",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "noImplicitAny": true,
12
- "skipLibCheck": true,
13
- "lib": ["es2022"],
14
- "declaration": true,
15
- "declarationMap": true,
16
- "sourceMap": true,
17
- "forceConsistentCasingInFileNames": true,
18
- "resolveJsonModule": true,
19
- "types": ["node", "picocolors"]
20
- },
21
- "include": ["src/**/*"]
22
- }
package/vite.config.ts DELETED
@@ -1,17 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- include: ['tests/**/*.test.ts'],
8
- coverage: {
9
- provider: 'v8',
10
- reporter: ['text', 'html', 'json'],
11
- reportsDirectory: './coverage',
12
- all: true,
13
- include: ['src/**/*.ts'],
14
- exclude: ['node_modules', 'test', 'coverage', 'dist'],
15
- },
16
- },
17
- });
package/vitest.config.js DELETED
@@ -1,12 +0,0 @@
1
- // vitest.config.js
2
- import { defineConfig } from 'vitest/config';
3
-
4
- export default defineConfig({
5
- test: {
6
- coverage: {
7
- provider: 'v8',
8
- reporter: ['text', 'html'],
9
- reportsDirectory: './coverage',
10
- },
11
- },
12
- });