@projectdochelp/s3te 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2022 Heinz Olaf Klöppel
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/README.md ADDED
@@ -0,0 +1,442 @@
1
+ # S3TemplateEngine
2
+
3
+ S3TemplateEngine is a lightweight serverless template engine for people who want to keep writing HTML and still publish through AWS. You write templates and assets, S3TE renders and publishes the static result.
4
+
5
+ This README is the user guide for the rewrite generation. The deeper implementation specs still live in [`docs/`](./docs/), but this file is intentionally written for users first.
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Motivation](#motivation)
10
+ 2. [Support](#support)
11
+ 3. [Concept](#concept)
12
+ 4. [Installation (AWS)](#installation-aws)
13
+ 5. [Installation (VSCode)](#installation-vscode)
14
+ 6. [Installation (S3TE)](#installation-s3te)
15
+ 7. [Usage](#usage)
16
+ 8. [Optional: Webiny CMS](#optional-webiny-cms)
17
+
18
+ ## Motivation
19
+
20
+ AWS S3 and CloudFront are a great platform for websites: cheap, fast and low-maintenance. The annoying part usually starts before hosting. You still need reusable HTML, a safe deployment flow, and maybe a way for editors to maintain content without turning your project into a full framework.
21
+
22
+ **S3TemplateEngine is for you if** you want to keep writing HTML by hand, but still want to:
23
+
24
+ - reuse snippets like headers, navigation and footer blocks
25
+ - generate many pages from one template
26
+ - publish multiple languages or variants from one project
27
+ - keep AWS hosting simple and low-cost
28
+ - optionally let editors maintain content in Webiny
29
+ - deploy without hand-editing Lambda settings or uploading ZIP files yourself
30
+
31
+ ## Support
32
+
33
+ If S3TE saves you time and you want to buy me a tea, you can do that here:
34
+
35
+ [![Support me on Ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/hokcomics)
36
+
37
+ If you need help, found a bug, or want to contribute, open an issue or pull request on GitHub.
38
+
39
+ ## Concept
40
+
41
+ S3TE keeps one simple promise: you write source templates, S3TE turns them into static files, AWS serves the result.
42
+
43
+ ![S3TE overview](https://user-images.githubusercontent.com/100029932/174443152-b16c98fc-f2f2-420e-9f5b-a6ea7a861acd.png)
44
+
45
+ <details>
46
+ <summary>What is the difference between the code bucket and the website bucket?</summary>
47
+
48
+ The code bucket receives your source project files: `.html`, `.part`, CSS, JavaScript, images and everything else you deploy from your project.
49
+
50
+ The website bucket contains the finished result that visitors actually receive through CloudFront. That split is what makes incremental rendering, generated pages and safe re-deploys possible.
51
+
52
+ </details>
53
+
54
+ <details>
55
+ <summary>What happens when I deploy?</summary>
56
+
57
+ `s3te deploy` validates the project, packages the AWS runtime, creates or updates one persistent CloudFormation environment stack, creates one temporary CloudFormation deploy stack for packaging artifacts, synchronizes your current source files into the code bucket, and removes the temporary stack again after the real deploy run.
58
+
59
+ The persistent environment stack contains the long-lived AWS resources such as buckets, Lambda functions, DynamoDB tables, CloudFront distributions and the runtime manifest parameter. The temporary deploy stack exists only so CloudFormation can consume the packaged Lambda artifacts cleanly.
60
+
61
+ </details>
62
+
63
+ ## Installation (AWS)
64
+
65
+ This section is only about the AWS things you need before you touch S3TE. The actual click-by-click screens are best left to the official AWS documentation, because the console changes over time. The goal here is to tell you exactly what S3TE needs from AWS, why it needs it, and which official page gets you there.
66
+
67
+ <details>
68
+ <summary>What you need before your first S3TE deploy</summary>
69
+
70
+ | Item | Why S3TE needs it | Official guide |
71
+ | --- | --- | --- |
72
+ | AWS account | S3TE deploys into your own AWS account. | [Create an AWS account](https://portal.aws.amazon.com/billing/signup) |
73
+ | Daily-work AWS access | `s3te deploy` needs credentials that can create CloudFormation stacks and related resources. | [Create an IAM user](https://docs.aws.amazon.com/console/iam/add-users), [Manage access keys](https://docs.aws.amazon.com/IAM/latest/UserGuide/access-keys-admin-managed.html) |
74
+ | AWS CLI v2 | The S3TE CLI shells out to the official `aws` CLI. | [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html), [Get started with AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) |
75
+ | Domain name you control | CloudFront and TLS only make sense for domains you can point to AWS. | Use your registrar of choice |
76
+ | ACM certificate in `us-east-1` | CloudFront requires its public certificate in `us-east-1`. | [Public certificates in ACM](https://docs.aws.amazon.com/acm/latest/userguide/acm-public-certificates.html) |
77
+ | Optional Route53 hosted zone | Needed only if S3TE should create DNS alias records automatically. | [Create a public hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/CreatingHostedZone.html) |
78
+
79
+ </details>
80
+
81
+ <details>
82
+ <summary>Recommended AWS order for first-time users</summary>
83
+
84
+ 1. Create the AWS account.
85
+ 2. Stop using the root user for daily work.
86
+ 3. Create one deployment identity for yourself.
87
+ 4. Install AWS CLI v2 locally.
88
+ 5. Run `aws configure` and verify it with `aws sts get-caller-identity`.
89
+ 6. Request the ACM certificate in `us-east-1`.
90
+ 7. If you want automatic DNS records, create or locate the Route53 hosted zone.
91
+
92
+ For a first personal setup, the easiest route is usually one IAM user with console access and access keys. If you already work with AWS Identity Center or another federated login, that is fine too. S3TE uses the standard AWS credential chain.
93
+
94
+ </details>
95
+
96
+ ## Installation (VSCode)
97
+
98
+ This section is only about the local editing experience. S3TE does not require VSCode, but VSCode is the reference editor workflow and the easiest path for most users.
99
+
100
+ <details>
101
+ <summary>Install the local tools you need</summary>
102
+
103
+ | Tool | Why you want it | Official guide |
104
+ | --- | --- | --- |
105
+ | Visual Studio Code | Comfortable editor with integrated terminal and extension support. | [VS Code setup overview](https://code.visualstudio.com/docs/setup/setup-overview), [Get started with VS Code](https://code.visualstudio.com/docs/getstarted/getting-started) |
106
+ | Node.js 20 or newer | Required for the S3TE CLI and local rendering. | [Download Node.js](https://nodejs.org/en/download) |
107
+
108
+ </details>
109
+
110
+ <details>
111
+ <summary>What to verify before you continue</summary>
112
+
113
+ Open VSCode, open the integrated terminal, and run:
114
+
115
+ ```bash
116
+ node --version
117
+ npm --version
118
+ ```
119
+
120
+ If both commands print a version number, your local machine is ready for S3TE.
121
+
122
+ </details>
123
+
124
+ ## Installation (S3TE)
125
+
126
+ This is the S3TE-specific part. No AWS console links, no editor tutorial, just the steps that actually create and run an S3TE project.
127
+
128
+ <details>
129
+ <summary>1. Create your project folder</summary>
130
+
131
+ ```bash
132
+ mkdir mywebsite
133
+ cd mywebsite
134
+ ```
135
+
136
+ </details>
137
+
138
+ <details>
139
+ <summary>2. Scaffold the project once</summary>
140
+
141
+ If `@projectdochelp/s3te` is already published on npm, scaffold the project once with a temporary `npx` run:
142
+
143
+ ```bash
144
+ npx --package @projectdochelp/s3te s3te init --project-name mywebsite --base-url example.com
145
+ ```
146
+
147
+ That command only works after a real npm publish. A GitHub repository on its own is not enough.
148
+
149
+ If you are still working from this repository before the first npm publish, run the CLI directly from the repo root instead:
150
+
151
+ ```bash
152
+ node packages/cli/bin/s3te.mjs init --dir ./mywebsite --project-name mywebsite --base-url example.com
153
+ ```
154
+
155
+ </details>
156
+
157
+ <details>
158
+ <summary>3. What the scaffold creates</summary>
159
+
160
+ The default scaffold creates:
161
+
162
+ ```text
163
+ mywebsite/
164
+ package.json
165
+ s3te.config.json
166
+ app/
167
+ part/
168
+ head.part
169
+ website/
170
+ index.html
171
+ offline/
172
+ content/
173
+ en.json
174
+ schemas/
175
+ s3te.config.schema.json
176
+ tests/
177
+ project.test.mjs
178
+ .vscode/
179
+ extensions.json
180
+ ```
181
+
182
+ </details>
183
+
184
+ <details>
185
+ <summary>4. Install S3TE locally in the project</summary>
186
+
187
+ ```bash
188
+ npm install --save-dev @projectdochelp/s3te
189
+ npx s3te --help
190
+ ```
191
+
192
+ This is the recommended everyday setup. From this point on, you run the project-local version with `npx s3te ...`.
193
+
194
+ If you want to use the built-in test helpers in your own project tests, import them from the same package via `@projectdochelp/s3te/testkit`.
195
+
196
+ </details>
197
+
198
+ <details>
199
+ <summary>5. Fill in the real AWS values in <code>s3te.config.json</code></summary>
200
+
201
+ The most important fields for a first deployment are:
202
+
203
+ ```json
204
+ {
205
+ "environments": {
206
+ "dev": {
207
+ "awsRegion": "eu-central-1",
208
+ "stackPrefix": "DEV",
209
+ "certificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/replace-me",
210
+ "route53HostedZoneId": "Z1234567890"
211
+ }
212
+ },
213
+ "variants": {
214
+ "website": {
215
+ "languages": {
216
+ "en": {
217
+ "baseUrl": "example.com",
218
+ "cloudFrontAliases": ["example.com", "www.example.com"]
219
+ }
220
+ }
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
226
+ `route53HostedZoneId` is optional. Leave it out if you want to manage DNS yourself.
227
+
228
+ </details>
229
+
230
+ <details>
231
+ <summary>6. Run the first local check and deploy</summary>
232
+
233
+ ```bash
234
+ npx s3te validate
235
+ npx s3te render --env dev
236
+ npx s3te test
237
+ npx s3te doctor --env dev
238
+ npx s3te deploy --env dev
239
+ ```
240
+
241
+ `render` writes the local preview into `offline/S3TELocal/preview/dev/...`.
242
+
243
+ `deploy` creates or updates the persistent environment stack, uses a temporary deploy stack for packaged Lambda artifacts, synchronizes the source project into the code bucket, and removes the temporary stack again when the deploy finishes.
244
+
245
+ If you left `route53HostedZoneId` out of the config, the last DNS step stays manual: point your domain at the created CloudFront distribution after deploy.
246
+
247
+ </details>
248
+
249
+ ## Usage
250
+
251
+ Once the project is installed, your everyday loop is deliberately small: edit templates, validate, render locally, then deploy.
252
+
253
+ ### Daily Workflow
254
+
255
+ <details>
256
+ <summary>Typical daily loop</summary>
257
+
258
+ 1. Edit files in `app/part/` and `app/website/`.
259
+ 2. If you use content-driven tags without Webiny, edit `offline/content/en.json` or `offline/content/items.json`.
260
+ 3. Validate and render locally.
261
+ 4. Run your tests.
262
+ 5. Deploy when the result looks right.
263
+
264
+ ```bash
265
+ npx s3te validate
266
+ npx s3te render --env dev
267
+ npx s3te test
268
+ npx s3te deploy --env dev
269
+ ```
270
+
271
+ </details>
272
+
273
+ ### CLI Commands
274
+
275
+ <details>
276
+ <summary>Command overview</summary>
277
+
278
+ | Command | What it does |
279
+ | --- | --- |
280
+ | `s3te init` | Creates the starter project structure and base config. |
281
+ | `s3te validate` | Checks config and template syntax without rendering outputs. |
282
+ | `s3te render --env <name>` | Renders locally into `offline/S3TELocal/preview/<env>/...`. |
283
+ | `s3te test` | Runs the project tests from `offline/tests/`. |
284
+ | `s3te package --env <name>` | Builds the AWS deployment artifacts without deploying them yet. |
285
+ | `s3te doctor --env <name>` | Checks local machine and AWS access before deploy. |
286
+ | `s3te deploy --env <name>` | Deploys or updates the AWS environment and syncs source files. |
287
+ | `s3te migrate` | Updates older project configs and can retrofit Webiny into an existing S3TE project. |
288
+
289
+ </details>
290
+
291
+ ### Template Commands
292
+
293
+ These are the core S3TE commands you will use even in a plain HTML-only project.
294
+
295
+ <details>
296
+ <summary><code>&lt;part&gt;</code> - reuse a partial file</summary>
297
+
298
+ ```html
299
+ <part>head.part</part>
300
+ ```
301
+
302
+ </details>
303
+
304
+ <details>
305
+ <summary><code>&lt;if&gt;</code> - render inline HTML only when a condition matches</summary>
306
+
307
+ ```html
308
+ <if>{
309
+ "env": "prod",
310
+ "template": "<meta name='robots' content='all'>"
311
+ }</if>
312
+ ```
313
+
314
+ </details>
315
+
316
+ <details>
317
+ <summary><code>&lt;fileattribute&gt;</code> - print metadata of the current output file</summary>
318
+
319
+ ```html
320
+ <fileattribute>filename</fileattribute>
321
+ ```
322
+
323
+ </details>
324
+
325
+ <details>
326
+ <summary><code>&lt;lang&gt;</code> - print the current language metadata</summary>
327
+
328
+ ```html
329
+ <html lang="<lang>2</lang>">
330
+ <link rel="canonical" href="https://<lang>baseurl</lang>">
331
+ ```
332
+
333
+ </details>
334
+
335
+ <details>
336
+ <summary><code>&lt;switchlang&gt;</code> - choose inline content by language</summary>
337
+
338
+ ```html
339
+ <switchlang>
340
+ <de>Willkommen</de>
341
+ <en>Welcome</en>
342
+ </switchlang>
343
+ ```
344
+
345
+ </details>
346
+
347
+ If you also want content-driven commands such as `dbmulti` or `dbmultifile`, continue with the optional Webiny section below. The same commands can also read from local `offline/content/*.json` files when you are not using Webiny yet.
348
+
349
+ ## Optional: Webiny CMS
350
+
351
+ You do not need Webiny to use S3TE. Start with plain HTML first. Add Webiny only when editors should maintain content in a CMS instead of editing local JSON files under `offline/content/`.
352
+
353
+ The supported target for this optional path is Webiny 6.x on its standard AWS deployment model.
354
+
355
+ ![S3TE with Webiny](https://user-images.githubusercontent.com/100029932/174443536-7af050de-eea7-4456-81aa-a173863b6ec9.png)
356
+
357
+ <details>
358
+ <summary>Install Webiny first by following the official guides</summary>
359
+
360
+ This section assumes that S3TE is already installed and deployed. The S3TE-specific Webiny setup only starts after you already have a running Webiny 6.x installation in AWS.
361
+
362
+ - [Install Webiny](https://www.webiny.com/learn/course/getting-started/installing-webiny)
363
+
364
+ </details>
365
+
366
+ <details>
367
+ <summary>Retrofit Webiny onto an existing S3TE project</summary>
368
+
369
+ 1. Install Webiny in AWS and finish the Webiny setup first.
370
+ 2. Find the Webiny DynamoDB table that contains the CMS entries you want S3TE to mirror.
371
+ 3. Upgrade your existing S3TE config for Webiny:
372
+
373
+ ```bash
374
+ npx s3te migrate --enable-webiny --webiny-source-table webiny-1234567 --webiny-tenant root --webiny-model article --write
375
+ ```
376
+
377
+ `staticContent` and `staticCodeContent` are kept automatically. Add `--webiny-model` once per custom model you want S3TE to mirror.
378
+
379
+ 4. Turn on DynamoDB Streams for the Webiny source table with `NEW_AND_OLD_IMAGES`.
380
+ 5. If your S3TE language keys are not identical to your Webiny locales, add `webinyLocale` per language in `s3te.config.json`, for example `"en": { "webinyLocale": "en-US" }`.
381
+ 6. If your Webiny installation hosts multiple tenants, keep `integrations.webiny.tenant` set so S3TE only mirrors the intended tenant.
382
+ 7. Check the project again:
383
+
384
+ ```bash
385
+ npx s3te doctor --env prod
386
+ ```
387
+
388
+ 8. Redeploy the existing S3TE environment:
389
+
390
+ ```bash
391
+ npx s3te deploy --env prod
392
+ ```
393
+
394
+ That deploy updates the existing environment stack and adds the Webiny mirror resources to it. You do not need a fresh S3TE installation.
395
+
396
+ </details>
397
+
398
+ <details>
399
+ <summary>What the migration command changes</summary>
400
+
401
+ The migration command writes or updates the `integrations.webiny` block in `s3te.config.json`. A typical result looks like this:
402
+
403
+ Example config block:
404
+
405
+ ```json
406
+ "integrations": {
407
+ "webiny": {
408
+ "enabled": true,
409
+ "sourceTableName": "webiny-1234567",
410
+ "mirrorTableName": "{stackPrefix}_s3te_content_{project}",
411
+ "tenant": "root",
412
+ "relevantModels": ["article", "staticContent", "staticCodeContent"]
413
+ }
414
+ }
415
+ ```
416
+
417
+ For localized Webiny projects, the language block can also carry the mapping explicitly:
418
+
419
+ ```json
420
+ "languages": {
421
+ "en": {
422
+ "baseUrl": "example.com",
423
+ "cloudFrontAliases": ["example.com"],
424
+ "webinyLocale": "en-US"
425
+ }
426
+ }
427
+ ```
428
+
429
+ </details>
430
+
431
+ <details>
432
+ <summary>Content template commands</summary>
433
+
434
+ These commands are useful both with Webiny and with local JSON content files:
435
+
436
+ - `dbpart`
437
+ - `dbmulti`
438
+ - `dbmultifile`
439
+ - `dbitem`
440
+ - `dbmultifileitem`
441
+
442
+ </details>
package/bin/s3te.mjs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../packages/cli/bin/s3te.mjs";
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@projectdochelp/s3te",
3
+ "version": "1.0.0",
4
+ "description": "CLI, render core, AWS adapter, and testkit for S3TemplateEngine projects",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/ProjectDocHelp/S3TemplateEngine.git"
8
+ },
9
+ "homepage": "https://github.com/ProjectDocHelp/S3TemplateEngine#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/ProjectDocHelp/S3TemplateEngine/issues"
12
+ },
13
+ "license": "MIT",
14
+ "type": "module",
15
+ "keywords": [
16
+ "s3te",
17
+ "s3templateengine",
18
+ "cli",
19
+ "aws",
20
+ "static-site"
21
+ ],
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
25
+ "bin": {
26
+ "s3te": "bin/s3te.mjs"
27
+ },
28
+ "exports": {
29
+ ".": "./src/index.mjs",
30
+ "./core": "./packages/core/src/index.mjs",
31
+ "./aws-adapter": "./packages/aws-adapter/src/index.mjs",
32
+ "./testkit": "./packages/testkit/src/index.mjs",
33
+ "./package.json": "./package.json"
34
+ },
35
+ "files": [
36
+ "bin/",
37
+ "packages/cli/bin/",
38
+ "packages/cli/src/",
39
+ "packages/core/src/",
40
+ "packages/aws-adapter/src/",
41
+ "packages/testkit/src/",
42
+ "src/",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "dependencies": {
47
+ "@aws-sdk/client-cloudfront": "^3.0.0",
48
+ "@aws-sdk/client-dynamodb": "^3.0.0",
49
+ "@aws-sdk/client-lambda": "^3.0.0",
50
+ "@aws-sdk/client-s3": "^3.0.0",
51
+ "@aws-sdk/client-sfn": "^3.0.0",
52
+ "@aws-sdk/client-ssm": "^3.0.0",
53
+ "@aws-sdk/lib-dynamodb": "^3.0.0",
54
+ "@aws-sdk/util-dynamodb": "^3.0.0"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "test": "node ./scripts/run-tests.mjs",
61
+ "cli": "node ./packages/cli/bin/s3te.mjs",
62
+ "pack:publish": "npm pack",
63
+ "pack:check": "npm pack --dry-run",
64
+ "publish:dry-run": "npm publish --access public --dry-run"
65
+ }
66
+ }
@@ -0,0 +1,102 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ import { S3teError } from "../../core/src/index.mjs";
4
+
5
+ function toCliError(message, details) {
6
+ return new S3teError("AWS_AUTH_ERROR", message, details);
7
+ }
8
+
9
+ export function runProcess(command, args, options = {}) {
10
+ return new Promise((resolve, reject) => {
11
+ const child = spawn(command, args, {
12
+ cwd: options.cwd,
13
+ env: {
14
+ ...process.env,
15
+ AWS_PAGER: "",
16
+ ...(options.env ?? {})
17
+ },
18
+ stdio: options.stdio ?? "pipe"
19
+ });
20
+
21
+ let stdout = "";
22
+ let stderr = "";
23
+
24
+ if (child.stdout) {
25
+ child.stdout.on("data", (chunk) => {
26
+ stdout += chunk.toString();
27
+ });
28
+ }
29
+
30
+ if (child.stderr) {
31
+ child.stderr.on("data", (chunk) => {
32
+ stderr += chunk.toString();
33
+ });
34
+ }
35
+
36
+ child.on("error", reject);
37
+ child.on("close", (code) => {
38
+ if (code !== 0) {
39
+ reject(new S3teError(options.errorCode ?? "ADAPTER_ERROR", `${command} ${args.join(" ")} failed.`, {
40
+ command,
41
+ args,
42
+ code,
43
+ stdout,
44
+ stderr
45
+ }));
46
+ return;
47
+ }
48
+
49
+ resolve({ code: code ?? 0, stdout, stderr });
50
+ });
51
+ });
52
+ }
53
+
54
+ export async function runAwsCli(args, options = {}) {
55
+ const finalArgs = [...args];
56
+ if (options.profile) {
57
+ finalArgs.unshift(options.profile);
58
+ finalArgs.unshift("--profile");
59
+ }
60
+
61
+ if (options.region) {
62
+ finalArgs.unshift(options.region);
63
+ finalArgs.unshift("--region");
64
+ }
65
+
66
+ return runProcess("aws", finalArgs, {
67
+ cwd: options.cwd,
68
+ env: options.env,
69
+ errorCode: options.errorCode ?? "ADAPTER_ERROR",
70
+ stdio: options.stdio
71
+ });
72
+ }
73
+
74
+ export async function ensureAwsCliAvailable(options = {}) {
75
+ try {
76
+ await runProcess("aws", ["--version"], {
77
+ cwd: options.cwd,
78
+ errorCode: "AWS_AUTH_ERROR"
79
+ });
80
+ } catch (error) {
81
+ throw toCliError("AWS CLI is not installed or not available in PATH.", { cause: error.message });
82
+ }
83
+ }
84
+
85
+ export async function ensureAwsCredentials({ region, profile, cwd }) {
86
+ try {
87
+ const result = await runAwsCli(["sts", "get-caller-identity", "--output", "json"], {
88
+ region,
89
+ profile,
90
+ cwd,
91
+ errorCode: "AWS_AUTH_ERROR"
92
+ });
93
+ return JSON.parse(result.stdout || "{}");
94
+ } catch (error) {
95
+ throw toCliError("AWS credentials are not configured or not valid.", {
96
+ region,
97
+ profile,
98
+ cause: error.message,
99
+ details: error.details
100
+ });
101
+ }
102
+ }