slskit-cli 1.0.0

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.
Files changed (135) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/COMMAND.md +636 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE.md +22 -0
  5. package/README.md +168 -0
  6. package/bin/slskit.js +3 -0
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +25 -0
  9. package/dist/commands/base-command.d.ts +5 -0
  10. package/dist/commands/base-command.js +29 -0
  11. package/dist/commands/configure/action.d.ts +2 -0
  12. package/dist/commands/configure/action.js +120 -0
  13. package/dist/commands/configure/aws-cli.d.ts +16 -0
  14. package/dist/commands/configure/aws-cli.js +135 -0
  15. package/dist/commands/configure/index.d.ts +2 -0
  16. package/dist/commands/configure/index.js +20 -0
  17. package/dist/commands/configure/prompts.d.ts +8 -0
  18. package/dist/commands/configure/prompts.js +117 -0
  19. package/dist/commands/configure/types.d.ts +24 -0
  20. package/dist/commands/configure/types.js +6 -0
  21. package/dist/commands/deploy/action.d.ts +2 -0
  22. package/dist/commands/deploy/action.js +115 -0
  23. package/dist/commands/deploy/index.d.ts +2 -0
  24. package/dist/commands/deploy/index.js +23 -0
  25. package/dist/commands/deploy/sam-deploy.d.ts +10 -0
  26. package/dist/commands/deploy/sam-deploy.js +127 -0
  27. package/dist/commands/deploy/scope.d.ts +9 -0
  28. package/dist/commands/deploy/scope.js +34 -0
  29. package/dist/commands/deploy/types.d.ts +20 -0
  30. package/dist/commands/deploy/types.js +2 -0
  31. package/dist/commands/env/action.d.ts +11 -0
  32. package/dist/commands/env/action.js +274 -0
  33. package/dist/commands/env/dotenv.d.ts +6 -0
  34. package/dist/commands/env/dotenv.js +99 -0
  35. package/dist/commands/env/index.d.ts +2 -0
  36. package/dist/commands/env/index.js +76 -0
  37. package/dist/commands/env/keys.d.ts +8 -0
  38. package/dist/commands/env/keys.js +41 -0
  39. package/dist/commands/env/parameters.d.ts +7 -0
  40. package/dist/commands/env/parameters.js +63 -0
  41. package/dist/commands/env/templates.d.ts +3 -0
  42. package/dist/commands/env/templates.js +65 -0
  43. package/dist/commands/env/types.d.ts +21 -0
  44. package/dist/commands/env/types.js +2 -0
  45. package/dist/commands/function/action.d.ts +2 -0
  46. package/dist/commands/function/action.js +71 -0
  47. package/dist/commands/function/generator.d.ts +8 -0
  48. package/dist/commands/function/generator.js +58 -0
  49. package/dist/commands/function/index.d.ts +2 -0
  50. package/dist/commands/function/index.js +26 -0
  51. package/dist/commands/function/manifest.d.ts +5 -0
  52. package/dist/commands/function/manifest.js +57 -0
  53. package/dist/commands/function/prompts.d.ts +18 -0
  54. package/dist/commands/function/prompts.js +196 -0
  55. package/dist/commands/function/tooling.d.ts +2 -0
  56. package/dist/commands/function/tooling.js +43 -0
  57. package/dist/commands/function/types.d.ts +52 -0
  58. package/dist/commands/function/types.js +2 -0
  59. package/dist/commands/index.d.ts +2 -0
  60. package/dist/commands/index.js +19 -0
  61. package/dist/commands/init/action.d.ts +2 -0
  62. package/dist/commands/init/action.js +36 -0
  63. package/dist/commands/init/content.d.ts +2 -0
  64. package/dist/commands/init/content.js +97 -0
  65. package/dist/commands/init/index.d.ts +2 -0
  66. package/dist/commands/init/index.js +30 -0
  67. package/dist/commands/init/manifest.d.ts +6 -0
  68. package/dist/commands/init/manifest.js +147 -0
  69. package/dist/commands/init/prompts.d.ts +6 -0
  70. package/dist/commands/init/prompts.js +155 -0
  71. package/dist/commands/init/scaffold.d.ts +7 -0
  72. package/dist/commands/init/scaffold.js +85 -0
  73. package/dist/commands/init/templates/helpers.d.ts +9 -0
  74. package/dist/commands/init/templates/helpers.js +42 -0
  75. package/dist/commands/init/templates/node.d.ts +4 -0
  76. package/dist/commands/init/templates/node.js +99 -0
  77. package/dist/commands/init/templates/project.d.ts +8 -0
  78. package/dist/commands/init/templates/project.js +239 -0
  79. package/dist/commands/init/templates/python.d.ts +4 -0
  80. package/dist/commands/init/templates/python.js +65 -0
  81. package/dist/commands/init/templates/sam.d.ts +4 -0
  82. package/dist/commands/init/templates/sam.js +369 -0
  83. package/dist/commands/init/templates/shared-code.d.ts +6 -0
  84. package/dist/commands/init/templates/shared-code.js +159 -0
  85. package/dist/commands/init/types.d.ts +47 -0
  86. package/dist/commands/init/types.js +75 -0
  87. package/dist/commands/rm/action.d.ts +2 -0
  88. package/dist/commands/rm/action.js +89 -0
  89. package/dist/commands/rm/index.d.ts +2 -0
  90. package/dist/commands/rm/index.js +25 -0
  91. package/dist/commands/rm/plan.d.ts +19 -0
  92. package/dist/commands/rm/plan.js +78 -0
  93. package/dist/commands/rm/prompts.d.ts +4 -0
  94. package/dist/commands/rm/prompts.js +107 -0
  95. package/dist/commands/rm/types.d.ts +7 -0
  96. package/dist/commands/rm/types.js +2 -0
  97. package/dist/commands/run/action.d.ts +2 -0
  98. package/dist/commands/run/action.js +171 -0
  99. package/dist/commands/run/index.d.ts +2 -0
  100. package/dist/commands/run/index.js +21 -0
  101. package/dist/commands/run/local-template.d.ts +7 -0
  102. package/dist/commands/run/local-template.js +51 -0
  103. package/dist/commands/run/manifest.d.ts +2 -0
  104. package/dist/commands/run/manifest.js +20 -0
  105. package/dist/commands/run/resources.d.ts +20 -0
  106. package/dist/commands/run/resources.js +93 -0
  107. package/dist/commands/run/sam-cli.d.ts +10 -0
  108. package/dist/commands/run/sam-cli.js +183 -0
  109. package/dist/commands/run/types.d.ts +18 -0
  110. package/dist/commands/run/types.js +2 -0
  111. package/dist/commands/run/watch.d.ts +11 -0
  112. package/dist/commands/run/watch.js +131 -0
  113. package/dist/core/context.d.ts +15 -0
  114. package/dist/core/context.js +41 -0
  115. package/dist/core/credentials.d.ts +10 -0
  116. package/dist/core/credentials.js +22 -0
  117. package/dist/core/environments.d.ts +46 -0
  118. package/dist/core/environments.js +161 -0
  119. package/dist/core/errors.d.ts +4 -0
  120. package/dist/core/errors.js +11 -0
  121. package/dist/core/flags-docs.d.ts +2 -0
  122. package/dist/core/flags-docs.js +63 -0
  123. package/dist/core/is-ci.d.ts +4 -0
  124. package/dist/core/is-ci.js +16 -0
  125. package/dist/core/logger.d.ts +4 -0
  126. package/dist/core/logger.js +19 -0
  127. package/dist/core/run.d.ts +1 -0
  128. package/dist/core/run.js +27 -0
  129. package/dist/core/scope.d.ts +33 -0
  130. package/dist/core/scope.js +108 -0
  131. package/dist/index.d.ts +3 -0
  132. package/dist/index.js +9 -0
  133. package/dist/program.d.ts +2 -0
  134. package/dist/program.js +26 -0
  135. package/package.json +110 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Savinda Jayasekara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,22 @@
