@nmakarov/cli-toolkit 0.27.0 → 0.32.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/README.md +38 -0
- package/dist/aws.cjs +210 -0
- package/dist/aws.cjs.map +1 -0
- package/dist/aws.js +201 -0
- package/dist/aws.js.map +1 -0
- package/dist/cli-runner.cjs +43 -11
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +43 -11
- package/dist/cli-runner.js.map +1 -1
- package/dist/deploy.cjs +1166 -0
- package/dist/deploy.cjs.map +1 -0
- package/dist/deploy.js +1096 -0
- package/dist/deploy.js.map +1 -0
- package/dist/index.cjs +1405 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1354 -29
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +19 -6
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +19 -6
- package/dist/init.js.map +1 -1
- package/dist/params.cjs +8 -4
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +8 -4
- package/dist/params.js.map +1 -1
- package/dist/s3.cjs +1 -0
- package/dist/s3.cjs.map +1 -1
- package/dist/s3.js +1 -0
- package/dist/s3.js.map +1 -1
- package/dist/tasks.cjs +25 -7
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +25 -7
- package/dist/tasks.js.map +1 -1
- package/package.json +15 -2
- package/scripts/deploy/cli.js +181 -0
package/README.md
CHANGED
|
@@ -51,6 +51,42 @@ Shipped as TypeScript sources under `scripts/ssm/` (included in the npm package)
|
|
|
51
51
|
|
|
52
52
|
From a clone of this repo: `npm run ssm:list -- --prefix /your/prefix/`, `npm run ssm:pull -- --path /your/prefix/`.
|
|
53
53
|
|
|
54
|
+
## Deploy ecosystem (`cli-deploy`)
|
|
55
|
+
|
|
56
|
+
Manifest-driven release deploys to EC2: clone on the host, serve versioned
|
|
57
|
+
releases out of `/apps/<service>/`, atomic `current` symlink switch, pm2 + nginx,
|
|
58
|
+
and rollback. The engine is project-agnostic — projects only write a manifests
|
|
59
|
+
module describing each app.
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
// deploy/services.js
|
|
63
|
+
export const services = {
|
|
64
|
+
web: {
|
|
65
|
+
name: "web",
|
|
66
|
+
appsRoot: "/apps/web",
|
|
67
|
+
repoUrl: "git@github.com:acme/web.git",
|
|
68
|
+
pm2: { script: "./src/index.js", port: 3000 },
|
|
69
|
+
nginx: { fqdn: "web.example.com" },
|
|
70
|
+
testCommand: "npm run test:ci", // optional
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# from your laptop (SSH Host alias); add --host to run remotely
|
|
77
|
+
cli-deploy setup --service=web --host=web-prod # bootstrap+init+provision+deploy
|
|
78
|
+
cli-deploy deploy --service=web --host=web-prod
|
|
79
|
+
cli-deploy rollback --service=web --host=web-prod
|
|
80
|
+
cli-deploy status --service=web --host=web-prod
|
|
81
|
+
|
|
82
|
+
# on the host (or a /tmp dry run): omit --host
|
|
83
|
+
cli-deploy deploy --service=web --appsRoot=/tmp/web --dryRun
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Programmatic API and a full field reference: `@nmakarov/cli-toolkit/deploy`
|
|
87
|
+
(`deployService`, `provisionService`, `rollbackService`, `defineService`, …).
|
|
88
|
+
Example manifest: `examples/deploy/services.js`.
|
|
89
|
+
|
|
54
90
|
## Quick Start
|
|
55
91
|
|
|
56
92
|
### Args - Parse Command Line Arguments
|
|
@@ -276,6 +312,8 @@ throw new CriticalRequestError('API endpoint unreachable');
|
|
|
276
312
|
| **Screen** | Interactive terminal UIs with React/Ink | `@nmakarov/cli-toolkit/screen` |
|
|
277
313
|
| **Logger** | Structured logging with progress tracking | `@nmakarov/cli-toolkit/logger` |
|
|
278
314
|
| **Errors** | Custom error classes | `@nmakarov/cli-toolkit/errors` |
|
|
315
|
+
| **AWS** | Discover AWS resources (VPC/subnets/Route53/…) | `@nmakarov/cli-toolkit/aws` |
|
|
316
|
+
| **Deploy** | Manifest-driven EC2 release deploys (+ `cli-deploy`) | `@nmakarov/cli-toolkit/deploy` |
|
|
279
317
|
|
|
280
318
|
## Examples
|
|
281
319
|
|
package/dist/aws.cjs
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
|
|
19
|
+
// src/aws/index.js
|
|
20
|
+
var aws_exports = {};
|
|
21
|
+
__export(aws_exports, {
|
|
22
|
+
Aws: () => Aws
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(aws_exports);
|
|
25
|
+
var import_client_sts = require("@aws-sdk/client-sts");
|
|
26
|
+
var import_client_ec2 = require("@aws-sdk/client-ec2");
|
|
27
|
+
var import_client_route_53 = require("@aws-sdk/client-route-53");
|
|
28
|
+
var DEFAULT_REGION = "us-east-1";
|
|
29
|
+
var CANONICAL_OWNER_ID = "099720109477";
|
|
30
|
+
var DEFAULT_UBUNTU_PATTERN = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*";
|
|
31
|
+
function nameTag(tags) {
|
|
32
|
+
const t = (tags ?? []).find((x) => x.Key === "Name");
|
|
33
|
+
return t?.Value ?? null;
|
|
34
|
+
}
|
|
35
|
+
var Aws = class _Aws {
|
|
36
|
+
/**
|
|
37
|
+
* Resolve credentials/region from params and return an Aws instance.
|
|
38
|
+
* Does not call AWS until you invoke a method (clients are lazy).
|
|
39
|
+
*/
|
|
40
|
+
static async init(context, options = {}) {
|
|
41
|
+
const getOpt = async (k) => {
|
|
42
|
+
try {
|
|
43
|
+
const v = await context?.params?.get?.(k, "string");
|
|
44
|
+
return v != null && String(v).trim() !== "" ? v : void 0;
|
|
45
|
+
} catch {
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const config = {
|
|
50
|
+
region: options.region ?? await getOpt("awsRegion") ?? DEFAULT_REGION,
|
|
51
|
+
accessKeyId: options.accessKeyId ?? await getOpt("awsAccessKeyId"),
|
|
52
|
+
secretAccessKey: options.secretAccessKey ?? await getOpt("awsSecretAccessKey"),
|
|
53
|
+
sessionToken: options.sessionToken ?? await getOpt("awsSessionToken")
|
|
54
|
+
};
|
|
55
|
+
const aws = new _Aws(context, config);
|
|
56
|
+
context?.logger?.debug?.(
|
|
57
|
+
`[aws] region=${config.region} credentials=${config.accessKeyId ? "explicit" : "default-chain"}`
|
|
58
|
+
);
|
|
59
|
+
return aws;
|
|
60
|
+
}
|
|
61
|
+
constructor(context, config) {
|
|
62
|
+
this.logger = context?.logger ?? console;
|
|
63
|
+
this.region = config.region || DEFAULT_REGION;
|
|
64
|
+
this._credentials = config.accessKeyId && config.secretAccessKey ? {
|
|
65
|
+
accessKeyId: config.accessKeyId,
|
|
66
|
+
secretAccessKey: config.secretAccessKey,
|
|
67
|
+
...config.sessionToken ? { sessionToken: config.sessionToken } : {}
|
|
68
|
+
} : void 0;
|
|
69
|
+
process.env.AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED ??= "true";
|
|
70
|
+
this._clients = {};
|
|
71
|
+
}
|
|
72
|
+
// ── clients (lazy, optionally region-scoped) ──────────────────────────────
|
|
73
|
+
_clientConfig(region) {
|
|
74
|
+
const cfg = { region: region || this.region };
|
|
75
|
+
if (this._credentials) cfg.credentials = this._credentials;
|
|
76
|
+
return cfg;
|
|
77
|
+
}
|
|
78
|
+
_sts() {
|
|
79
|
+
return this._clients.sts ??= new import_client_sts.STSClient(this._clientConfig());
|
|
80
|
+
}
|
|
81
|
+
_ec2(region) {
|
|
82
|
+
const key = `ec2:${region || this.region}`;
|
|
83
|
+
return this._clients[key] ??= new import_client_ec2.EC2Client(this._clientConfig(region));
|
|
84
|
+
}
|
|
85
|
+
_route53() {
|
|
86
|
+
return this._clients.route53 ??= new import_client_route_53.Route53Client(this._clientConfig());
|
|
87
|
+
}
|
|
88
|
+
getRegion() {
|
|
89
|
+
return this.region;
|
|
90
|
+
}
|
|
91
|
+
// ── identity ──────────────────────────────────────────────────────────────
|
|
92
|
+
/** { account, arn, userId } — confirm which account/identity the keys belong to. */
|
|
93
|
+
async whoAmI() {
|
|
94
|
+
const out = await this._sts().send(new import_client_sts.GetCallerIdentityCommand({}));
|
|
95
|
+
return { account: out.Account, arn: out.Arn, userId: out.UserId };
|
|
96
|
+
}
|
|
97
|
+
// ── regions / AZs ──────────────────────────────────────────────────────────
|
|
98
|
+
/** Enabled region names, sorted. */
|
|
99
|
+
async listRegions({ allRegions = false } = {}) {
|
|
100
|
+
const out = await this._ec2().send(new import_client_ec2.DescribeRegionsCommand({ AllRegions: allRegions }));
|
|
101
|
+
return (out.Regions ?? []).map((r) => r.RegionName).sort();
|
|
102
|
+
}
|
|
103
|
+
/** Availability zone names for a region (defaults to the instance region). */
|
|
104
|
+
async listAvailabilityZones(region) {
|
|
105
|
+
const out = await this._ec2(region).send(new import_client_ec2.DescribeAvailabilityZonesCommand({}));
|
|
106
|
+
return (out.AvailabilityZones ?? []).filter((z) => z.State === "available").map((z) => z.ZoneName).sort();
|
|
107
|
+
}
|
|
108
|
+
// ── VPC / subnets / SGs / key pairs ────────────────────────────────────────
|
|
109
|
+
/** [{ id, cidr, isDefault, name }] in a region. */
|
|
110
|
+
async listVpcs(region) {
|
|
111
|
+
const out = await this._ec2(region).send(new import_client_ec2.DescribeVpcsCommand({}));
|
|
112
|
+
return (out.Vpcs ?? []).map((v) => ({
|
|
113
|
+
id: v.VpcId,
|
|
114
|
+
cidr: v.CidrBlock,
|
|
115
|
+
isDefault: !!v.IsDefault,
|
|
116
|
+
name: nameTag(v.Tags)
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
/** [{ id, vpcId, az, cidr, public, name }]; pass vpcId to scope. */
|
|
120
|
+
async listSubnets({ vpcId, region } = {}) {
|
|
121
|
+
const Filters = vpcId ? [{ Name: "vpc-id", Values: [vpcId] }] : void 0;
|
|
122
|
+
const out = await this._ec2(region).send(new import_client_ec2.DescribeSubnetsCommand({ Filters }));
|
|
123
|
+
return (out.Subnets ?? []).map((s) => ({
|
|
124
|
+
id: s.SubnetId,
|
|
125
|
+
vpcId: s.VpcId,
|
|
126
|
+
az: s.AvailabilityZone,
|
|
127
|
+
cidr: s.CidrBlock,
|
|
128
|
+
public: !!s.MapPublicIpOnLaunch,
|
|
129
|
+
name: nameTag(s.Tags)
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
/** [{ id, name, vpcId, description }]; pass vpcId to scope. */
|
|
133
|
+
async listSecurityGroups({ vpcId, region } = {}) {
|
|
134
|
+
const Filters = vpcId ? [{ Name: "vpc-id", Values: [vpcId] }] : void 0;
|
|
135
|
+
const out = await this._ec2(region).send(new import_client_ec2.DescribeSecurityGroupsCommand({ Filters }));
|
|
136
|
+
return (out.SecurityGroups ?? []).map((g) => ({
|
|
137
|
+
id: g.GroupId,
|
|
138
|
+
name: g.GroupName,
|
|
139
|
+
vpcId: g.VpcId,
|
|
140
|
+
description: g.Description
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
/** [{ name, fingerprint }] EC2 key pairs in a region. */
|
|
144
|
+
async listKeyPairs(region) {
|
|
145
|
+
const out = await this._ec2(region).send(new import_client_ec2.DescribeKeyPairsCommand({}));
|
|
146
|
+
return (out.KeyPairs ?? []).map((k) => ({ name: k.KeyName, fingerprint: k.KeyFingerprint }));
|
|
147
|
+
}
|
|
148
|
+
// ── Route53 ────────────────────────────────────────────────────────────────
|
|
149
|
+
/** [{ id, name, private, recordCount }] — id is the bare zone id (no /hostedzone/). */
|
|
150
|
+
async listHostedZones() {
|
|
151
|
+
const zones = [];
|
|
152
|
+
let marker;
|
|
153
|
+
do {
|
|
154
|
+
const out = await this._route53().send(new import_client_route_53.ListHostedZonesCommand({ Marker: marker }));
|
|
155
|
+
for (const z of out.HostedZones ?? []) {
|
|
156
|
+
zones.push({
|
|
157
|
+
id: (z.Id ?? "").replace("/hostedzone/", ""),
|
|
158
|
+
name: (z.Name ?? "").replace(/\.$/, ""),
|
|
159
|
+
// strip trailing dot
|
|
160
|
+
private: !!z.Config?.PrivateZone,
|
|
161
|
+
recordCount: z.ResourceRecordSetCount
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
marker = out.IsTruncated ? out.NextMarker : void 0;
|
|
165
|
+
} while (marker);
|
|
166
|
+
return zones;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Best hosted zone for a domain: exact match, else the longest zone name that
|
|
170
|
+
* is a suffix of the domain (so "api.foo.com" matches the "foo.com" zone).
|
|
171
|
+
* Returns the zone object or null.
|
|
172
|
+
*/
|
|
173
|
+
async findHostedZoneForDomain(domain) {
|
|
174
|
+
const target = String(domain ?? "").replace(/\.$/, "").toLowerCase();
|
|
175
|
+
if (!target) return null;
|
|
176
|
+
const zones = await this.listHostedZones();
|
|
177
|
+
const exact = zones.find((z) => z.name.toLowerCase() === target);
|
|
178
|
+
if (exact) return exact;
|
|
179
|
+
return zones.filter((z) => target.endsWith(`.${z.name.toLowerCase()}`)).sort((a, b) => b.name.length - a.name.length)[0] ?? null;
|
|
180
|
+
}
|
|
181
|
+
// ── AMI lookup ──────────────────────────────────────────────────────────────
|
|
182
|
+
/**
|
|
183
|
+
* Latest Ubuntu AMI matching a name pattern in a region.
|
|
184
|
+
* Returns { id, name, creationDate, architecture } or null.
|
|
185
|
+
*/
|
|
186
|
+
async findLatestUbuntuAmi({ region, pattern = DEFAULT_UBUNTU_PATTERN, architecture = "x86_64" } = {}) {
|
|
187
|
+
const out = await this._ec2(region).send(new import_client_ec2.DescribeImagesCommand({
|
|
188
|
+
Owners: [CANONICAL_OWNER_ID],
|
|
189
|
+
Filters: [
|
|
190
|
+
{ Name: "name", Values: [pattern] },
|
|
191
|
+
{ Name: "virtualization-type", Values: ["hvm"] },
|
|
192
|
+
{ Name: "state", Values: ["available"] },
|
|
193
|
+
...architecture ? [{ Name: "architecture", Values: [architecture] }] : []
|
|
194
|
+
]
|
|
195
|
+
}));
|
|
196
|
+
const newest = (out.Images ?? []).sort((a, b) => String(b.CreationDate).localeCompare(String(a.CreationDate)))[0];
|
|
197
|
+
if (!newest) return null;
|
|
198
|
+
return {
|
|
199
|
+
id: newest.ImageId,
|
|
200
|
+
name: newest.Name,
|
|
201
|
+
creationDate: newest.CreationDate,
|
|
202
|
+
architecture: newest.Architecture
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
207
|
+
0 && (module.exports = {
|
|
208
|
+
Aws
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=aws.cjs.map
|
package/dist/aws.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/aws/index.js"],"sourcesContent":["/**\n * AWS discovery component\n *\n * A thin, read-only wrapper over a few AWS SDK v3 clients (STS, EC2, Route53)\n * with cli-toolkit conventions:\n *\n * const aws = await Aws.init(context);\n * const me = await aws.whoAmI();\n * const zones = await aws.listHostedZones();\n *\n * The whole point: given just an access key + secret (usually from .env), it can\n * *discover* the boring-to-look-up bits of an AWS account (account id, regions,\n * VPCs, subnets, security groups, key pairs, Route53 hosted zones, the latest\n * Ubuntu AMI) so scripts can pre-fill config (e.g. a terraform.tfvars) instead of\n * making you hunt through the console.\n *\n * Credentials + region are resolved via context.params, so they come from CLI,\n * env, or .env transparently:\n * awsAccessKeyId -> AWS_ACCESS_KEY_ID\n * awsSecretAccessKey -> AWS_SECRET_ACCESS_KEY\n * awsSessionToken -> AWS_SESSION_TOKEN (optional, for temp creds)\n * awsRegion -> AWS_REGION (default us-east-1)\n *\n * If no explicit keys are found, the SDK's default credential chain is used\n * (shared config/profile, instance role, etc.) — so AWS_PROFILE also works.\n *\n * Everything here is read-only (Describe / List / Get calls). It never mutates AWS.\n */\n\nimport {\n STSClient,\n GetCallerIdentityCommand,\n} from \"@aws-sdk/client-sts\";\nimport {\n EC2Client,\n DescribeRegionsCommand,\n DescribeAvailabilityZonesCommand,\n DescribeVpcsCommand,\n DescribeSubnetsCommand,\n DescribeSecurityGroupsCommand,\n DescribeKeyPairsCommand,\n DescribeImagesCommand,\n} from \"@aws-sdk/client-ec2\";\nimport {\n Route53Client,\n ListHostedZonesCommand,\n} from \"@aws-sdk/client-route-53\";\n\nconst DEFAULT_REGION = \"us-east-1\";\nconst CANONICAL_OWNER_ID = \"099720109477\"; // Ubuntu images\nconst DEFAULT_UBUNTU_PATTERN = \"ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*\";\n\nfunction nameTag(tags) {\n const t = (tags ?? []).find(x => x.Key === \"Name\");\n return t?.Value ?? null;\n}\n\nexport class Aws {\n /**\n * Resolve credentials/region from params and return an Aws instance.\n * Does not call AWS until you invoke a method (clients are lazy).\n */\n static async init(context, options = {}) {\n // Tolerate blank/missing values (e.g. an env var present but empty, like\n // a half-filled .env): treat them as \"not set\" rather than failing param\n // validation, so we fall back cleanly to the SDK default credential chain.\n const getOpt = async (k) => {\n try {\n const v = await context?.params?.get?.(k, \"string\");\n return v != null && String(v).trim() !== \"\" ? v : undefined;\n } catch {\n return undefined;\n }\n };\n\n const config = {\n region: options.region ?? (await getOpt(\"awsRegion\")) ?? DEFAULT_REGION,\n accessKeyId: options.accessKeyId ?? (await getOpt(\"awsAccessKeyId\")),\n secretAccessKey: options.secretAccessKey ?? (await getOpt(\"awsSecretAccessKey\")),\n sessionToken: options.sessionToken ?? (await getOpt(\"awsSessionToken\")),\n };\n\n const aws = new Aws(context, config);\n context?.logger?.debug?.(\n `[aws] region=${config.region} credentials=${config.accessKeyId ? \"explicit\" : \"default-chain\"}`,\n );\n return aws;\n }\n\n constructor(context, config) {\n this.logger = context?.logger ?? console;\n this.region = config.region || DEFAULT_REGION;\n\n // Build an explicit credentials object only when keys were supplied;\n // otherwise leave undefined so the SDK uses its default chain (profile,\n // instance role, env, …).\n this._credentials = config.accessKeyId && config.secretAccessKey\n ? {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n ...(config.sessionToken ? { sessionToken: config.sessionToken } : {}),\n }\n : undefined;\n\n // Quiet the AWS SDK v3 \"node >=22 required after Jan 2027\" notice.\n process.env.AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED ??= \"true\";\n\n this._clients = {};\n }\n\n // ── clients (lazy, optionally region-scoped) ──────────────────────────────\n _clientConfig(region) {\n const cfg = { region: region || this.region };\n if (this._credentials) cfg.credentials = this._credentials;\n return cfg;\n }\n\n _sts() {\n return (this._clients.sts ??= new STSClient(this._clientConfig()));\n }\n\n _ec2(region) {\n const key = `ec2:${region || this.region}`;\n return (this._clients[key] ??= new EC2Client(this._clientConfig(region)));\n }\n\n _route53() {\n // Route53 is global; region is irrelevant but the client still wants one.\n return (this._clients.route53 ??= new Route53Client(this._clientConfig()));\n }\n\n getRegion() { return this.region; }\n\n // ── identity ──────────────────────────────────────────────────────────────\n /** { account, arn, userId } — confirm which account/identity the keys belong to. */\n async whoAmI() {\n const out = await this._sts().send(new GetCallerIdentityCommand({}));\n return { account: out.Account, arn: out.Arn, userId: out.UserId };\n }\n\n // ── regions / AZs ──────────────────────────────────────────────────────────\n /** Enabled region names, sorted. */\n async listRegions({ allRegions = false } = {}) {\n const out = await this._ec2().send(new DescribeRegionsCommand({ AllRegions: allRegions }));\n return (out.Regions ?? []).map(r => r.RegionName).sort();\n }\n\n /** Availability zone names for a region (defaults to the instance region). */\n async listAvailabilityZones(region) {\n const out = await this._ec2(region).send(new DescribeAvailabilityZonesCommand({}));\n return (out.AvailabilityZones ?? [])\n .filter(z => z.State === \"available\")\n .map(z => z.ZoneName)\n .sort();\n }\n\n // ── VPC / subnets / SGs / key pairs ────────────────────────────────────────\n /** [{ id, cidr, isDefault, name }] in a region. */\n async listVpcs(region) {\n const out = await this._ec2(region).send(new DescribeVpcsCommand({}));\n return (out.Vpcs ?? []).map(v => ({\n id: v.VpcId,\n cidr: v.CidrBlock,\n isDefault: !!v.IsDefault,\n name: nameTag(v.Tags),\n }));\n }\n\n /** [{ id, vpcId, az, cidr, public, name }]; pass vpcId to scope. */\n async listSubnets({ vpcId, region } = {}) {\n const Filters = vpcId ? [{ Name: \"vpc-id\", Values: [vpcId] }] : undefined;\n const out = await this._ec2(region).send(new DescribeSubnetsCommand({ Filters }));\n return (out.Subnets ?? []).map(s => ({\n id: s.SubnetId,\n vpcId: s.VpcId,\n az: s.AvailabilityZone,\n cidr: s.CidrBlock,\n public: !!s.MapPublicIpOnLaunch,\n name: nameTag(s.Tags),\n }));\n }\n\n /** [{ id, name, vpcId, description }]; pass vpcId to scope. */\n async listSecurityGroups({ vpcId, region } = {}) {\n const Filters = vpcId ? [{ Name: \"vpc-id\", Values: [vpcId] }] : undefined;\n const out = await this._ec2(region).send(new DescribeSecurityGroupsCommand({ Filters }));\n return (out.SecurityGroups ?? []).map(g => ({\n id: g.GroupId,\n name: g.GroupName,\n vpcId: g.VpcId,\n description: g.Description,\n }));\n }\n\n /** [{ name, fingerprint }] EC2 key pairs in a region. */\n async listKeyPairs(region) {\n const out = await this._ec2(region).send(new DescribeKeyPairsCommand({}));\n return (out.KeyPairs ?? []).map(k => ({ name: k.KeyName, fingerprint: k.KeyFingerprint }));\n }\n\n // ── Route53 ────────────────────────────────────────────────────────────────\n /** [{ id, name, private, recordCount }] — id is the bare zone id (no /hostedzone/). */\n async listHostedZones() {\n const zones = [];\n let marker;\n do {\n const out = await this._route53().send(new ListHostedZonesCommand({ Marker: marker }));\n for (const z of out.HostedZones ?? []) {\n zones.push({\n id: (z.Id ?? \"\").replace(\"/hostedzone/\", \"\"),\n name: (z.Name ?? \"\").replace(/\\.$/, \"\"), // strip trailing dot\n private: !!z.Config?.PrivateZone,\n recordCount: z.ResourceRecordSetCount,\n });\n }\n marker = out.IsTruncated ? out.NextMarker : undefined;\n } while (marker);\n return zones;\n }\n\n /**\n * Best hosted zone for a domain: exact match, else the longest zone name that\n * is a suffix of the domain (so \"api.foo.com\" matches the \"foo.com\" zone).\n * Returns the zone object or null.\n */\n async findHostedZoneForDomain(domain) {\n const target = String(domain ?? \"\").replace(/\\.$/, \"\").toLowerCase();\n if (!target) return null;\n const zones = await this.listHostedZones();\n const exact = zones.find(z => z.name.toLowerCase() === target);\n if (exact) return exact;\n return zones\n .filter(z => target.endsWith(`.${z.name.toLowerCase()}`))\n .sort((a, b) => b.name.length - a.name.length)[0] ?? null;\n }\n\n // ── AMI lookup ──────────────────────────────────────────────────────────────\n /**\n * Latest Ubuntu AMI matching a name pattern in a region.\n * Returns { id, name, creationDate, architecture } or null.\n */\n async findLatestUbuntuAmi({ region, pattern = DEFAULT_UBUNTU_PATTERN, architecture = \"x86_64\" } = {}) {\n const out = await this._ec2(region).send(new DescribeImagesCommand({\n Owners: [CANONICAL_OWNER_ID],\n Filters: [\n { Name: \"name\", Values: [pattern] },\n { Name: \"virtualization-type\", Values: [\"hvm\"] },\n { Name: \"state\", Values: [\"available\"] },\n ...(architecture ? [{ Name: \"architecture\", Values: [architecture] }] : []),\n ],\n }));\n const newest = (out.Images ?? [])\n .sort((a, b) => String(b.CreationDate).localeCompare(String(a.CreationDate)))[0];\n if (!newest) return null;\n return {\n id: newest.ImageId,\n name: newest.Name,\n creationDate: newest.CreationDate,\n architecture: newest.Architecture,\n };\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/aws.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// src/aws/index.js
|
|
2
|
+
import {
|
|
3
|
+
STSClient,
|
|
4
|
+
GetCallerIdentityCommand
|
|
5
|
+
} from "@aws-sdk/client-sts";
|
|
6
|
+
import {
|
|
7
|
+
EC2Client,
|
|
8
|
+
DescribeRegionsCommand,
|
|
9
|
+
DescribeAvailabilityZonesCommand,
|
|
10
|
+
DescribeVpcsCommand,
|
|
11
|
+
DescribeSubnetsCommand,
|
|
12
|
+
DescribeSecurityGroupsCommand,
|
|
13
|
+
DescribeKeyPairsCommand,
|
|
14
|
+
DescribeImagesCommand
|
|
15
|
+
} from "@aws-sdk/client-ec2";
|
|
16
|
+
import {
|
|
17
|
+
Route53Client,
|
|
18
|
+
ListHostedZonesCommand
|
|
19
|
+
} from "@aws-sdk/client-route-53";
|
|
20
|
+
var DEFAULT_REGION = "us-east-1";
|
|
21
|
+
var CANONICAL_OWNER_ID = "099720109477";
|
|
22
|
+
var DEFAULT_UBUNTU_PATTERN = "ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*";
|
|
23
|
+
function nameTag(tags) {
|
|
24
|
+
const t = (tags ?? []).find((x) => x.Key === "Name");
|
|
25
|
+
return t?.Value ?? null;
|
|
26
|
+
}
|
|
27
|
+
var Aws = class _Aws {
|
|
28
|
+
/**
|
|
29
|
+
* Resolve credentials/region from params and return an Aws instance.
|
|
30
|
+
* Does not call AWS until you invoke a method (clients are lazy).
|
|
31
|
+
*/
|
|
32
|
+
static async init(context, options = {}) {
|
|
33
|
+
const getOpt = async (k) => {
|
|
34
|
+
try {
|
|
35
|
+
const v = await context?.params?.get?.(k, "string");
|
|
36
|
+
return v != null && String(v).trim() !== "" ? v : void 0;
|
|
37
|
+
} catch {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const config = {
|
|
42
|
+
region: options.region ?? await getOpt("awsRegion") ?? DEFAULT_REGION,
|
|
43
|
+
accessKeyId: options.accessKeyId ?? await getOpt("awsAccessKeyId"),
|
|
44
|
+
secretAccessKey: options.secretAccessKey ?? await getOpt("awsSecretAccessKey"),
|
|
45
|
+
sessionToken: options.sessionToken ?? await getOpt("awsSessionToken")
|
|
46
|
+
};
|
|
47
|
+
const aws = new _Aws(context, config);
|
|
48
|
+
context?.logger?.debug?.(
|
|
49
|
+
`[aws] region=${config.region} credentials=${config.accessKeyId ? "explicit" : "default-chain"}`
|
|
50
|
+
);
|
|
51
|
+
return aws;
|
|
52
|
+
}
|
|
53
|
+
constructor(context, config) {
|
|
54
|
+
this.logger = context?.logger ?? console;
|
|
55
|
+
this.region = config.region || DEFAULT_REGION;
|
|
56
|
+
this._credentials = config.accessKeyId && config.secretAccessKey ? {
|
|
57
|
+
accessKeyId: config.accessKeyId,
|
|
58
|
+
secretAccessKey: config.secretAccessKey,
|
|
59
|
+
...config.sessionToken ? { sessionToken: config.sessionToken } : {}
|
|
60
|
+
} : void 0;
|
|
61
|
+
process.env.AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED ??= "true";
|
|
62
|
+
this._clients = {};
|
|
63
|
+
}
|
|
64
|
+
// ── clients (lazy, optionally region-scoped) ──────────────────────────────
|
|
65
|
+
_clientConfig(region) {
|
|
66
|
+
const cfg = { region: region || this.region };
|
|
67
|
+
if (this._credentials) cfg.credentials = this._credentials;
|
|
68
|
+
return cfg;
|
|
69
|
+
}
|
|
70
|
+
_sts() {
|
|
71
|
+
return this._clients.sts ??= new STSClient(this._clientConfig());
|
|
72
|
+
}
|
|
73
|
+
_ec2(region) {
|
|
74
|
+
const key = `ec2:${region || this.region}`;
|
|
75
|
+
return this._clients[key] ??= new EC2Client(this._clientConfig(region));
|
|
76
|
+
}
|
|
77
|
+
_route53() {
|
|
78
|
+
return this._clients.route53 ??= new Route53Client(this._clientConfig());
|
|
79
|
+
}
|
|
80
|
+
getRegion() {
|
|
81
|
+
return this.region;
|
|
82
|
+
}
|
|
83
|
+
// ── identity ──────────────────────────────────────────────────────────────
|
|
84
|
+
/** { account, arn, userId } — confirm which account/identity the keys belong to. */
|
|
85
|
+
async whoAmI() {
|
|
86
|
+
const out = await this._sts().send(new GetCallerIdentityCommand({}));
|
|
87
|
+
return { account: out.Account, arn: out.Arn, userId: out.UserId };
|
|
88
|
+
}
|
|
89
|
+
// ── regions / AZs ──────────────────────────────────────────────────────────
|
|
90
|
+
/** Enabled region names, sorted. */
|
|
91
|
+
async listRegions({ allRegions = false } = {}) {
|
|
92
|
+
const out = await this._ec2().send(new DescribeRegionsCommand({ AllRegions: allRegions }));
|
|
93
|
+
return (out.Regions ?? []).map((r) => r.RegionName).sort();
|
|
94
|
+
}
|
|
95
|
+
/** Availability zone names for a region (defaults to the instance region). */
|
|
96
|
+
async listAvailabilityZones(region) {
|
|
97
|
+
const out = await this._ec2(region).send(new DescribeAvailabilityZonesCommand({}));
|
|
98
|
+
return (out.AvailabilityZones ?? []).filter((z) => z.State === "available").map((z) => z.ZoneName).sort();
|
|
99
|
+
}
|
|
100
|
+
// ── VPC / subnets / SGs / key pairs ────────────────────────────────────────
|
|
101
|
+
/** [{ id, cidr, isDefault, name }] in a region. */
|
|
102
|
+
async listVpcs(region) {
|
|
103
|
+
const out = await this._ec2(region).send(new DescribeVpcsCommand({}));
|
|
104
|
+
return (out.Vpcs ?? []).map((v) => ({
|
|
105
|
+
id: v.VpcId,
|
|
106
|
+
cidr: v.CidrBlock,
|
|
107
|
+
isDefault: !!v.IsDefault,
|
|
108
|
+
name: nameTag(v.Tags)
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
/** [{ id, vpcId, az, cidr, public, name }]; pass vpcId to scope. */
|
|
112
|
+
async listSubnets({ vpcId, region } = {}) {
|
|
113
|
+
const Filters = vpcId ? [{ Name: "vpc-id", Values: [vpcId] }] : void 0;
|
|
114
|
+
const out = await this._ec2(region).send(new DescribeSubnetsCommand({ Filters }));
|
|
115
|
+
return (out.Subnets ?? []).map((s) => ({
|
|
116
|
+
id: s.SubnetId,
|
|
117
|
+
vpcId: s.VpcId,
|
|
118
|
+
az: s.AvailabilityZone,
|
|
119
|
+
cidr: s.CidrBlock,
|
|
120
|
+
public: !!s.MapPublicIpOnLaunch,
|
|
121
|
+
name: nameTag(s.Tags)
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
/** [{ id, name, vpcId, description }]; pass vpcId to scope. */
|
|
125
|
+
async listSecurityGroups({ vpcId, region } = {}) {
|
|
126
|
+
const Filters = vpcId ? [{ Name: "vpc-id", Values: [vpcId] }] : void 0;
|
|
127
|
+
const out = await this._ec2(region).send(new DescribeSecurityGroupsCommand({ Filters }));
|
|
128
|
+
return (out.SecurityGroups ?? []).map((g) => ({
|
|
129
|
+
id: g.GroupId,
|
|
130
|
+
name: g.GroupName,
|
|
131
|
+
vpcId: g.VpcId,
|
|
132
|
+
description: g.Description
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
/** [{ name, fingerprint }] EC2 key pairs in a region. */
|
|
136
|
+
async listKeyPairs(region) {
|
|
137
|
+
const out = await this._ec2(region).send(new DescribeKeyPairsCommand({}));
|
|
138
|
+
return (out.KeyPairs ?? []).map((k) => ({ name: k.KeyName, fingerprint: k.KeyFingerprint }));
|
|
139
|
+
}
|
|
140
|
+
// ── Route53 ────────────────────────────────────────────────────────────────
|
|
141
|
+
/** [{ id, name, private, recordCount }] — id is the bare zone id (no /hostedzone/). */
|
|
142
|
+
async listHostedZones() {
|
|
143
|
+
const zones = [];
|
|
144
|
+
let marker;
|
|
145
|
+
do {
|
|
146
|
+
const out = await this._route53().send(new ListHostedZonesCommand({ Marker: marker }));
|
|
147
|
+
for (const z of out.HostedZones ?? []) {
|
|
148
|
+
zones.push({
|
|
149
|
+
id: (z.Id ?? "").replace("/hostedzone/", ""),
|
|
150
|
+
name: (z.Name ?? "").replace(/\.$/, ""),
|
|
151
|
+
// strip trailing dot
|
|
152
|
+
private: !!z.Config?.PrivateZone,
|
|
153
|
+
recordCount: z.ResourceRecordSetCount
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
marker = out.IsTruncated ? out.NextMarker : void 0;
|
|
157
|
+
} while (marker);
|
|
158
|
+
return zones;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Best hosted zone for a domain: exact match, else the longest zone name that
|
|
162
|
+
* is a suffix of the domain (so "api.foo.com" matches the "foo.com" zone).
|
|
163
|
+
* Returns the zone object or null.
|
|
164
|
+
*/
|
|
165
|
+
async findHostedZoneForDomain(domain) {
|
|
166
|
+
const target = String(domain ?? "").replace(/\.$/, "").toLowerCase();
|
|
167
|
+
if (!target) return null;
|
|
168
|
+
const zones = await this.listHostedZones();
|
|
169
|
+
const exact = zones.find((z) => z.name.toLowerCase() === target);
|
|
170
|
+
if (exact) return exact;
|
|
171
|
+
return zones.filter((z) => target.endsWith(`.${z.name.toLowerCase()}`)).sort((a, b) => b.name.length - a.name.length)[0] ?? null;
|
|
172
|
+
}
|
|
173
|
+
// ── AMI lookup ──────────────────────────────────────────────────────────────
|
|
174
|
+
/**
|
|
175
|
+
* Latest Ubuntu AMI matching a name pattern in a region.
|
|
176
|
+
* Returns { id, name, creationDate, architecture } or null.
|
|
177
|
+
*/
|
|
178
|
+
async findLatestUbuntuAmi({ region, pattern = DEFAULT_UBUNTU_PATTERN, architecture = "x86_64" } = {}) {
|
|
179
|
+
const out = await this._ec2(region).send(new DescribeImagesCommand({
|
|
180
|
+
Owners: [CANONICAL_OWNER_ID],
|
|
181
|
+
Filters: [
|
|
182
|
+
{ Name: "name", Values: [pattern] },
|
|
183
|
+
{ Name: "virtualization-type", Values: ["hvm"] },
|
|
184
|
+
{ Name: "state", Values: ["available"] },
|
|
185
|
+
...architecture ? [{ Name: "architecture", Values: [architecture] }] : []
|
|
186
|
+
]
|
|
187
|
+
}));
|
|
188
|
+
const newest = (out.Images ?? []).sort((a, b) => String(b.CreationDate).localeCompare(String(a.CreationDate)))[0];
|
|
189
|
+
if (!newest) return null;
|
|
190
|
+
return {
|
|
191
|
+
id: newest.ImageId,
|
|
192
|
+
name: newest.Name,
|
|
193
|
+
creationDate: newest.CreationDate,
|
|
194
|
+
architecture: newest.Architecture
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
export {
|
|
199
|
+
Aws
|
|
200
|
+
};
|
|
201
|
+
//# sourceMappingURL=aws.js.map
|
package/dist/aws.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/aws/index.js"],"sourcesContent":["/**\n * AWS discovery component\n *\n * A thin, read-only wrapper over a few AWS SDK v3 clients (STS, EC2, Route53)\n * with cli-toolkit conventions:\n *\n * const aws = await Aws.init(context);\n * const me = await aws.whoAmI();\n * const zones = await aws.listHostedZones();\n *\n * The whole point: given just an access key + secret (usually from .env), it can\n * *discover* the boring-to-look-up bits of an AWS account (account id, regions,\n * VPCs, subnets, security groups, key pairs, Route53 hosted zones, the latest\n * Ubuntu AMI) so scripts can pre-fill config (e.g. a terraform.tfvars) instead of\n * making you hunt through the console.\n *\n * Credentials + region are resolved via context.params, so they come from CLI,\n * env, or .env transparently:\n * awsAccessKeyId -> AWS_ACCESS_KEY_ID\n * awsSecretAccessKey -> AWS_SECRET_ACCESS_KEY\n * awsSessionToken -> AWS_SESSION_TOKEN (optional, for temp creds)\n * awsRegion -> AWS_REGION (default us-east-1)\n *\n * If no explicit keys are found, the SDK's default credential chain is used\n * (shared config/profile, instance role, etc.) — so AWS_PROFILE also works.\n *\n * Everything here is read-only (Describe / List / Get calls). It never mutates AWS.\n */\n\nimport {\n STSClient,\n GetCallerIdentityCommand,\n} from \"@aws-sdk/client-sts\";\nimport {\n EC2Client,\n DescribeRegionsCommand,\n DescribeAvailabilityZonesCommand,\n DescribeVpcsCommand,\n DescribeSubnetsCommand,\n DescribeSecurityGroupsCommand,\n DescribeKeyPairsCommand,\n DescribeImagesCommand,\n} from \"@aws-sdk/client-ec2\";\nimport {\n Route53Client,\n ListHostedZonesCommand,\n} from \"@aws-sdk/client-route-53\";\n\nconst DEFAULT_REGION = \"us-east-1\";\nconst CANONICAL_OWNER_ID = \"099720109477\"; // Ubuntu images\nconst DEFAULT_UBUNTU_PATTERN = \"ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*\";\n\nfunction nameTag(tags) {\n const t = (tags ?? []).find(x => x.Key === \"Name\");\n return t?.Value ?? null;\n}\n\nexport class Aws {\n /**\n * Resolve credentials/region from params and return an Aws instance.\n * Does not call AWS until you invoke a method (clients are lazy).\n */\n static async init(context, options = {}) {\n // Tolerate blank/missing values (e.g. an env var present but empty, like\n // a half-filled .env): treat them as \"not set\" rather than failing param\n // validation, so we fall back cleanly to the SDK default credential chain.\n const getOpt = async (k) => {\n try {\n const v = await context?.params?.get?.(k, \"string\");\n return v != null && String(v).trim() !== \"\" ? v : undefined;\n } catch {\n return undefined;\n }\n };\n\n const config = {\n region: options.region ?? (await getOpt(\"awsRegion\")) ?? DEFAULT_REGION,\n accessKeyId: options.accessKeyId ?? (await getOpt(\"awsAccessKeyId\")),\n secretAccessKey: options.secretAccessKey ?? (await getOpt(\"awsSecretAccessKey\")),\n sessionToken: options.sessionToken ?? (await getOpt(\"awsSessionToken\")),\n };\n\n const aws = new Aws(context, config);\n context?.logger?.debug?.(\n `[aws] region=${config.region} credentials=${config.accessKeyId ? \"explicit\" : \"default-chain\"}`,\n );\n return aws;\n }\n\n constructor(context, config) {\n this.logger = context?.logger ?? console;\n this.region = config.region || DEFAULT_REGION;\n\n // Build an explicit credentials object only when keys were supplied;\n // otherwise leave undefined so the SDK uses its default chain (profile,\n // instance role, env, …).\n this._credentials = config.accessKeyId && config.secretAccessKey\n ? {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n ...(config.sessionToken ? { sessionToken: config.sessionToken } : {}),\n }\n : undefined;\n\n // Quiet the AWS SDK v3 \"node >=22 required after Jan 2027\" notice.\n process.env.AWS_SDK_JS_NODE_VERSION_SUPPORT_WARNING_DISABLED ??= \"true\";\n\n this._clients = {};\n }\n\n // ── clients (lazy, optionally region-scoped) ──────────────────────────────\n _clientConfig(region) {\n const cfg = { region: region || this.region };\n if (this._credentials) cfg.credentials = this._credentials;\n return cfg;\n }\n\n _sts() {\n return (this._clients.sts ??= new STSClient(this._clientConfig()));\n }\n\n _ec2(region) {\n const key = `ec2:${region || this.region}`;\n return (this._clients[key] ??= new EC2Client(this._clientConfig(region)));\n }\n\n _route53() {\n // Route53 is global; region is irrelevant but the client still wants one.\n return (this._clients.route53 ??= new Route53Client(this._clientConfig()));\n }\n\n getRegion() { return this.region; }\n\n // ── identity ──────────────────────────────────────────────────────────────\n /** { account, arn, userId } — confirm which account/identity the keys belong to. */\n async whoAmI() {\n const out = await this._sts().send(new GetCallerIdentityCommand({}));\n return { account: out.Account, arn: out.Arn, userId: out.UserId };\n }\n\n // ── regions / AZs ──────────────────────────────────────────────────────────\n /** Enabled region names, sorted. */\n async listRegions({ allRegions = false } = {}) {\n const out = await this._ec2().send(new DescribeRegionsCommand({ AllRegions: allRegions }));\n return (out.Regions ?? []).map(r => r.RegionName).sort();\n }\n\n /** Availability zone names for a region (defaults to the instance region). */\n async listAvailabilityZones(region) {\n const out = await this._ec2(region).send(new DescribeAvailabilityZonesCommand({}));\n return (out.AvailabilityZones ?? [])\n .filter(z => z.State === \"available\")\n .map(z => z.ZoneName)\n .sort();\n }\n\n // ── VPC / subnets / SGs / key pairs ────────────────────────────────────────\n /** [{ id, cidr, isDefault, name }] in a region. */\n async listVpcs(region) {\n const out = await this._ec2(region).send(new DescribeVpcsCommand({}));\n return (out.Vpcs ?? []).map(v => ({\n id: v.VpcId,\n cidr: v.CidrBlock,\n isDefault: !!v.IsDefault,\n name: nameTag(v.Tags),\n }));\n }\n\n /** [{ id, vpcId, az, cidr, public, name }]; pass vpcId to scope. */\n async listSubnets({ vpcId, region } = {}) {\n const Filters = vpcId ? [{ Name: \"vpc-id\", Values: [vpcId] }] : undefined;\n const out = await this._ec2(region).send(new DescribeSubnetsCommand({ Filters }));\n return (out.Subnets ?? []).map(s => ({\n id: s.SubnetId,\n vpcId: s.VpcId,\n az: s.AvailabilityZone,\n cidr: s.CidrBlock,\n public: !!s.MapPublicIpOnLaunch,\n name: nameTag(s.Tags),\n }));\n }\n\n /** [{ id, name, vpcId, description }]; pass vpcId to scope. */\n async listSecurityGroups({ vpcId, region } = {}) {\n const Filters = vpcId ? [{ Name: \"vpc-id\", Values: [vpcId] }] : undefined;\n const out = await this._ec2(region).send(new DescribeSecurityGroupsCommand({ Filters }));\n return (out.SecurityGroups ?? []).map(g => ({\n id: g.GroupId,\n name: g.GroupName,\n vpcId: g.VpcId,\n description: g.Description,\n }));\n }\n\n /** [{ name, fingerprint }] EC2 key pairs in a region. */\n async listKeyPairs(region) {\n const out = await this._ec2(region).send(new DescribeKeyPairsCommand({}));\n return (out.KeyPairs ?? []).map(k => ({ name: k.KeyName, fingerprint: k.KeyFingerprint }));\n }\n\n // ── Route53 ────────────────────────────────────────────────────────────────\n /** [{ id, name, private, recordCount }] — id is the bare zone id (no /hostedzone/). */\n async listHostedZones() {\n const zones = [];\n let marker;\n do {\n const out = await this._route53().send(new ListHostedZonesCommand({ Marker: marker }));\n for (const z of out.HostedZones ?? []) {\n zones.push({\n id: (z.Id ?? \"\").replace(\"/hostedzone/\", \"\"),\n name: (z.Name ?? \"\").replace(/\\.$/, \"\"), // strip trailing dot\n private: !!z.Config?.PrivateZone,\n recordCount: z.ResourceRecordSetCount,\n });\n }\n marker = out.IsTruncated ? out.NextMarker : undefined;\n } while (marker);\n return zones;\n }\n\n /**\n * Best hosted zone for a domain: exact match, else the longest zone name that\n * is a suffix of the domain (so \"api.foo.com\" matches the \"foo.com\" zone).\n * Returns the zone object or null.\n */\n async findHostedZoneForDomain(domain) {\n const target = String(domain ?? \"\").replace(/\\.$/, \"\").toLowerCase();\n if (!target) return null;\n const zones = await this.listHostedZones();\n const exact = zones.find(z => z.name.toLowerCase() === target);\n if (exact) return exact;\n return zones\n .filter(z => target.endsWith(`.${z.name.toLowerCase()}`))\n .sort((a, b) => b.name.length - a.name.length)[0] ?? null;\n }\n\n // ── AMI lookup ──────────────────────────────────────────────────────────────\n /**\n * Latest Ubuntu AMI matching a name pattern in a region.\n * Returns { id, name, creationDate, architecture } or null.\n */\n async findLatestUbuntuAmi({ region, pattern = DEFAULT_UBUNTU_PATTERN, architecture = \"x86_64\" } = {}) {\n const out = await this._ec2(region).send(new DescribeImagesCommand({\n Owners: [CANONICAL_OWNER_ID],\n Filters: [\n { Name: \"name\", Values: [pattern] },\n { Name: \"virtualization-type\", Values: [\"hvm\"] },\n { Name: \"state\", Values: [\"available\"] },\n ...(architecture ? [{ Name: \"architecture\", Values: [architecture] }] : []),\n ],\n }));\n const newest = (out.Images ?? [])\n .sort((a, b) => String(b.CreationDate).localeCompare(String(a.CreationDate)))[0];\n if (!newest) return null;\n return {\n id: newest.ImageId,\n name: newest.Name,\n creationDate: newest.CreationDate,\n architecture: newest.Architecture,\n };\n }\n}\n"],"mappings":"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","names":[]}
|