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 +26 -0
- package/bin/cloud-doctor.js +13 -0
- package/dist/chunk-4PZ65VRA.js +586 -0
- package/dist/chunk-4PZ65VRA.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +612 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/utils/cli-logger.ts","../../src/cli/commands/aws-meta.ts","../../src/cli/commands/scan.ts","../../src/cli/utils/prompts.ts","../../src/cli/utils/unref-stdin.ts","../../src/cli/utils/is-non-interactive-environment.ts","../../src/cli/utils/should-skip-prompts.ts","../../src/cli/resolve-identity.ts","../../src/cli/resolve-provider.ts","../../src/cli/render-scan-report.ts","../../src/cli/utils/json-mode.ts","../../src/cli/utils/apply-color-preference.ts","../../src/cli/utils/version.ts","../../src/cli/utils/exit-gracefully.ts","../../src/cli/utils/guard-stdin.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { highlighter } from \"@cloud-doctor/core\";\nimport { awsProfilesAction, awsWhoamiAction } from \"./commands/aws-meta.js\";\nimport { scanAction } from \"./commands/scan.js\";\nimport {\n applyColorPreference,\n formatExampleLines,\n} from \"./utils/apply-color-preference.js\";\nimport { exitGracefully } from \"./utils/exit-gracefully.js\";\nimport { guardStdin } from \"./utils/guard-stdin.js\";\nimport { enableJsonMode, isJsonModeActive, writeJsonErrorReport } from \"./utils/json-mode.js\";\nimport { unrefStdin } from \"./utils/unref-stdin.js\";\nimport { VERSION } from \"./utils/version.js\";\n\nprocess.on(\"SIGINT\", exitGracefully);\nprocess.on(\"SIGTERM\", exitGracefully);\nunrefStdin();\nguardStdin();\n\nconst renderRootHelpEpilog = (): string => `\n${highlighter.dim(\"Examples:\")}\n${formatExampleLines([\n [\"cloud-doctor\", \"pick a provider, then grade (defaults to AWS)\"],\n [\"cloud-doctor aws\", \"grade your AWS account\"],\n [\"cloud-doctor aws --profile prod\", \"use a named AWS profile\"],\n [\"cloud-doctor aws --yes --json\", \"non-interactive JSON report\"],\n [\"cloud-doctor aws profiles\", \"list discovered AWS profiles\"],\n [\"cloud-doctor aws whoami\", \"show the resolved AWS identity\"],\n])}\n\n${highlighter.dim(\"Configuration:\")}\n Add a ${highlighter.info(\"doctor.config.ts\")} (or a ${highlighter.info('\"cloudDoctor\"')} key in package.json).\n`;\n\nconst program = new Command()\n .name(\"cloud-doctor\")\n .description(\"Grade your cloud account — misconfigurations, observability, and security posture\")\n .version(VERSION, \"-v, --version\", \"display the version number\")\n .option(\"--yes, -y\", \"skip interactive prompts\", false)\n .option(\"--json\", \"write a machine-readable report to stdout\", false)\n .option(\"--verbose\", \"show every finding\", false)\n .option(\"--no-color\", \"disable ANSI colors\")\n .hook(\"preAction\", (thisCommand) => {\n const opts = thisCommand.opts<{ color?: boolean }>();\n applyColorPreference(opts.color);\n if (thisCommand.opts<{ json?: boolean }>().json) {\n enableJsonMode(false);\n }\n })\n .addHelpText(\"after\", renderRootHelpEpilog);\n\nconst addScanOptions = (command: Command): Command =>\n command\n .option(\"--profile <name>\", \"AWS named profile (or provider-specific identity)\")\n .option(\"--region <region>\", \"default region for discovery\")\n .option(\"--regions <list>\", \"comma-separated regions to scan\")\n .option(\"--account <id>\", \"assert the expected account ID\");\n\nconst collectOptions = (command: Command): Record<string, unknown> => {\n const parent = command.parent;\n const root = parent?.parent ?? parent;\n return { ...(root?.opts() ?? {}), ...(parent?.opts() ?? {}), ...command.opts() };\n};\n\nconst runScan = async (providerArg: string | undefined, command: Command): Promise<void> => {\n const opts = collectOptions(command);\n try {\n await scanAction({\n providerArg,\n profile: opts.profile as string | undefined,\n region: opts.region as string | undefined,\n account: opts.account as string | undefined,\n regions: opts.regions as string | undefined,\n verbose: Boolean(opts.verbose),\n yes: Boolean(opts.yes),\n json: Boolean(opts.json),\n });\n } catch (error) {\n if (isJsonModeActive()) {\n writeJsonErrorReport(error);\n process.exit(1);\n }\n const message = error instanceof Error ? error.message : String(error);\n console.error(highlighter.error(message));\n process.exit(1);\n }\n};\n\naddScanOptions(\n program\n .command(\"scan [provider]\")\n .description(\"grade a cloud account\")\n .action(async (provider, _options, command) => {\n await runScan(provider, command);\n }),\n);\n\n// Default: `cloud-doctor` or `cloud-doctor aws` with root-level flags\nprogram\n .argument(\"[provider]\", \"cloud provider (aws, gcloud, k8s, db, cloudflare)\")\n .option(\"--profile <name>\", \"AWS named profile (or provider-specific identity)\")\n .option(\"--region <region>\", \"default region for discovery\")\n .option(\"--regions <list>\", \"comma-separated regions to scan\")\n .option(\"--account <id>\", \"assert the expected account ID\")\n .action(async (provider, _options, command) => {\n await runScan(provider, command);\n });\n\nconst aws = program.command(\"aws\").description(\"AWS account commands\");\n\naddScanOptions(\n aws\n .command(\"scan\")\n .description(\"grade an AWS account\")\n .action(async (_options, command) => {\n await runScan(\"aws\", command);\n }),\n);\n\naws\n .command(\"profiles\")\n .description(\"list AWS profiles from ~/.aws/config\")\n .action(async () => {\n await awsProfilesAction();\n });\n\naws\n .command(\"whoami\")\n .description(\"show the resolved AWS identity\")\n .option(\"--profile <name>\", \"AWS named profile\")\n .action(async (options: { profile?: string }) => {\n try {\n await awsWhoamiAction(options.profile);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(highlighter.error(message));\n process.exit(1);\n }\n });\n\nfor (const provider of [\"gcloud\", \"k8s\", \"db\", \"cloudflare\"] as const) {\n addScanOptions(\n program\n .command(provider)\n .description(`grade ${provider} (coming soon)`)\n .action(async (_options, command) => {\n await runScan(provider, command);\n }),\n );\n}\n\nawait program.parseAsync(process.argv);\n","import { highlighter } from \"@cloud-doctor/core\";\n\nexport const cliLogger = {\n log: (message = \"\"): void => {\n console.log(message);\n },\n break: (): void => {\n console.log(\"\");\n },\n error: (message: string): void => {\n console.error(highlighter.error(message));\n },\n warn: (message: string): void => {\n console.warn(highlighter.warn(message));\n },\n info: (message: string): void => {\n console.log(highlighter.info(message));\n },\n};\n","import { highlighter } from \"@cloud-doctor/core\";\nimport { listAwsProfiles, validateAwsCredentials } from \"@cloud-doctor/plugin-aws\";\nimport { cliLogger } from \"../utils/cli-logger.js\";\n\nexport const awsProfilesAction = async (): Promise<void> => {\n const profiles = await listAwsProfiles();\n if (profiles.length === 0) {\n cliLogger.warn(\"No AWS profiles found in ~/.aws/config or ~/.aws/credentials\");\n return;\n }\n\n cliLogger.log(highlighter.bold(\"AWS profiles\"));\n cliLogger.break();\n for (const profile of profiles) {\n const region = profile.region ? highlighter.dim(` · ${profile.region}`) : \"\";\n const kind = highlighter.gray(` (${profile.kind})`);\n cliLogger.log(` ${profile.name}${region}${kind}`);\n if (profile.roleArn) {\n cliLogger.log(highlighter.dim(` role ${profile.roleArn}`));\n }\n }\n cliLogger.break();\n};\n\nexport const awsWhoamiAction = async (profile?: string): Promise<void> => {\n const identity = await validateAwsCredentials({\n profileName: profile ?? process.env.AWS_PROFILE,\n region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,\n label: profile ?? process.env.AWS_PROFILE ?? \"default\",\n kind: \"profile\",\n useEnvironment: !profile && Boolean(process.env.AWS_ACCESS_KEY_ID),\n });\n\n cliLogger.log(highlighter.bold(\"AWS identity\"));\n cliLogger.break();\n cliLogger.log(` Account ${identity.accountId ?? \"unknown\"}`);\n cliLogger.log(` ARN ${identity.arn ?? \"unknown\"}`);\n if (identity.profileName) cliLogger.log(` Profile ${identity.profileName}`);\n if (identity.region) cliLogger.log(` Region ${identity.region}`);\n cliLogger.break();\n};\n","import { buildJsonReport, loadConfig } from \"@cloud-doctor/core\";\nimport ora from \"ora\";\nimport { resolveIdentity } from \"../resolve-identity.js\";\nimport { resolveProvider } from \"../resolve-provider.js\";\nimport { renderScanReport } from \"../render-scan-report.js\";\nimport { isJsonModeActive, writeJsonReport } from \"../utils/json-mode.js\";\nimport { isNonInteractiveEnvironment } from \"../utils/is-non-interactive-environment.js\";\n\nexport interface ScanActionOptions {\n providerArg?: string;\n profile?: string;\n region?: string;\n account?: string;\n regions?: string;\n verbose?: boolean;\n yes?: boolean;\n json?: boolean;\n}\n\nexport const scanAction = async (options: ScanActionOptions): Promise<void> => {\n const config = await loadConfig();\n const plugin = await resolveProvider({\n providerArg: options.providerArg,\n yes: options.yes,\n json: options.json,\n });\n\n if (plugin.status === \"coming-soon\") {\n throw new Error(`${plugin.displayName} support is coming soon. Use \"aws\" for now.`);\n }\n\n const spinner =\n !isJsonModeActive() && !isNonInteractiveEnvironment()\n ? ora(`Connecting to ${plugin.displayName}…`).start()\n : null;\n\n try {\n const { identity } = await resolveIdentity({\n plugin,\n profile: options.profile ?? config.aws?.profile,\n region: options.region,\n account: options.account ?? config.aws?.account,\n yes: options.yes,\n json: options.json,\n });\n\n if (spinner) {\n spinner.text = `Grading ${plugin.displayName} account ${identity.accountId ?? identity.id}…`;\n }\n\n const regions = options.regions?.split(\",\").map((r) => r.trim()) ?? config.aws?.regions;\n const result = await plugin.scan(identity, {\n regions,\n verbose: Boolean(options.verbose),\n accountGuard: options.account,\n });\n\n spinner?.stop();\n\n if (isJsonModeActive()) {\n writeJsonReport(buildJsonReport(result));\n return;\n }\n\n renderScanReport(result, Boolean(options.verbose));\n } catch (error) {\n spinner?.stop();\n throw error;\n }\n};\n","import basePrompts, { type Answers, type PromptObject } from \"prompts\";\nimport { cliLogger } from \"./cli-logger.js\";\nimport { unrefStdin } from \"./unref-stdin.js\";\n\nconst onCancel = (): void => {\n cliLogger.break();\n cliLogger.log(\"Cancelled.\");\n cliLogger.break();\n process.exit(0);\n};\n\nexport interface CliPromptOptions {\n readonly onCancel?: () => void;\n}\n\nexport const prompts = <T extends string = string>(\n questions: PromptObject<T> | PromptObject<T>[],\n options: CliPromptOptions = {},\n): Promise<Answers<T>> =>\n basePrompts(questions, { onCancel: options.onCancel ?? onCancel }).finally(unrefStdin);\n","// HACK: cloud-doctor is a one-shot CLI. When stdin is not a TTY, unref it so\n// a parent holding the write-end open cannot keep the process alive after exit.\n// Mirrors react-doctor's unref-stdin.ts.\nexport const unrefStdin = (): void => {\n if (process.stdin.isTTY) return;\n process.stdin.unref?.();\n};\n","export const NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [\n \"CI\",\n \"GITHUB_ACTIONS\",\n \"GITLAB_CI\",\n \"BUILDKITE\",\n \"JENKINS_URL\",\n \"TF_BUILD\",\n \"CODEBUILD_BUILD_ID\",\n \"TEAMCITY_VERSION\",\n \"BITBUCKET_BUILD_NUMBER\",\n \"CIRCLECI\",\n \"TRAVIS\",\n \"DRONE\",\n \"GIT_DIR\",\n] as const;\n\nconst CODING_AGENT_ENVIRONMENT_VARIABLES = [\n \"CURSOR_AGENT\",\n \"CLAUDE_CODE\",\n \"CODEX_CI\",\n \"AGENT_MODE\",\n] as const;\n\nexport const isNonInteractiveEnvironment = (): boolean =>\n NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some((envVariable) =>\n Boolean(process.env[envVariable]),\n ) || CODING_AGENT_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable]));\n","import { isNonInteractiveEnvironment } from \"./is-non-interactive-environment.js\";\n\ninterface ShouldSkipPromptsInput {\n yes?: boolean;\n json?: boolean;\n}\n\nexport const shouldSkipPrompts = (input: ShouldSkipPromptsInput = {}): boolean =>\n Boolean(input.yes) ||\n Boolean(input.json) ||\n isNonInteractiveEnvironment() ||\n !process.stdin.isTTY;\n","import {\n highlighter,\n loadConfig,\n type CloudDoctorPlugin,\n type DiscoveryContext,\n type IdentityInput,\n type IdentitySummary,\n type ResolvedIdentity,\n} from \"@cloud-doctor/core\";\nimport { SsoLoginRequiredError, validateAwsCredentials } from \"@cloud-doctor/plugin-aws\";\nimport { prompts } from \"./utils/prompts.js\";\nimport { shouldSkipPrompts } from \"./utils/should-skip-prompts.js\";\n\ninterface ResolveIdentityOptions {\n plugin: CloudDoctorPlugin;\n profile?: string;\n region?: string;\n account?: string;\n yes?: boolean;\n json?: boolean;\n}\n\nconst formatIdentityChoice = (identity: IdentitySummary): string => {\n const hints = [identity.hint, identity.accountId ? `account ${identity.accountId}` : undefined]\n .filter(Boolean)\n .join(\" · \");\n return hints ? `${identity.label} (${hints})` : identity.label;\n};\n\nconst promptForIdentity = async (\n identities: IdentitySummary[],\n): Promise<IdentitySummary> => {\n const selectable = identities.filter((identity) => !identity.disabled);\n\n if (selectable.length === 0) {\n throw new Error(\"No identities available. Configure AWS credentials or pass --profile.\");\n }\n\n const defaultIndex = Math.max(\n 0,\n selectable.findIndex((identity) => identity.profileName === process.env.AWS_PROFILE),\n );\n\n const { identityId } = await prompts<{ identityId: string }>({\n type: \"select\",\n name: \"identityId\",\n message: \"Which AWS identity?\",\n initial: defaultIndex === -1 ? 0 : defaultIndex,\n choices: [\n ...selectable.map((identity) => ({\n title: formatIdentityChoice(identity),\n value: identity.id,\n })),\n {\n title: \"Enter credentials manually…\",\n value: \"__manual__\",\n },\n ],\n });\n\n if (identityId === \"__manual__\") {\n return promptForManualIdentity();\n }\n\n const selected = selectable.find((identity) => identity.id === identityId);\n if (!selected) throw new Error(\"Invalid identity selection\");\n return selected;\n};\n\nconst promptForManualIdentity = async (): Promise<IdentitySummary> => {\n const answers = await prompts<\"accessKeyId\" | \"secretAccessKey\" | \"sessionToken\" | \"region\">([\n {\n type: \"text\",\n name: \"accessKeyId\",\n message: \"AWS access key ID\",\n validate: (value) => (value.trim() ? true : \"Required\"),\n },\n {\n type: \"password\",\n name: \"secretAccessKey\",\n message: \"AWS secret access key\",\n validate: (value) => (value.trim() ? true : \"Required\"),\n },\n {\n type: \"text\",\n name: \"sessionToken\",\n message: \"Session token (optional)\",\n },\n {\n type: \"text\",\n name: \"region\",\n message: \"Default region (optional)\",\n },\n ]);\n\n return {\n id: \"__manual__\",\n label: \"(manual credentials)\",\n kind: \"manual\",\n status: \"unknown\",\n hint: \"Session-only — not saved to disk\",\n profileName: undefined,\n defaultRegion: answers.region || undefined,\n };\n};\n\nexport const identitySummaryToInput = (\n identity: IdentitySummary,\n options: ResolveIdentityOptions,\n manual?: { accessKeyId?: string; secretAccessKey?: string; sessionToken?: string },\n): IdentityInput => {\n if (identity.id === \"__manual__\" && manual) {\n return {\n manualAccessKeyId: manual.accessKeyId,\n manualSecretAccessKey: manual.secretAccessKey,\n manualSessionToken: manual.sessionToken,\n region: options.region ?? identity.defaultRegion,\n accountId: options.account,\n };\n }\n\n if (identity.kind === \"environment\") {\n return {\n useEnvironment: true,\n region: options.region,\n accountId: options.account,\n };\n }\n\n return {\n profile: options.profile ?? identity.profileName,\n region: options.region ?? identity.defaultRegion,\n accountId: options.account,\n };\n};\n\nexport const resolveIdentity = async (\n options: ResolveIdentityOptions,\n): Promise<{ identity: ResolvedIdentity; identityInput: IdentityInput }> => {\n const config = await loadConfig();\n const ctx: DiscoveryContext = {\n cwd: process.cwd(),\n yes: Boolean(options.yes),\n json: Boolean(options.json),\n };\n\n const configProfile = config.aws?.profile;\n const identities = await options.plugin.discoverIdentities(ctx);\n\n let identityInput: IdentityInput = {\n profile: options.profile ?? configProfile,\n region: options.region,\n accountId: options.account ?? config.aws?.account,\n };\n\n if (\n !identityInput.profile &&\n !identityInput.useEnvironment &&\n !shouldSkipPrompts({ yes: options.yes, json: options.json })\n ) {\n const selected = await promptForIdentity(identities);\n if (selected.id === \"__manual__\") {\n const manualAnswers = await prompts<\"accessKeyId\" | \"secretAccessKey\" | \"sessionToken\">([\n {\n type: \"text\",\n name: \"accessKeyId\",\n message: \"AWS access key ID\",\n validate: (value) => (value.trim() ? true : \"Required\"),\n },\n {\n type: \"password\",\n name: \"secretAccessKey\",\n message: \"AWS secret access key\",\n validate: (value) => (value.trim() ? true : \"Required\"),\n },\n {\n type: \"text\",\n name: \"sessionToken\",\n message: \"Session token (optional)\",\n },\n ]);\n identityInput = identitySummaryToInput(selected, options, manualAnswers);\n } else {\n identityInput = identitySummaryToInput(selected, options);\n }\n }\n\n const resolved = await options.plugin.resolveIdentity(ctx, identityInput);\n\n if (resolved.kind === \"manual\" && identityInput.manualAccessKeyId) {\n const validated = await validateAwsCredentials({\n profileName: undefined,\n region: identityInput.region,\n label: \"(manual credentials)\",\n kind: \"manual\",\n manualAccessKeyId: identityInput.manualAccessKeyId,\n manualSecretAccessKey: identityInput.manualSecretAccessKey,\n manualSessionToken: identityInput.manualSessionToken,\n });\n return { identity: validated, identityInput };\n }\n\n try {\n const validated = await options.plugin.validateIdentity(resolved);\n if (options.account && validated.accountId && validated.accountId !== options.account) {\n throw new Error(\n `Account guard failed: expected ${options.account}, got ${validated.accountId}`,\n );\n }\n return { identity: validated, identityInput };\n } catch (error) {\n if (error instanceof SsoLoginRequiredError) {\n console.error(highlighter.error(error.message));\n process.exit(1);\n }\n throw error;\n }\n};\n","import {\n highlighter,\n loadConfig,\n type CloudDoctorPlugin,\n type ProviderId,\n} from \"@cloud-doctor/core\";\nimport { getDefaultRegistry, isProviderId } from \"@cloud-doctor/plugins\";\nimport { prompts } from \"./utils/prompts.js\";\nimport { shouldSkipPrompts } from \"./utils/should-skip-prompts.js\";\n\ninterface ResolveProviderInput {\n providerArg?: string;\n yes?: boolean;\n json?: boolean;\n}\n\nexport const resolveProvider = async (\n input: ResolveProviderInput,\n): Promise<CloudDoctorPlugin> => {\n const registry = getDefaultRegistry();\n const config = await loadConfig();\n\n if (input.providerArg) {\n if (!isProviderId(input.providerArg)) {\n throw new Error(\n `Unknown provider \"${input.providerArg}\". Expected one of: aws, gcloud, k8s, db, cloudflare`,\n );\n }\n return registry.require(input.providerArg);\n }\n\n const configured = config.defaultProvider;\n if (configured) {\n return registry.require(configured);\n }\n\n if (shouldSkipPrompts(input)) {\n return registry.require(\"aws\");\n }\n\n const plugins = registry.list();\n const { provider } = await prompts<{ provider: ProviderId }>({\n type: \"select\",\n name: \"provider\",\n message: \"What do you want to grade?\",\n initial: 0,\n choices: plugins.map((plugin) => ({\n title:\n plugin.status === \"coming-soon\"\n ? `${plugin.displayName} (coming soon)`\n : plugin.displayName,\n description: plugin.description,\n value: plugin.id,\n disabled: plugin.status === \"coming-soon\",\n })),\n });\n\n const selected = registry.require(provider);\n if (selected.status === \"coming-soon\") {\n throw new Error(`${selected.displayName} support is coming soon. Use \"aws\" for now.`);\n }\n\n return selected;\n};\n\nexport const printProviderHint = (): void => {\n console.log(highlighter.dim(\"Tip: pass a provider to skip this step, e.g. cloud-doctor aws\"));\n};\n","import isUnicodeSupported from \"is-unicode-supported\";\nimport {\n computeProjectedScore,\n getTopErrorRules,\n highlighter,\n sortCategoriesForDisplay,\n TOP_FIXES_DISPLAY_COUNT,\n type ScanResult,\n} from \"@cloud-doctor/core\";\n\nconst POINTER = isUnicodeSupported() ? \"›\" : \">\";\n\nconst renderScoreBar = (score: number, width = 24): string => {\n const filled = Math.round((score / 100) * width);\n const filledSegment = highlighter.success(\"█\".repeat(filled));\n const emptySegment = highlighter.dim(\"░\".repeat(Math.max(0, width - filled)));\n return `${filledSegment}${emptySegment}`;\n};\n\nexport const renderScanReport = (result: ScanResult, verbose: boolean): void => {\n const { summary, identity, provider, diagnostics, meta } = result;\n const projectedScore = computeProjectedScore(diagnostics) ?? summary.projectedScore;\n const totalFindings = summary.errorCount + summary.warningCount;\n\n console.log(\"\");\n console.log(\n ` ${highlighter.bold(`${summary.score} / 100`)} ${summary.label} ${totalFindings} findings`,\n );\n console.log(` ${renderScoreBar(summary.score)}`);\n if (projectedScore !== undefined && projectedScore > summary.score) {\n console.log(\n highlighter.dim(\n ` +${projectedScore - summary.score} by fixing the top ${TOP_FIXES_DISPLAY_COUNT} issues`,\n ),\n );\n }\n console.log(\"\");\n console.log(\n highlighter.dim(\n `${provider.toUpperCase()} · account ${identity.accountId ?? identity.id} · ${identity.label}`,\n ),\n );\n console.log(\"\");\n\n const topRules = getTopErrorRules(diagnostics);\n if (topRules.length > 0) {\n console.log(highlighter.bold(` Top ${topRules.length} issues to fix first`));\n console.log(\"\");\n topRules.forEach(({ ruleKey, diagnostics: ruleDiagnostics }, index) => {\n const sample = ruleDiagnostics[0];\n if (!sample) return;\n console.log(\n ` ${index + 1}. ${highlighter.bold(`${sample.category}: ${sample.title}`)}`,\n );\n const location = [sample.region, sample.resourceArn].filter(Boolean).join(\" · \");\n if (location) console.log(highlighter.dim(` ${location}`));\n console.log(` ${POINTER} ${sample.recommendation}`);\n if (verbose && ruleDiagnostics.length > 1) {\n console.log(highlighter.gray(` (${ruleDiagnostics.length} resources)`));\n }\n console.log(\"\");\n });\n } else if (totalFindings === 0) {\n console.log(highlighter.success(\" No issues found.\"));\n console.log(\"\");\n }\n\n if (totalFindings > 0) {\n console.log(highlighter.bold(` All ${totalFindings} issues`));\n const categories = sortCategoriesForDisplay(\n Object.keys(summary.categoryCounts) as Array<keyof typeof summary.categoryCounts>,\n );\n for (const category of categories) {\n const counts = summary.categoryCounts[category];\n if (!counts) continue;\n const parts: string[] = [];\n if (counts.errors > 0) parts.push(`${counts.errors} errors`);\n if (counts.warnings > 0) parts.push(`${counts.warnings} warnings`);\n console.log(` ${category.padEnd(16)} ${POINTER} ${parts.join(\", \")}`);\n }\n console.log(\"\");\n }\n\n const regionInfo =\n meta.regionsScanned && meta.regionsScanned.length > 0\n ? ` · ${meta.regionsScanned.length} regions`\n : \"\";\n console.log(\n highlighter.dim(\n ` Scanned in ${(meta.durationMs / 1000).toFixed(1)}s${regionInfo} · read-only · no changes made`,\n ),\n );\n\n if (!verbose && totalFindings > 0) {\n console.log(highlighter.dim(\" Run npx cloud-doctor@latest --verbose for every finding\"));\n }\n console.log(\"\");\n};\n","import { performance } from \"node:perf_hooks\";\nimport { buildJsonReportError } from \"@cloud-doctor/core\";\nimport type { JsonReport, JsonReportError } from \"@cloud-doctor/core\";\n\ninterface JsonModeContext {\n compact: boolean;\n startTime: number;\n}\n\nlet context: JsonModeContext | null = null;\n\nconst installSilentConsole = (): void => {\n const noop = (): void => {};\n for (const key of [\"log\", \"error\", \"warn\", \"info\", \"debug\", \"trace\"] as const) {\n console[key] = noop;\n }\n};\n\nconst serialize = (value: unknown): string =>\n context?.compact ? JSON.stringify(value) : JSON.stringify(value, null, 2);\n\nexport const enableJsonMode = (compact: boolean): void => {\n context = { compact, startTime: performance.now() };\n installSilentConsole();\n};\n\nexport const isJsonModeActive = (): boolean => context !== null;\n\nexport const writeJsonReport = (report: JsonReport): void => {\n process.stdout.write(`${serialize(report)}\\n`);\n};\n\nexport const writeJsonErrorReport = (error: unknown): void => {\n if (!context) return;\n const report: JsonReportError = buildJsonReportError({\n error,\n elapsedMilliseconds: performance.now() - context.startTime,\n });\n process.stdout.write(`${serialize(report)}\\n`);\n};\n","import { highlighter, setColorEnabled } from \"@cloud-doctor/core\";\n\nconst parseColorPreference = (\n colorOption: boolean | undefined,\n): boolean | undefined => {\n if (colorOption === true) return true;\n if (colorOption === false) return false;\n return undefined;\n};\n\nexport const applyColorPreference = (colorOption: boolean | undefined): void => {\n const preference = parseColorPreference(colorOption);\n if (preference === undefined) return;\n setColorEnabled(preference);\n};\n\nexport const formatExampleLines = (\n examples: ReadonlyArray<readonly [command: string, description: string]>,\n): string => {\n const width = Math.max(...examples.map(([command]) => command.length));\n return examples\n .map(\n ([command, description]) =>\n ` $ ${command.padEnd(width)} ${highlighter.dim(`# ${description}`)}`,\n )\n .join(\"\\n\");\n};\n","export const VERSION = \"0.0.1-alpha.0\";\nexport const TERMINAL_HANGUP_EXIT_CODE = 130;\n","import { TERMINAL_HANGUP_EXIT_CODE } from \"./version.js\";\n\nexport const exitGracefully = (): void => {\n process.exit(TERMINAL_HANGUP_EXIT_CODE);\n};\n","import { TERMINAL_HANGUP_EXIT_CODE } from \"./version.js\";\n\nconst TERMINAL_HANGUP_CODES = new Set([\"EIO\", \"ENXIO\"]);\n\nexport const handleStdinError = (error: NodeJS.ErrnoException): void => {\n if (error.code !== undefined && TERMINAL_HANGUP_CODES.has(error.code)) {\n process.exit(TERMINAL_HANGUP_EXIT_CODE);\n }\n throw error;\n};\n\nexport const guardStdin = (): void => {\n process.stdin.on(\"error\", handleStdinError);\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as _cloud_doctor_core from '@cloud-doctor/core';
|
|
2
|
+
import { ProviderId } from '@cloud-doctor/core';
|
|
3
|
+
export { CloudDoctorConfig, JsonReport, ScanResult, defineConfig, loadConfig } from '@cloud-doctor/core';
|
|
4
|
+
export { getDefaultRegistry } from '@cloud-doctor/plugins';
|
|
5
|
+
|
|
6
|
+
interface DiagnoseOptions {
|
|
7
|
+
provider?: ProviderId;
|
|
8
|
+
profile?: string;
|
|
9
|
+
region?: string;
|
|
10
|
+
regions?: string[];
|
|
11
|
+
account?: string;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}
|
|
14
|
+
declare const diagnose: (options?: DiagnoseOptions) => Promise<_cloud_doctor_core.JsonReport>;
|
|
15
|
+
|
|
16
|
+
export { type DiagnoseOptions, diagnose };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildJsonReport,
|
|
3
|
+
defineConfig,
|
|
4
|
+
getDefaultRegistry,
|
|
5
|
+
loadConfig
|
|
6
|
+
} from "./chunk-4PZ65VRA.js";
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
var diagnose = async (options = {}) => {
|
|
10
|
+
const registry = getDefaultRegistry();
|
|
11
|
+
const providerId = options.provider ?? "aws";
|
|
12
|
+
const plugin = registry.require(providerId);
|
|
13
|
+
const ctx = { cwd: process.cwd(), yes: true, json: true };
|
|
14
|
+
const identity = await plugin.resolveIdentity(ctx, {
|
|
15
|
+
profile: options.profile,
|
|
16
|
+
region: options.region,
|
|
17
|
+
accountId: options.account
|
|
18
|
+
});
|
|
19
|
+
const validated = await plugin.validateIdentity(identity);
|
|
20
|
+
const config = {
|
|
21
|
+
regions: options.regions,
|
|
22
|
+
verbose: options.verbose ?? false,
|
|
23
|
+
accountGuard: options.account
|
|
24
|
+
};
|
|
25
|
+
const result = await plugin.scan(validated, config);
|
|
26
|
+
return buildJsonReport(result);
|
|
27
|
+
};
|
|
28
|
+
export {
|
|
29
|
+
defineConfig,
|
|
30
|
+
diagnose,
|
|
31
|
+
getDefaultRegistry,
|
|
32
|
+
loadConfig
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { defineConfig, loadConfig } from \"@cloud-doctor/core\";\nexport type { CloudDoctorConfig, JsonReport, ScanResult } from \"@cloud-doctor/core\";\nexport { getDefaultRegistry } from \"@cloud-doctor/plugins\";\n\nimport { buildJsonReport } from \"@cloud-doctor/core\";\nimport type { ProviderId, ScanConfig } from \"@cloud-doctor/core\";\nimport { getDefaultRegistry } from \"@cloud-doctor/plugins\";\n\nexport interface DiagnoseOptions {\n provider?: ProviderId;\n profile?: string;\n region?: string;\n regions?: string[];\n account?: string;\n verbose?: boolean;\n}\n\nexport const diagnose = async (options: DiagnoseOptions = {}) => {\n const registry = getDefaultRegistry();\n const providerId = options.provider ?? \"aws\";\n const plugin = registry.require(providerId);\n\n const ctx = { cwd: process.cwd(), yes: true, json: true };\n const identity = await plugin.resolveIdentity(ctx, {\n profile: options.profile,\n region: options.region,\n accountId: options.account,\n });\n const validated = await plugin.validateIdentity(identity);\n\n const config: ScanConfig = {\n regions: options.regions,\n verbose: options.verbose ?? false,\n accountGuard: options.account,\n };\n\n const result = await plugin.scan(validated, config);\n return buildJsonReport(result);\n};\n"],"mappings":";;;;;;;;AAiBO,IAAM,WAAW,OAAO,UAA2B,CAAC,MAAM;AAC/D,QAAM,WAAW,mBAAmB;AACpC,QAAM,aAAa,QAAQ,YAAY;AACvC,QAAM,SAAS,SAAS,QAAQ,UAAU;AAE1C,QAAM,MAAM,EAAE,KAAK,QAAQ,IAAI,GAAG,KAAK,MAAM,MAAM,KAAK;AACxD,QAAM,WAAW,MAAM,OAAO,gBAAgB,KAAK;AAAA,IACjD,SAAS,QAAQ;AAAA,IACjB,QAAQ,QAAQ;AAAA,IAChB,WAAW,QAAQ;AAAA,EACrB,CAAC;AACD,QAAM,YAAY,MAAM,OAAO,iBAAiB,QAAQ;AAExD,QAAM,SAAqB;AAAA,IACzB,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ,WAAW;AAAA,IAC5B,cAAc,QAAQ;AAAA,EACxB;AAEA,QAAM,SAAS,MAAM,OAAO,KAAK,WAAW,MAAM;AAClD,SAAO,gBAAgB,MAAM;AAC/B;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cloud-doctor",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"description": "One command grades your whole cloud account — misconfigurations, observability, and security posture",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"aws",
|
|
7
|
+
"cloud",
|
|
8
|
+
"security",
|
|
9
|
+
"devops",
|
|
10
|
+
"cli",
|
|
11
|
+
"infrastructure",
|
|
12
|
+
"observability",
|
|
13
|
+
"cspm"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/dumbmachine/cloud-doctor#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/dumbmachine/cloud-doctor/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/dumbmachine/cloud-doctor.git",
|
|
22
|
+
"directory": "packages/cloud-doctor"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "dumbmachine",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"bin": {
|
|
29
|
+
"cloud-doctor": "./bin/cloud-doctor.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"bin/**",
|
|
33
|
+
"dist/**",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/cli/index.d.ts",
|
|
39
|
+
"default": "./dist/cli/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./api": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"default": "./dist/index.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "vitest run"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public",
|
|
54
|
+
"tag": "alpha"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@aws-sdk/client-sts": "^3.888.0",
|
|
58
|
+
"@aws-sdk/credential-providers": "^3.888.0",
|
|
59
|
+
"@smithy/shared-ini-file-loader": "^4.1.2",
|
|
60
|
+
"commander": "^14.0.3",
|
|
61
|
+
"confbox": "^0.2.4",
|
|
62
|
+
"effect": "4.0.0-beta.70",
|
|
63
|
+
"is-unicode-supported": "^2.1.0",
|
|
64
|
+
"jiti": "^2.7.0",
|
|
65
|
+
"ora": "^9.4.0",
|
|
66
|
+
"picocolors": "^1.1.1",
|
|
67
|
+
"prompts": "^2.4.2"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": "^20.19.0 || >=22.13.0"
|
|
71
|
+
}
|
|
72
|
+
}
|