1
+ # NOTICE
2
+
3
+ slskit is licensed under the MIT License. See [LICENSE](LICENSE).
4
+
5
+ Copyright (c) 2026 Savinda Jayasekara.
6
+
7
+ ## Third-party runtime software
8
+
9
+ Production dependencies and their licenses (also checked by `npm run licenses`):
10
+
11
+ | Package | License |
12
+ | --- | --- |
13
+ | commander | MIT |
14
+ | @inquirer/prompts | MIT |
15
+
16
+ Each of those packages includes its own copyright and license notice. Those
17
+ notices remain in force.
18
+
19
+ ## Reference material
20
+
21
+ Architecture and CI practices were informed by publicly documented patterns from
22
+ the Netlify CLI (MIT). No Netlify source code is included in this repository.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # slskit
2
+
3
+ [![CI](https://github.com/savindaJ/SLSKIT-CLI/actions/workflows/ci.yml/badge.svg)](https://github.com/savindaJ/SLSKIT-CLI/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/slskit-cli.svg)](https://www.npmjs.com/package/slskit-cli)
5
+ [![node](https://img.shields.io/node/v/slskit-cli.svg)](https://www.npmjs.com/package/slskit-cli)
6
+ [![license](https://img.shields.io/npm/l/slskit-cli.svg)](LICENSE)
7
+
8
+ A CLI for scaffolding and growing multi-service AWS Lambda projects on AWS SAM.
9
+
10
+ Starting a serverless project usually means a day of wiring: a template, a build
11
+ setup, handlers, an API Gateway, environment variables that behave the same locally
12
+ and in AWS, and a deploy that doesn't fight you. `slskit` gives you all of it in one
13
+ command, then stays useful — adding functions, managing environments, running the
14
+ project locally, and deploying a single function when that's all that changed.
15
+
16
+ Two runtime dependencies. Everything that touches AWS goes through the official
17
+ `sam` and `aws` CLIs.
18
+
19
+ ```bash
20
+ npm install -g slskit-cli
21
+ slskit init my-lambda-app
22
+ cd my-lambda-app
23
+ slskit run
24
+ ```
25
+
26
+ In CI, install it locally and pin the version with a lockfile. A global install
27
+ always pulls the latest release, including breaking changes.
28
+
29
+ ```bash
30
+ npm install --save-dev slskit-cli
31
+ npx slskit deploy --yes
32
+ ```
33
+
34
+ That's a running API on `http://127.0.0.1:3000` with two services, four functions, a
35
+ shared Lambda layer, and hot reload on save.
36
+
37
+ ## What you get
38
+
39
+ ```text
40
+ my-lambda-app/
41
+ ├── src/
42
+ │ ├── functions/ # handlers, one folder per service
43
+ │ ├── services/ # business logic the handlers import
44
+ │ └── shared/ # shared utilities, published as a Lambda layer
45
+ ├── templates/ # one CloudFormation stack per service
46
+ ├── template.yaml # root stack, nests them
47
+ ├── .env.dev # the dev environment's variables (gitignored)
48
+ └── slskit.json # the project graph every command reads
49
+ ```
50
+
51
+ A root stack nesting one template per service, so the root file stays short as the
52
+ project grows and services can be deployed one at a time. TypeScript, JavaScript or
53
+ Python — and individual functions can differ from the rest of the project.
54
+
55
+ ## Highlights
56
+
57
+ **Environments that don't leak into each other.** Every environment is its own deploy
58
+ target: region, profile, CloudFormation stack, variables. `APP_ENVIRONMENT` is
59
+ generated for each one, and every resource name is built from it — `shop-dev-login`
60
+ and `shop-production-login` are different functions in different stacks.
61
+
62
+ ```bash
63
+ slskit env add production --profile prod-admin --region eu-west-2
64
+ slskit env set API_KEY=sk-live-xxx --secret --env production
65
+ slskit deploy production
66
+ ```
67
+
68
+ **Variables that reach your code the same way locally and in AWS.** Add one with
69
+ `slskit env set`, or just type it into `.env.<environment>` — either way it becomes a
70
+ CloudFormation parameter wired into every function, and `process.env.YOUR_KEY` works
71
+ in both places. Secrets stay out of `slskit.json`, and `--ssm` keeps them out of the
72
+ CLI entirely.
73
+
74
+ **Run or deploy just the part you're working on.**
75
+
76
+ ```bash
77
+ slskit run --service auth # only auth's routes, so only those build
78
+ slskit deploy dev --function login # code-only update of one function
79
+ ```
80
+
81
+ Both commands ask if you don't tell them. A scoped run builds only the functions it
82
+ serves; a scoped deploy uses `sam sync --code`, which is far faster than a full one.
83
+
84
+ **Hot reload.** `slskit run` watches `src/` and rebuilds only the function you
85
+ touched — the next request uses the new code, no restart. A broken file leaves the
86
+ previous build serving and waits for the next save.
87
+
88
+ **A deploy that tells you what it will do.** It stops before calling AWS if the
89
+ region is missing, the credentials don't verify, or a declared secret has no value —
90
+ so a failed deploy costs nothing. Then it prints the plan, asks, and reports the API
91
+ Gateway URLs when it's done.
92
+
93
+ **Credentials never enter your project.** `slskit configure` stores only the AWS
94
+ profile *name* in `slskit.json`; access keys go to `~/.aws/credentials` via the AWS
95
+ CLI. Secrets are never echoed, and override values are never logged.
96
+
97
+ ## Commands
98
+
99
+ | Command | What it does |
100
+ | --- | --- |
101
+ | `slskit init [name]` | Scaffold a new project |
102
+ | `slskit run [environment]` | Run it locally on one API Gateway port |
103
+ | `slskit function [name]` | Add a function to a new or existing service |
104
+ | `slskit rm [name]` | Remove a function or a service, and everything that referenced it |
105
+ | `slskit configure` | Set AWS credentials and the deploy target |
106
+ | `slskit env` | Manage environments and their variables |
107
+ | `slskit deploy [environment]` | Deploy to AWS |
108
+
109
+ **[COMMAND.md](COMMAND.md) is the full reference** — every flag, every behaviour,
110
+ and the reasoning behind the parts that aren't obvious.
111
+
112
+ ## Requirements
113
+
114
+ | | |
115
+ | --- | --- |
116
+ | Node.js | 18 or newer |
117
+ | [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) | to run and deploy — `slskit run` offers to install it |
118
+ | [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) | to verify credentials and read stack outputs |
119
+ | Docker | for `sam local`, which runs functions in Lambda-like containers |
120
+
121
+ ## First deploy
122
+
123
+ ```bash
124
+ slskit configure --set-credentials # asks for a key, writes ~/.aws/credentials
125
+ slskit deploy dev
126
+ ```
127
+
128
+ API Gateway, the functions, the layer and any tables are created in one pass —
129
+ `--resolve-s3` provisions the artifact bucket, so there's no manual setup. It prints
130
+ the URLs at the end:
131
+
132
+ ```text
133
+ Deployed the whole project of "shop" to "dev".
134
+ stack: shop-dev
135
+ region: us-east-1
136
+
137
+ Endpoints:
138
+ ApiUrl: https://6hjgplpky0.execute-api.us-east-1.amazonaws.com
139
+ ```
140
+
141
+ ## Development
142
+
143
+ ```bash
144
+ git clone https://github.com/savindaJ/SLSKIT-CLI.git
145
+ cd SLSKIT-CLI
146
+ npm install
147
+ npm test
148
+ ```
149
+
150
+ ```bash
151
+ npm run build && npm link # use your working copy as the real slskit command
152
+ npm run dev # run from source via tsx
153
+ npm run typecheck # tsc --noEmit
154
+ npm run docs # regenerate docs/flags.md from Commander
155
+ npm run licenses # check the project and production dependency licenses
156
+ ```
157
+
158
+ See [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. Bug reports and
159
+ documentation fixes are just as welcome as code.
160
+
161
+ ## Security
162
+
163
+ Report vulnerabilities privately — see [SECURITY.md](SECURITY.md), which also
164
+ documents exactly how slskit handles your credentials.
165
+
166
+ ## License
167
+
168
+ [MIT](LICENSE) © Savinda Jayasekara. Third-party notices are in [NOTICE.md](NOTICE.md).
package/bin/slskit.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require("../dist/cli.js");
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const errors_js_1 = require("./core/errors.js");
4
+ const logger_js_1 = require("./core/logger.js");
5
+ const program_js_1 = require("./program.js");
6
+ async function main() {
7
+ const program = (0, program_js_1.createProgram)();
8
+ const argv = process.argv.slice(2);
9
+ if (argv.length === 0) {
10
+ program.outputHelp();
11
+ return;
12
+ }
13
+ try {
14
+ await program.parseAsync(process.argv);
15
+ }
16
+ catch (error) {
17
+ if (error instanceof errors_js_1.CliError) {
18
+ logger_js_1.logger.error(error.message);
19
+ process.exitCode = error.exitCode;
20
+ return;
21
+ }
22
+ throw error;
23
+ }
24
+ }
25
+ void main();
@@ -0,0 +1,5 @@
1
+ import { Command } from "commander";
2
+ export declare function addGlobalOptions(command: Command): Command;
3
+ export declare class BaseCommand extends Command {
4
+ createCommand(name?: string): BaseCommand;
5
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseCommand = void 0;
4
+ exports.addGlobalOptions = addGlobalOptions;
5
+ const commander_1 = require("commander");
6
+ const context_js_1 = require("../core/context.js");
7
+ function addGlobalOptions(command) {
8
+ return command
9
+ .addOption(new commander_1.Option("--cwd <path>", "Run as if started from this directory"))
10
+ .addOption(new commander_1.Option("--debug", "Print stack traces when a command fails"))
11
+ .addOption(new commander_1.Option("--silent", "Silence informational output"))
12
+ .addOption(new commander_1.Option("--json", "JSON output where a command supports it"));
13
+ }
14
+ function attachGlobalHook(command) {
15
+ command.hook("preAction", (_parent, actionCommand) => {
16
+ (0, context_js_1.applyGlobalOptions)(actionCommand.optsWithGlobals());
17
+ });
18
+ }
19
+ // Every command created through program.command() inherits the global flags,
20
+ // so `slskit deploy --debug` and `slskit --debug deploy` both work.
21
+ class BaseCommand extends commander_1.Command {
22
+ createCommand(name) {
23
+ const command = new BaseCommand(name);
24
+ addGlobalOptions(command);
25
+ attachGlobalHook(command);
26
+ return command;
27
+ }
28
+ }
29
+ exports.BaseCommand = BaseCommand;
@@ -0,0 +1,2 @@
1
+ import type { ConfigureOptions } from "./types.js";
2
+ export declare function configureAction(options: ConfigureOptions): Promise<void>;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.configureAction = configureAction;
4
+ const errors_js_1 = require("../../core/errors.js");
5
+ const logger_js_1 = require("../../core/logger.js");
6
+ const environments_js_1 = require("../../core/environments.js");
7
+ const action_js_1 = require("../env/action.js");
8
+ const aws_cli_js_1 = require("./aws-cli.js");
9
+ const prompts_js_1 = require("./prompts.js");
10
+ const ACCESS_KEY_PATTERN = /^[A-Z0-9]{16,128}$/;
11
+ function credentialSource(profile) {
12
+ return profile ? `profile "${profile}"` : "the environment";
13
+ }
14
+ function fixHint(profile) {
15
+ return profile
16
+ ? `aws configure --profile ${profile} (or "aws sso login --profile ${profile}" for an SSO profile)`
17
+ : "Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, or pick a named profile instead.";
18
+ }
19
+ // Prompts for an access key and hands it to the AWS CLI, which stores it in
20
+ // ~/.aws/credentials. The secret is never echoed, never logged, and never written
21
+ // anywhere inside the project.
22
+ async function promptForCredentials(answers) {
23
+ const { input, password, confirm } = await import("@inquirer/prompts");
24
+ const proceed = await confirm({
25
+ message: "Enter an AWS access key now and store it in ~/.aws/credentials?",
26
+ default: true,
27
+ });
28
+ if (!proceed) {
29
+ return undefined;
30
+ }
31
+ const profile = answers.profile ??
32
+ (await input({
33
+ message: "Profile name to store it under:",
34
+ default: "default",
35
+ required: true,
36
+ }));
37
+ const accessKeyId = (await input({
38
+ message: "AWS Access Key ID:",
39
+ required: true,
40
+ })).trim();
41
+ if (!ACCESS_KEY_PATTERN.test(accessKeyId)) {
42
+ throw new errors_js_1.CliError(`"${accessKeyId}" does not look like an AWS Access Key ID (expected something like AKIA... ).`);
43
+ }
44
+ const secretAccessKey = (await password({ message: "AWS Secret Access Key:", mask: true })).trim();
45
+ if (!secretAccessKey) {
46
+ throw new errors_js_1.CliError("A secret access key is required.");
47
+ }
48
+ const { ok, error } = (0, aws_cli_js_1.setProfileCredentials)(profile, accessKeyId, secretAccessKey, answers.region);
49
+ if (!ok) {
50
+ throw new errors_js_1.CliError(`Could not store the credentials: ${error ?? "aws configure set failed."}\nStore them yourself with: aws configure --profile ${profile}`);
51
+ }
52
+ logger_js_1.logger.info(`\nStored the key for profile "${profile}" in ${(0, aws_cli_js_1.credentialsFilePath)()}.`);
53
+ logger_js_1.logger.info(" the secret was not echoed and is not stored in this project");
54
+ return profile;
55
+ }
56
+ // Returns the profile actually used, which may change if credentials were entered
57
+ // against a new profile name.
58
+ async function verifyCredentials(answers, options) {
59
+ if (options.skipVerify) {
60
+ logger_js_1.logger.info("\nSkipped the credential check (--skip-verify).");
61
+ return { profile: answers.profile };
62
+ }
63
+ if (!(0, aws_cli_js_1.isAwsCliAvailable)()) {
64
+ logger_js_1.logger.info(`\nAWS CLI was not found on your PATH, so the credentials were not verified.\n Install it: ${aws_cli_js_1.AWS_INSTALL_URL}\n Then re-run "slskit configure" to check them.`);
65
+ return { profile: answers.profile };
66
+ }
67
+ let profile = answers.profile;
68
+ let { identity, error } = (0, aws_cli_js_1.getCallerIdentity)(profile, answers.region);
69
+ const wantsNewKey = options.setCredentials && process.stdin.isTTY;
70
+ if (!identity || wantsNewKey) {
71
+ if (!process.stdin.isTTY) {
72
+ throw new errors_js_1.CliError(`Could not verify AWS credentials for ${credentialSource(profile)} in ${answers.region}.\n ${error ?? "sts get-caller-identity failed."}\n\nFix them with:\n ${fixHint(profile)}\n\nThen re-run "slskit configure", or pass --skip-verify to save without checking.`);
73
+ }
74
+ if (!identity) {
75
+ logger_js_1.logger.info(`\nCould not verify AWS credentials for ${credentialSource(profile)} in ${answers.region}.`);
76
+ logger_js_1.logger.info(` ${error ?? "sts get-caller-identity failed."}`);
77
+ }
78
+ const stored = await promptForCredentials(answers);
79
+ if (!stored) {
80
+ throw new errors_js_1.CliError(`AWS credentials are required to deploy.\n\nFix them with:\n ${fixHint(profile)}\n\nThen re-run "slskit configure", or pass --skip-verify to save without checking.`);
81
+ }
82
+ profile = stored;
83
+ ({ identity, error } = (0, aws_cli_js_1.getCallerIdentity)(profile, answers.region));
84
+ if (!identity) {
85
+ throw new errors_js_1.CliError(`The key was stored for profile "${profile}", but AWS still rejected it in ${answers.region}.\n ${error ?? "sts get-caller-identity failed."}\n\nCheck that the key is active and has permission to call sts:GetCallerIdentity.`);
86
+ }
87
+ }
88
+ logger_js_1.logger.info("\nVerified AWS credentials.");
89
+ logger_js_1.logger.info(` account: ${identity.account}`);
90
+ logger_js_1.logger.info(` identity: ${identity.arn}`);
91
+ return { profile, identity };
92
+ }
93
+ async function configureAction(options) {
94
+ const cwd = process.cwd();
95
+ const manifest = (0, environments_js_1.readManifest)(cwd, "slskit configure");
96
+ const answers = await (0, prompts_js_1.collectConfigureAnswers)(manifest, options);
97
+ const { profile } = await verifyCredentials(answers, options);
98
+ const existing = manifest.environments?.list?.[answers.environment];
99
+ const config = {
100
+ region: answers.region,
101
+ stackName: answers.stackName,
102
+ };
103
+ if (profile) {
104
+ config.profile = profile;
105
+ }
106
+ // Variables already recorded for this environment survive a re-configure.
107
+ if (existing?.variables) {
108
+ config.variables = existing.variables;
109
+ }
110
+ (0, environments_js_1.writeEnvironment)(cwd, manifest, answers.environment, config);
111
+ const dotenv = (0, action_js_1.seedDotenv)(cwd, answers.environment);
112
+ logger_js_1.logger.info(`\nConfigured environment "${answers.environment}" for "${manifest.name}".`);
113
+ logger_js_1.logger.info(` ${environments_js_1.APP_ENVIRONMENT_KEY}: ${answers.environment}`);
114
+ logger_js_1.logger.info(` credentials: ${credentialSource(profile)}`);
115
+ logger_js_1.logger.info(` region: ${answers.region}`);
116
+ logger_js_1.logger.info(` stack: ${answers.stackName}`);
117
+ logger_js_1.logger.info(` written to: ${environments_js_1.MANIFEST_FILE}, ${dotenv}`);
118
+ logger_js_1.logger.info("\nNo credential material is stored in this project — only the profile name, resolved at deploy time.");
119
+ logger_js_1.logger.info(`\nDeploy it with: slskit deploy ${answers.environment}`);
120
+ }
@@ -0,0 +1,16 @@
1
+ import type { IdentityResult } from "./types.js";
2
+ export declare const AWS_INSTALL_URL = "https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html";
3
+ export declare function isAwsCliAvailable(): boolean;
4
+ export declare function awsConfigDir(): string;
5
+ export declare function listAwsProfiles(): string[];
6
+ export declare function hasEnvCredentials(): boolean;
7
+ export declare function envProfile(): string | undefined;
8
+ export declare function envRegion(): string | undefined;
9
+ export declare function configuredRegion(profile?: string): string | undefined;
10
+ export declare function getCallerIdentity(profile: string | undefined, region: string): IdentityResult;
11
+ export interface CredentialWriteResult {
12
+ ok: boolean;
13
+ error?: string;
14
+ }
15
+ export declare function setProfileCredentials(profile: string, accessKeyId: string, secretAccessKey: string, region?: string): CredentialWriteResult;
16
+ export declare function credentialsFilePath(): string;
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AWS_INSTALL_URL = void 0;
7
+ exports.isAwsCliAvailable = isAwsCliAvailable;
8
+ exports.awsConfigDir = awsConfigDir;
9
+ exports.listAwsProfiles = listAwsProfiles;
10
+ exports.hasEnvCredentials = hasEnvCredentials;
11
+ exports.envProfile = envProfile;
12
+ exports.envRegion = envRegion;
13
+ exports.configuredRegion = configuredRegion;
14
+ exports.getCallerIdentity = getCallerIdentity;
15
+ exports.setProfileCredentials = setProfileCredentials;
16
+ exports.credentialsFilePath = credentialsFilePath;
17
+ const node_child_process_1 = require("node:child_process");
18
+ const node_fs_1 = __importDefault(require("node:fs"));
19
+ const node_os_1 = __importDefault(require("node:os"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const USE_SHELL = process.platform === "win32";
22
+ exports.AWS_INSTALL_URL = "https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html";
23
+ function isAwsCliAvailable() {
24
+ const result = (0, node_child_process_1.spawnSync)("aws", ["--version"], {
25
+ stdio: "ignore",
26
+ shell: USE_SHELL,
27
+ });
28
+ return !result.error && result.status === 0;
29
+ }
30
+ function readIfExists(file) {
31
+ try {
32
+ return node_fs_1.default.readFileSync(file, "utf8");
33
+ }
34
+ catch {
35
+ return "";
36
+ }
37
+ }
38
+ function awsConfigDir() {
39
+ const configFile = process.env.AWS_CONFIG_FILE;
40
+ return configFile ? node_path_1.default.dirname(configFile) : node_path_1.default.join(node_os_1.default.homedir(), ".aws");
41
+ }
42
+ // Section headers are "[default]" and "[profile foo]" in ~/.aws/config, but plain
43
+ // "[foo]" in ~/.aws/credentials, so the "profile " prefix is optional here.
44
+ function parseProfileNames(contents) {
45
+ const names = [];
46
+ for (const line of contents.split("\n")) {
47
+ const match = /^\s*\[\s*(?:profile\s+)?([^\]\s]+)\s*\]/.exec(line);
48
+ if (match) {
49
+ names.push(match[1]);
50
+ }
51
+ }
52
+ return names;
53
+ }
54
+ function listAwsProfiles() {
55
+ const dir = awsConfigDir();
56
+ const names = [
57
+ ...parseProfileNames(readIfExists(node_path_1.default.join(dir, "config"))),
58
+ ...parseProfileNames(readIfExists(node_path_1.default.join(dir, "credentials"))),
59
+ ];
60
+ return [...new Set(names)].sort();
61
+ }
62
+ function hasEnvCredentials() {
63
+ return Boolean(process.env.AWS_ACCESS_KEY_ID || process.env.AWS_SESSION_TOKEN);
64
+ }
65
+ function envProfile() {
66
+ return process.env.AWS_PROFILE?.trim() || undefined;
67
+ }
68
+ function envRegion() {
69
+ const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
70
+ return region?.trim() || undefined;
71
+ }
72
+ // Asking the AWS CLI lets it apply its own resolution rules (config file, SSO
73
+ // session, env) instead of this command re-deriving them and getting them wrong.
74
+ function configuredRegion(profile) {
75
+ const args = ["configure", "get", "region"];
76
+ if (profile) {
77
+ args.push("--profile", profile);
78
+ }
79
+ const result = (0, node_child_process_1.spawnSync)("aws", args, { encoding: "utf8", shell: USE_SHELL });
80
+ if (result.error || result.status !== 0) {
81
+ return undefined;
82
+ }
83
+ return result.stdout?.trim() || undefined;
84
+ }
85
+ function getCallerIdentity(profile, region) {
86
+ const args = ["sts", "get-caller-identity", "--output", "json", "--region", region];
87
+ if (profile) {
88
+ args.push("--profile", profile);
89
+ }
90
+ const result = (0, node_child_process_1.spawnSync)("aws", args, { encoding: "utf8", shell: USE_SHELL });
91
+ if (result.error) {
92
+ return { error: result.error.message };
93
+ }
94
+ if (result.status !== 0) {
95
+ return { error: result.stderr?.trim() || `aws exited with code ${result.status ?? 1}` };
96
+ }
97
+ try {
98
+ const parsed = JSON.parse(result.stdout);
99
+ if (!parsed.Account || !parsed.Arn) {
100
+ return { error: "sts get-caller-identity returned no account or ARN." };
101
+ }
102
+ return { identity: { account: parsed.Account, arn: parsed.Arn } };
103
+ }
104
+ catch {
105
+ return { error: "Could not parse the response from sts get-caller-identity." };
106
+ }
107
+ }
108
+ // Credentials are handed to the AWS CLI so they land in ~/.aws/credentials in the
109
+ // format AWS expects — never in the project directory, and never in slskit.json.
110
+ // Deliberately runs without a shell so the secret is not exposed to shell parsing.
111
+ function setProfileCredentials(profile, accessKeyId, secretAccessKey, region) {
112
+ const entries = [
113
+ ["aws_access_key_id", accessKeyId],
114
+ ["aws_secret_access_key", secretAccessKey],
115
+ ];
116
+ if (region) {
117
+ entries.push(["region", region]);
118
+ }
119
+ for (const [key, value] of entries) {
120
+ const result = (0, node_child_process_1.spawnSync)("aws", ["configure", "set", key, value, "--profile", profile], { encoding: "utf8" });
121
+ if (result.error) {
122
+ return { ok: false, error: result.error.message };
123
+ }
124
+ if (result.status !== 0) {
125
+ return {
126
+ ok: false,
127
+ error: result.stderr?.trim() || `aws configure set ${key} exited with ${result.status}`,
128
+ };
129
+ }
130
+ }
131
+ return { ok: true };
132
+ }
133
+ function credentialsFilePath() {
134
+ return process.env.AWS_SHARED_CREDENTIALS_FILE ?? node_path_1.default.join(awsConfigDir(), "credentials");
135
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerConfigureCommand(program: Command): void;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerConfigureCommand = registerConfigureCommand;
4
+ const run_js_1 = require("../../core/run.js");
5
+ const types_js_1 = require("./types.js");
6
+ function registerConfigureCommand(program) {
7
+ program
8
+ .command("configure")
9
+ .description("Set up AWS credentials and the deploy target for an environment")
10
+ .option("-e, --env <name>", `Environment to configure (default: "${types_js_1.DEFAULT_ENVIRONMENT}")`)
11
+ .option("--profile <name>", "AWS named profile to resolve credentials from")
12
+ .option("--region <region>", "AWS region to deploy into (example: us-east-1)")
13
+ .option("--stack-name <name>", "CloudFormation stack name for this environment")
14
+ .option("--skip-verify", "Save without checking that the credentials work", false)
15
+ .option("--set-credentials", "Enter an AWS access key and store it in ~/.aws/credentials", false)
16
+ .action((options) => (0, run_js_1.runCommand)(async () => {
17
+ const { configureAction } = await import("./action.js");
18
+ await configureAction(options);
19
+ }));
20
+ }
@@ -0,0 +1,8 @@
1
+ import { parseEnvironmentName } from "../../core/environments.js";
2
+ import type { ConfigurableManifest, ConfigureAnswers, ConfigureOptions } from "./types.js";
3
+ export declare const ENVIRONMENT_PROFILE = "";
4
+ export declare function parseRegion(value: string | undefined): string | undefined;
5
+ export declare function parseStackName(value: string | undefined): string | undefined;
6
+ export declare function defaultStackName(projectName: string, environment: string): string;
7
+ export declare function collectConfigureAnswers(manifest: ConfigurableManifest, options: ConfigureOptions): Promise<ConfigureAnswers>;
8
+ export { parseEnvironmentName };