cloud-doctor 0.0.1-alpha.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 ADDED
@@ -0,0 +1,26 @@
1
+ # cloud-doctor
2
+
3
+ **Alpha** — early scaffold. One command grades your whole cloud account.
4
+
5
+ ```bash
6
+ npx cloud-doctor@alpha
7
+ npx cloud-doctor@alpha aws --profile prod
8
+ ```
9
+
10
+ ## Status
11
+
12
+ This is an **alpha name-reservation release**. The CLI shell works (provider picker, AWS profile discovery, identity validation). Security rules and scoring land in upcoming releases.
13
+
14
+ ## Commands
15
+
16
+ ```bash
17
+ cloud-doctor # interactive: pick provider → AWS profile → scan
18
+ cloud-doctor aws # grade AWS account
19
+ cloud-doctor aws profiles # list ~/.aws/config profiles
20
+ cloud-doctor aws whoami # show resolved AWS identity
21
+ cloud-doctor aws --yes --json # CI-friendly JSON output
22
+ ```
23
+
24
+ ## License
25
+
26
+ MIT
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ import module from "node:module";
4
+
5
+ if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
6
+ try {
7
+ module.enableCompileCache();
8
+ } catch {
9
+ // Ignore compile-cache errors.
10
+ }
11
+ }
12
+
13
+ await import("../dist/cli/index.js");
@@ -0,0 +1,586 @@
1
+ // ../core/dist/index.js
2
+ import pc from "picocolors";
3
+ import { createJiti } from "jiti";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ import { parseJSONC } from "confbox";
7
+ var PERFECT_SCORE = 100;
8
+ var SCORE_GOOD_THRESHOLD = 75;
9
+ var SCORE_OK_THRESHOLD = 50;
10
+ var TOP_FIXES_DISPLAY_COUNT = 5;
11
+ var ERROR_RULE_PENALTY = 1.5;
12
+ var WARNING_RULE_PENALTY = 0.75;
13
+ var DIAGNOSTIC_CATEGORY_BUCKETS = [
14
+ "Security",
15
+ "Misconfiguration",
16
+ "Observability",
17
+ "Reliability",
18
+ "Governance"
19
+ ];
20
+ var getScoreLabel = (score) => {
21
+ if (score >= SCORE_GOOD_THRESHOLD) return "Great";
22
+ if (score >= SCORE_OK_THRESHOLD) return "Needs work";
23
+ return "Critical";
24
+ };
25
+ var calculateScore = (diagnostics) => {
26
+ if (diagnostics.length === 0) {
27
+ return { score: PERFECT_SCORE, label: getScoreLabel(PERFECT_SCORE) };
28
+ }
29
+ const errorRules = /* @__PURE__ */ new Set();
30
+ const warningRules = /* @__PURE__ */ new Set();
31
+ for (const diagnostic of diagnostics) {
32
+ const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
33
+ if (diagnostic.severity === "error") {
34
+ errorRules.add(ruleKey);
35
+ } else {
36
+ warningRules.add(ruleKey);
37
+ }
38
+ }
39
+ const penalty = errorRules.size * ERROR_RULE_PENALTY + warningRules.size * WARNING_RULE_PENALTY;
40
+ const score = Math.max(0, Math.round(PERFECT_SCORE - penalty));
41
+ return { score, label: getScoreLabel(score) };
42
+ };
43
+ var highlighter = {
44
+ error: pc.red,
45
+ warn: pc.yellow,
46
+ info: pc.cyan,
47
+ success: pc.green,
48
+ dim: pc.dim,
49
+ gray: pc.gray,
50
+ bold: pc.bold
51
+ };
52
+ var setColorEnabled = (enabled) => {
53
+ const colors = pc.createColors(enabled);
54
+ highlighter.error = colors.red;
55
+ highlighter.warn = colors.yellow;
56
+ highlighter.info = colors.cyan;
57
+ highlighter.success = colors.green;
58
+ highlighter.dim = colors.dim;
59
+ highlighter.gray = colors.gray;
60
+ highlighter.bold = colors.bold;
61
+ };
62
+ var CONFIG_FILENAMES = [
63
+ "doctor.config.ts",
64
+ "doctor.config.mts",
65
+ "doctor.config.js",
66
+ "doctor.config.mjs",
67
+ "doctor.config.json",
68
+ "doctor.config.jsonc"
69
+ ];
70
+ var findConfigFile = (startDir) => {
71
+ let current = path.resolve(startDir);
72
+ const root = path.parse(current).root;
73
+ while (true) {
74
+ for (const filename of CONFIG_FILENAMES) {
75
+ const candidate = path.join(current, filename);
76
+ if (fs.existsSync(candidate)) return candidate;
77
+ }
78
+ const packageJsonPath = path.join(current, "package.json");
79
+ if (fs.existsSync(packageJsonPath)) {
80
+ try {
81
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
82
+ if (packageJson.cloudDoctor) {
83
+ return packageJsonPath;
84
+ }
85
+ } catch {
86
+ }
87
+ }
88
+ if (current === root) break;
89
+ current = path.dirname(current);
90
+ }
91
+ return null;
92
+ };
93
+ var loadConfigModule = async (configPath) => {
94
+ const ext = path.extname(configPath);
95
+ if (ext === ".json" || ext === ".jsonc") {
96
+ return parseJSONC(fs.readFileSync(configPath, "utf8"));
97
+ }
98
+ if (configPath.endsWith("package.json")) {
99
+ const packageJson = JSON.parse(fs.readFileSync(configPath, "utf8"));
100
+ return packageJson.cloudDoctor ?? {};
101
+ }
102
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
103
+ const loaded = await jiti.import(configPath);
104
+ const config = loaded.default ?? loaded;
105
+ return config;
106
+ };
107
+ var loadConfig = async (cwd = process.cwd()) => {
108
+ const configPath = findConfigFile(cwd);
109
+ if (!configPath) return {};
110
+ return loadConfigModule(configPath);
111
+ };
112
+ var defineConfig = (config) => config;
113
+ var PluginRegistry = class {
114
+ plugins = /* @__PURE__ */ new Map();
115
+ register(plugin) {
116
+ this.plugins.set(plugin.id, plugin);
117
+ }
118
+ get(id) {
119
+ return this.plugins.get(id);
120
+ }
121
+ require(id) {
122
+ const plugin = this.plugins.get(id);
123
+ if (!plugin) {
124
+ throw new Error(`Unknown provider: ${id}`);
125
+ }
126
+ return plugin;
127
+ }
128
+ list() {
129
+ return [...this.plugins.values()];
130
+ }
131
+ listAvailable() {
132
+ return this.list().filter((plugin) => plugin.status !== "coming-soon");
133
+ }
134
+ };
135
+ var createPluginRegistry = () => new PluginRegistry();
136
+ var buildCategoryCounts = (diagnostics) => {
137
+ const counts = {};
138
+ for (const diagnostic of diagnostics) {
139
+ const bucket = counts[diagnostic.category] ?? { errors: 0, warnings: 0 };
140
+ if (diagnostic.severity === "error") {
141
+ bucket.errors += 1;
142
+ } else {
143
+ bucket.warnings += 1;
144
+ }
145
+ counts[diagnostic.category] = bucket;
146
+ }
147
+ return counts;
148
+ };
149
+ var buildScanSummary = (diagnostics) => {
150
+ const { score, label } = calculateScore(diagnostics);
151
+ const errorCount = diagnostics.filter((d) => d.severity === "error").length;
152
+ const warningCount = diagnostics.filter((d) => d.severity === "warning").length;
153
+ return {
154
+ score,
155
+ label,
156
+ errorCount,
157
+ warningCount,
158
+ categoryCounts: buildCategoryCounts(diagnostics)
159
+ };
160
+ };
161
+ var computeProjectedScore = (diagnostics, topRuleCount = TOP_FIXES_DISPLAY_COUNT) => {
162
+ const errorRuleKeys = /* @__PURE__ */ new Set();
163
+ const orderedErrorRules = [];
164
+ for (const diagnostic of diagnostics) {
165
+ if (diagnostic.severity !== "error") continue;
166
+ const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
167
+ if (!errorRuleKeys.has(ruleKey)) {
168
+ errorRuleKeys.add(ruleKey);
169
+ orderedErrorRules.push(ruleKey);
170
+ }
171
+ }
172
+ if (orderedErrorRules.length === 0) return void 0;
173
+ const rulesToRemove = new Set(orderedErrorRules.slice(0, topRuleCount));
174
+ const remaining = diagnostics.filter(
175
+ (d) => !rulesToRemove.has(`${d.plugin}/${d.rule}`)
176
+ );
177
+ return calculateScore(remaining).score;
178
+ };
179
+ var buildJsonReport = (input) => {
180
+ const summary = buildScanSummary(input.diagnostics);
181
+ const projectedScore = computeProjectedScore(input.diagnostics);
182
+ if (projectedScore !== void 0) {
183
+ summary.projectedScore = projectedScore;
184
+ }
185
+ return {
186
+ schemaVersion: 1,
187
+ provider: input.provider,
188
+ identity: input.identity,
189
+ summary,
190
+ diagnostics: input.diagnostics,
191
+ meta: input.meta
192
+ };
193
+ };
194
+ var buildJsonReportError = (input) => ({
195
+ schemaVersion: 1,
196
+ error: input.error instanceof Error ? input.error.message : String(input.error),
197
+ elapsedMilliseconds: input.elapsedMilliseconds
198
+ });
199
+ var groupDiagnosticsByRule = (diagnostics) => {
200
+ const groups = /* @__PURE__ */ new Map();
201
+ for (const diagnostic of diagnostics) {
202
+ const key = `${diagnostic.plugin}/${diagnostic.rule}`;
203
+ const existing = groups.get(key) ?? [];
204
+ existing.push(diagnostic);
205
+ groups.set(key, existing);
206
+ }
207
+ return groups;
208
+ };
209
+ var getTopErrorRules = (diagnostics, limit = TOP_FIXES_DISPLAY_COUNT) => {
210
+ const errorDiagnostics = diagnostics.filter((d) => d.severity === "error");
211
+ const groups = groupDiagnosticsByRule(errorDiagnostics);
212
+ return [...groups.entries()].slice(0, limit).map(([ruleKey, ruleDiagnostics]) => ({ ruleKey, diagnostics: ruleDiagnostics }));
213
+ };
214
+ var sortCategoriesForDisplay = (categories) => {
215
+ const rank = new Map(DIAGNOSTIC_CATEGORY_BUCKETS.map((category, index) => [category, index]));
216
+ return [...categories].sort((a, b) => {
217
+ const rankA = rank.get(a);
218
+ const rankB = rank.get(b);
219
+ if (rankA !== void 0 && rankB !== void 0) return rankA - rankB;
220
+ if (rankA !== void 0) return -1;
221
+ if (rankB !== void 0) return 1;
222
+ return a.localeCompare(b);
223
+ });
224
+ };
225
+
226
+ // ../plugin-aws/dist/index.js
227
+ import * as os from "os";
228
+ import * as path2 from "path";
229
+ import { loadSharedConfigFiles } from "@smithy/shared-ini-file-loader";
230
+ import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
231
+ import { fromEnv, fromIni, fromInstanceMetadata, fromTokenFile } from "@aws-sdk/credential-providers";
232
+ var profileNameFromSection = (sectionName) => {
233
+ if (sectionName === "default") return "default";
234
+ if (sectionName.startsWith("profile ")) return sectionName.slice("profile ".length);
235
+ if (!sectionName.includes(" ")) return sectionName;
236
+ return null;
237
+ };
238
+ var classifyProfile = (profileName, configSection, hasStaticCredentials) => {
239
+ if (configSection["sso_start_url"] || configSection["sso_session"]) return "sso";
240
+ if (configSection["role_arn"]) return "assume-role";
241
+ if (configSection["web_identity_token_file"]) return "web-identity";
242
+ if (hasStaticCredentials) return "static";
243
+ if (profileName === "default" && hasStaticCredentials) return "static";
244
+ return "unknown";
245
+ };
246
+ var getAwsConfigPaths = () => {
247
+ const home = os.homedir();
248
+ return {
249
+ configFile: process.env.AWS_CONFIG_FILE ?? path2.join(home, ".aws", "config"),
250
+ credentialsFile: process.env.AWS_SHARED_CREDENTIALS_FILE ?? path2.join(home, ".aws", "credentials")
251
+ };
252
+ };
253
+ var listAwsProfiles = async () => {
254
+ const { configFile, credentialsFile } = getAwsConfigPaths();
255
+ const { configFile: configSections, credentialsFile: credentialSections } = await loadSharedConfigFiles({
256
+ configFilepath: configFile,
257
+ filepath: credentialsFile
258
+ });
259
+ const profileNames = /* @__PURE__ */ new Set();
260
+ for (const sectionName of Object.keys(configSections)) {
261
+ const profileName = profileNameFromSection(sectionName);
262
+ if (profileName) profileNames.add(profileName);
263
+ }
264
+ for (const sectionName of Object.keys(credentialSections)) {
265
+ const profileName = profileNameFromSection(sectionName);
266
+ if (profileName) profileNames.add(profileName);
267
+ }
268
+ if (profileNames.size === 0 && process.env.AWS_ACCESS_KEY_ID) {
269
+ return [
270
+ {
271
+ name: "(environment)",
272
+ kind: "unknown"
273
+ }
274
+ ];
275
+ }
276
+ const profiles = [];
277
+ for (const profileName of [...profileNames].sort((a, b) => {
278
+ if (a === "default") return -1;
279
+ if (b === "default") return 1;
280
+ return a.localeCompare(b);
281
+ })) {
282
+ const configSection = configSections[`profile ${profileName}`] ?? configSections[profileName] ?? {};
283
+ const credentialSection = credentialSections[profileName] ?? {};
284
+ const hasStaticCredentials = Boolean(
285
+ credentialSection.aws_access_key_id && credentialSection.aws_secret_access_key
286
+ );
287
+ profiles.push({
288
+ name: profileName,
289
+ region: configSection.region,
290
+ kind: classifyProfile(profileName, configSection, hasStaticCredentials),
291
+ ssoSessionName: configSection.sso_session,
292
+ roleArn: configSection.role_arn,
293
+ sourceProfile: configSection.source_profile
294
+ });
295
+ }
296
+ return profiles;
297
+ };
298
+ var resolveDefaultProfileName = (profiles) => {
299
+ if (process.env.AWS_PROFILE) return process.env.AWS_PROFILE;
300
+ if (profiles.some((profile) => profile.name === "default")) return "default";
301
+ if (profiles.length === 1 && profiles[0]?.name !== "(environment)") {
302
+ return profiles[0]?.name;
303
+ }
304
+ return void 0;
305
+ };
306
+ var hasEnvironmentCredentials = () => Boolean(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY);
307
+ var hasWebIdentityCredentials = () => Boolean(process.env.AWS_WEB_IDENTITY_TOKEN_FILE && process.env.AWS_ROLE_ARN);
308
+ var kindFromProfile = (profile) => {
309
+ switch (profile.kind) {
310
+ case "sso":
311
+ return "sso";
312
+ case "assume-role":
313
+ return "assume-role";
314
+ case "web-identity":
315
+ return "web-identity";
316
+ case "static":
317
+ return "profile";
318
+ default:
319
+ return "profile";
320
+ }
321
+ };
322
+ var createCredentialProvider = (input) => {
323
+ if (input.manualAccessKeyId && input.manualSecretAccessKey) {
324
+ return async () => ({
325
+ accessKeyId: input.manualAccessKeyId,
326
+ secretAccessKey: input.manualSecretAccessKey,
327
+ sessionToken: input.manualSessionToken
328
+ });
329
+ }
330
+ if (input.useEnvironment || !input.profileName && process.env.AWS_ACCESS_KEY_ID) {
331
+ return fromEnv();
332
+ }
333
+ if (!input.profileName && process.env.AWS_WEB_IDENTITY_TOKEN_FILE) {
334
+ return fromTokenFile({
335
+ roleArn: process.env.AWS_ROLE_ARN,
336
+ webIdentityTokenFile: process.env.AWS_WEB_IDENTITY_TOKEN_FILE
337
+ });
338
+ }
339
+ if (input.profileName) {
340
+ return fromIni({ profile: input.profileName });
341
+ }
342
+ return async () => {
343
+ try {
344
+ return await fromEnv()();
345
+ } catch {
346
+ try {
347
+ return await fromIni()();
348
+ } catch {
349
+ return fromInstanceMetadata()();
350
+ }
351
+ }
352
+ };
353
+ };
354
+ var validateAwsCredentials = async (input) => {
355
+ const credentials = createCredentialProvider({
356
+ profileName: input.profileName,
357
+ region: input.region,
358
+ useEnvironment: input.useEnvironment,
359
+ manualAccessKeyId: input.manualAccessKeyId,
360
+ manualSecretAccessKey: input.manualSecretAccessKey,
361
+ manualSessionToken: input.manualSessionToken
362
+ });
363
+ const client = new STSClient({
364
+ credentials,
365
+ region: input.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1"
366
+ });
367
+ try {
368
+ const response = await client.send(new GetCallerIdentityCommand({}));
369
+ return {
370
+ provider: "aws",
371
+ id: response.Account ?? "unknown",
372
+ label: input.label,
373
+ kind: input.kind,
374
+ profileName: input.profileName,
375
+ region: input.region,
376
+ accountId: response.Account,
377
+ arn: response.Arn
378
+ };
379
+ } catch (error) {
380
+ const message = error instanceof Error ? error.message : String(error);
381
+ if (message.includes("Token has expired") || message.includes("sso")) {
382
+ throw new SsoLoginRequiredError(input.profileName ?? "default");
383
+ }
384
+ throw error;
385
+ } finally {
386
+ client.destroy();
387
+ }
388
+ };
389
+ var SsoLoginRequiredError = class extends Error {
390
+ profileName;
391
+ constructor(profileName) {
392
+ super(`Profile "${profileName}" requires SSO login. Run: aws sso login --profile ${profileName}`);
393
+ this.name = "SsoLoginRequiredError";
394
+ this.profileName = profileName;
395
+ }
396
+ };
397
+ var profileToIdentityKind = kindFromProfile;
398
+ var runAwsRules = async (identity, _config) => {
399
+ void identity;
400
+ return [];
401
+ };
402
+ var formatProfileLabel = (profileName, region) => {
403
+ const regionSuffix = region ? ` \xB7 ${region}` : "";
404
+ return `${profileName}${regionSuffix}`;
405
+ };
406
+ var buildProfileSummaries = async () => {
407
+ const profiles = await listAwsProfiles();
408
+ const summaries = [];
409
+ for (const profile of profiles) {
410
+ if (profile.name === "(environment)") continue;
411
+ summaries.push({
412
+ id: profile.name,
413
+ label: formatProfileLabel(profile.name, profile.region),
414
+ kind: profileToIdentityKind(profile),
415
+ profileName: profile.name,
416
+ defaultRegion: profile.region,
417
+ status: "unknown",
418
+ hint: profile.kind === "sso" ? "SSO profile \u2014 will validate on selection" : profile.kind === "assume-role" ? `Assumes ${profile.roleArn ?? "role"}` : void 0
419
+ });
420
+ }
421
+ if (hasEnvironmentCredentials()) {
422
+ summaries.unshift({
423
+ id: "__environment__",
424
+ label: "(environment credentials)",
425
+ kind: "environment",
426
+ status: "unknown",
427
+ hint: "AWS_ACCESS_KEY_ID is set"
428
+ });
429
+ }
430
+ if (hasWebIdentityCredentials()) {
431
+ summaries.unshift({
432
+ id: "__web_identity__",
433
+ label: "(web identity / OIDC)",
434
+ kind: "web-identity",
435
+ status: "unknown",
436
+ hint: process.env.AWS_ROLE_ARN
437
+ });
438
+ }
439
+ return summaries;
440
+ };
441
+ var resolveProfileInput = (input, configProfile) => {
442
+ if (input.useEnvironment) return { useEnvironment: true };
443
+ if (input.profile) return { profileName: input.profile };
444
+ if (configProfile) return { profileName: configProfile };
445
+ if (process.env.AWS_PROFILE) return { profileName: process.env.AWS_PROFILE };
446
+ const envSelected = input.manualAccessKeyId ? void 0 : hasEnvironmentCredentials();
447
+ if (envSelected && !input.profile) return { useEnvironment: true };
448
+ return {};
449
+ };
450
+ var awsPlugin = {
451
+ id: "aws",
452
+ displayName: "AWS",
453
+ description: "Amazon Web Services account",
454
+ status: "stable",
455
+ async discoverIdentities(_ctx) {
456
+ return buildProfileSummaries();
457
+ },
458
+ async resolveIdentity(ctx, input) {
459
+ const profiles = await listAwsProfiles();
460
+ const resolution = resolveProfileInput(input, void 0);
461
+ if (resolution.useEnvironment) {
462
+ return {
463
+ provider: "aws",
464
+ id: "__environment__",
465
+ label: "(environment credentials)",
466
+ kind: "environment",
467
+ region: input.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION
468
+ };
469
+ }
470
+ if (input.manualAccessKeyId && input.manualSecretAccessKey) {
471
+ return {
472
+ provider: "aws",
473
+ id: "__manual__",
474
+ label: "(manual credentials)",
475
+ kind: "manual",
476
+ region: input.region
477
+ };
478
+ }
479
+ const profileName = resolution.profileName ?? resolveDefaultProfileName(profiles) ?? profiles[0]?.name;
480
+ if (!profileName) {
481
+ if (ctx.yes || ctx.json) {
482
+ throw new Error(
483
+ "No AWS profiles found. Configure ~/.aws/config or pass --profile, set AWS_PROFILE, or use environment credentials."
484
+ );
485
+ }
486
+ throw new Error("No AWS profiles found");
487
+ }
488
+ const profile = profiles.find((entry) => entry.name === profileName);
489
+ return {
490
+ provider: "aws",
491
+ id: profileName,
492
+ label: formatProfileLabel(profileName, profile?.region ?? input.region),
493
+ kind: profile ? profileToIdentityKind(profile) : "profile",
494
+ profileName,
495
+ region: input.region ?? profile?.region
496
+ };
497
+ },
498
+ async validateIdentity(identity) {
499
+ if (identity.kind === "manual") {
500
+ throw new Error("Manual credentials must be validated via validateAwsCredentials before scan.");
501
+ }
502
+ return validateAwsCredentials({
503
+ profileName: identity.profileName,
504
+ region: identity.region,
505
+ label: identity.label,
506
+ kind: identity.kind,
507
+ useEnvironment: identity.kind === "environment"
508
+ });
509
+ },
510
+ async scan(identity, config) {
511
+ const start = performance.now();
512
+ const diagnostics = await runAwsRules(identity, config);
513
+ const summary = buildScanSummary(diagnostics);
514
+ return {
515
+ provider: "aws",
516
+ identity,
517
+ diagnostics,
518
+ summary,
519
+ meta: {
520
+ durationMs: Math.round(performance.now() - start),
521
+ regionsScanned: config.regions
522
+ }
523
+ };
524
+ }
525
+ };
526
+
527
+ // ../plugins/dist/index.js
528
+ var comingSoon = (id, displayName, description) => ({
529
+ id,
530
+ displayName,
531
+ description,
532
+ status: "coming-soon",
533
+ async discoverIdentities() {
534
+ return [];
535
+ },
536
+ async resolveIdentity() {
537
+ throw new Error(`${displayName} support is coming soon. Use "aws" for now.`);
538
+ },
539
+ async validateIdentity(identity) {
540
+ return identity;
541
+ },
542
+ async scan() {
543
+ throw new Error(`${displayName} support is coming soon.`);
544
+ }
545
+ });
546
+ var gcloudPlugin = comingSoon("gcloud", "GCloud", "Google Cloud project");
547
+ var k8sPlugin = comingSoon("k8s", "Kubernetes", "Cluster workloads");
548
+ var dbPlugin = comingSoon("db", "Database", "Postgres, MySQL, Redis, and more");
549
+ var cloudflarePlugin = comingSoon("cloudflare", "Cloudflare", "DNS, WAF, Workers");
550
+ var cachedRegistry = null;
551
+ var createDefaultRegistry = () => {
552
+ const registry = createPluginRegistry();
553
+ registry.register(awsPlugin);
554
+ registry.register(gcloudPlugin);
555
+ registry.register(k8sPlugin);
556
+ registry.register(dbPlugin);
557
+ registry.register(cloudflarePlugin);
558
+ return registry;
559
+ };
560
+ var getDefaultRegistry = () => {
561
+ if (!cachedRegistry) {
562
+ cachedRegistry = createDefaultRegistry();
563
+ }
564
+ return cachedRegistry;
565
+ };
566
+ var PROVIDER_IDS = ["aws", "gcloud", "k8s", "db", "cloudflare"];
567
+ var isProviderId = (value) => PROVIDER_IDS.includes(value);
568
+
569
+ export {
570
+ TOP_FIXES_DISPLAY_COUNT,
571
+ highlighter,
572
+ setColorEnabled,
573
+ loadConfig,
574
+ defineConfig,
575
+ computeProjectedScore,
576
+ buildJsonReport,
577
+ buildJsonReportError,
578
+ getTopErrorRules,
579
+ sortCategoriesForDisplay,
580
+ listAwsProfiles,
581
+ validateAwsCredentials,
582
+ SsoLoginRequiredError,
583
+ getDefaultRegistry,
584
+ isProviderId
585
+ };
586
+ //# sourceMappingURL=chunk-4PZ65VRA.js.map