infrawise 0.2.2 → 0.3.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.
@@ -22,9 +22,17 @@ const credential_providers_1 = require("@aws-sdk/credential-providers");
22
22
  const core_1 = require("../core");
23
23
  function clientConfig(cfg) {
24
24
  const region = cfg.region ?? 'us-east-1';
25
- return cfg.profile
26
- ? { region, credentials: (0, credential_providers_1.fromIni)({ profile: cfg.profile }) }
27
- : { region };
25
+ const base = { region };
26
+ if (cfg.endpoint)
27
+ base.endpoint = cfg.endpoint;
28
+ if (cfg.profile)
29
+ base.credentials = (0, credential_providers_1.fromIni)({ profile: cfg.profile });
30
+ else if (cfg.endpoint)
31
+ base.credentials = {
32
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'test',
33
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'test',
34
+ };
35
+ return base;
28
36
  }
29
37
  // ─── SQS ─────────────────────────────────────────────────────────────────────
30
38
  async function extractSQSMetadata(cfg = {}) {
@@ -2,16 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractDynamoMetadata = extractDynamoMetadata;
4
4
  exports.validateDynamoAccess = validateDynamoAccess;
5
+ exports.probeDynamoAccess = probeDynamoAccess;
5
6
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
6
7
  const credential_providers_1 = require("@aws-sdk/credential-providers");
7
8
  const core_1 = require("../core");
8
9
  function createDynamoClient(config) {
9
10
  const region = config.aws?.region ?? 'us-east-1';
10
11
  const profile = config.aws?.profile;
12
+ const endpoint = config.aws?.endpoint;
11
13
  const clientConfig = { region };
12
- if (profile) {
14
+ if (endpoint)
15
+ clientConfig.endpoint = endpoint;
16
+ if (profile)
13
17
  clientConfig.credentials = (0, credential_providers_1.fromIni)({ profile });
14
- }
18
+ else if (endpoint)
19
+ clientConfig.credentials = {
20
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'test',
21
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'test',
22
+ };
15
23
  return new client_dynamodb_1.DynamoDBClient(clientConfig);
16
24
  }
17
25
  function parseTableDescription(desc) {
@@ -95,3 +103,7 @@ async function validateDynamoAccess(config) {
95
103
  return false;
96
104
  }
97
105
  }
106
+ async function probeDynamoAccess(config) {
107
+ const client = createDynamoClient(config);
108
+ await client.send(new client_dynamodb_1.ListTablesCommand({ Limit: 1 }));
109
+ }
@@ -10,9 +10,17 @@ const MAX_LOG_GROUPS = 50;
10
10
  const MAX_EVENTS_PER_GROUP = 50;
11
11
  function clientConfig(cfg) {
12
12
  const region = cfg.region ?? 'us-east-1';
13
- return cfg.profile
14
- ? { region, credentials: (0, credential_providers_1.fromIni)({ profile: cfg.profile }) }
15
- : { region };
13
+ const base = { region };
14
+ if (cfg.endpoint)
15
+ base.endpoint = cfg.endpoint;
16
+ if (cfg.profile)
17
+ base.credentials = (0, credential_providers_1.fromIni)({ profile: cfg.profile });
18
+ else if (cfg.endpoint)
19
+ base.credentials = {
20
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? 'test',
21
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? 'test',
22
+ };
23
+ return base;
16
24
  }
17
25
  // Strip UUIDs, timestamps, numbers → group similar messages by pattern
18
26
  function toPattern(message) {
@@ -64,7 +64,7 @@ async function runAnalyze(options = {}) {
64
64
  process.exit(1);
65
65
  }
66
66
  const repoPath = options.repo ?? process.cwd();
67
- const awsCfg = { region: config.aws?.region, profile: config.aws?.profile };
67
+ const awsCfg = { region: config.aws?.region, profile: config.aws?.profile, endpoint: config.aws?.endpoint };
68
68
  const dynamoMeta = [];
69
69
  const postgresMeta = [];
70
70
  const mysqlMeta = [];
@@ -113,7 +113,7 @@ async function runDoctor(options = {}) {
113
113
  detail: hasCreds ? undefined : 'Run: aws configure',
114
114
  };
115
115
  }));
116
- const awsCfg = { region: config?.aws?.region, profile: config?.aws?.profile };
116
+ const awsCfg = { region: config?.aws?.region, profile: config?.aws?.profile, endpoint: config?.aws?.endpoint };
117
117
  // DynamoDB
118
118
  results.push(await runCheck('Testing DynamoDB access...', async () => {
119
119
  if (!config)
@@ -121,15 +121,15 @@ async function runDoctor(options = {}) {
121
121
  if (config.dynamodb?.enabled !== true)
122
122
  return { name: 'DynamoDB', status: 'skip', message: 'Disabled in config' };
123
123
  try {
124
- const ok = await (0, dynamodb_1.validateDynamoAccess)(config);
125
- return {
126
- name: 'DynamoDB', status: ok ? 'pass' : 'warn',
127
- message: ok ? `Connected (profile: ${config.aws?.profile ?? 'default'})` : 'Cannot connect',
128
- detail: ok ? undefined : 'Check IAM: dynamodb:ListTables, dynamodb:DescribeTable',
129
- };
124
+ await (0, dynamodb_1.probeDynamoAccess)(config);
125
+ return { name: 'DynamoDB', status: 'pass', message: `Connected (profile: ${config.aws?.profile ?? 'default'})` };
130
126
  }
131
127
  catch (err) {
132
- return { name: 'DynamoDB', status: 'warn', message: err instanceof Error ? err.message : String(err) };
128
+ return {
129
+ name: 'DynamoDB', status: 'warn',
130
+ message: err instanceof Error ? err.message : String(err),
131
+ detail: 'Check IAM: dynamodb:ListTables, dynamodb:DescribeTable',
132
+ };
133
133
  }
134
134
  }));
135
135
  // SQS
@@ -293,7 +293,11 @@ async function runDoctor(options = {}) {
293
293
  // IaC detection
294
294
  results.push(await runCheck('Detecting IaC files...', async () => {
295
295
  const cwd = process.cwd();
296
- const hasTF = fs.existsSync(path.join(cwd, 'main.tf')) || fs.readdirSync(cwd).some((f) => f.endsWith('.tf'));
296
+ const hasTfFile = (dir) => fs.existsSync(dir) && fs.readdirSync(dir).some((f) => f.endsWith('.tf'));
297
+ const hasTF = hasTfFile(cwd) || fs.readdirSync(cwd).some((entry) => {
298
+ const full = path.join(cwd, entry);
299
+ return fs.statSync(full).isDirectory() && hasTfFile(full);
300
+ });
297
301
  const hasCFN = fs.existsSync(path.join(cwd, 'template.yaml')) || fs.existsSync(path.join(cwd, 'template.json'));
298
302
  const hasCDK = fs.existsSync(path.join(cwd, 'cdk.json'));
299
303
  const hasCDKOut = fs.existsSync(path.join(cwd, 'cdk.out'));
@@ -71,7 +71,13 @@ async function runInit(options = {}) {
71
71
  type: 'list',
72
72
  name: 'awsProfile',
73
73
  message: 'AWS profile:',
74
- choices: profiles,
74
+ choices: [
75
+ new inquirer_1.default.Separator('── no profile ──'),
76
+ { name: 'Environment variables (CI/CD, real AWS)', value: '__env__' },
77
+ { name: 'LocalStack (local development)', value: '__localstack__' },
78
+ new inquirer_1.default.Separator('── named profiles ──'),
79
+ ...profiles,
80
+ ],
75
81
  default: profiles[0],
76
82
  },
77
83
  {
@@ -80,24 +86,18 @@ async function runInit(options = {}) {
80
86
  message: 'AWS region:',
81
87
  default: detectedRegion,
82
88
  },
89
+ {
90
+ type: 'input',
91
+ name: 'endpoint',
92
+ message: 'LocalStack endpoint:',
93
+ default: 'http://localhost:4566',
94
+ when: (a) => a.awsProfile === '__localstack__',
95
+ },
83
96
  ]);
84
97
  // ── Databases ──────────────────────────────────────────────────────────────
85
98
  console.log('\n ' + chalk_1.default.bold('Databases'));
99
+ console.log(chalk_1.default.dim(' Self-hosted databases (PostgreSQL, MySQL, MongoDB).'));
86
100
  const databases = await inquirer_1.default.prompt([
87
- {
88
- type: 'confirm',
89
- name: 'dynamoEnabled',
90
- message: 'Enable DynamoDB analysis?',
91
- default: true,
92
- },
93
- {
94
- type: 'input',
95
- name: 'dynamoTables',
96
- message: 'DynamoDB tables to include:',
97
- default: '',
98
- suffix: chalk_1.default.dim(' (comma-separated, blank = all)'),
99
- when: (a) => a.dynamoEnabled,
100
- },
101
101
  {
102
102
  type: 'confirm',
103
103
  name: 'pgEnabled',
@@ -140,8 +140,22 @@ async function runInit(options = {}) {
140
140
  ]);
141
141
  // ── AWS services ───────────────────────────────────────────────────────────
142
142
  console.log('\n ' + chalk_1.default.bold('AWS Services'));
143
- console.log(chalk_1.default.dim(' Infrawise will introspect these services credentials from the AWS profile above.'));
143
+ console.log(chalk_1.default.dim(' Infrawise will introspect these services using the credentials configured above.'));
144
144
  const services = await inquirer_1.default.prompt([
145
+ {
146
+ type: 'confirm',
147
+ name: 'dynamoEnabled',
148
+ message: 'Introspect DynamoDB?',
149
+ default: true,
150
+ },
151
+ {
152
+ type: 'input',
153
+ name: 'dynamoTables',
154
+ message: 'DynamoDB tables to include:',
155
+ default: '',
156
+ suffix: chalk_1.default.dim(' (comma-separated, blank = all)'),
157
+ when: (a) => a.dynamoEnabled,
158
+ },
145
159
  {
146
160
  type: 'confirm',
147
161
  name: 'sqsEnabled',
@@ -196,8 +210,8 @@ async function runInit(options = {}) {
196
210
  },
197
211
  ]);
198
212
  // ── Build config ───────────────────────────────────────────────────────────
199
- const includeTables = databases.dynamoTables
200
- ? databases.dynamoTables.split(',').map((t) => t.trim()).filter(Boolean)
213
+ const includeTables = services.dynamoTables
214
+ ? services.dynamoTables.split(',').map((t) => t.trim()).filter(Boolean)
201
215
  : [];
202
216
  const ssmPaths = services.ssmPaths
203
217
  ? services.ssmPaths.split(',').map((p) => p.trim()).filter(Boolean)
@@ -205,9 +219,13 @@ async function runInit(options = {}) {
205
219
  const logGroupPrefixes = services.logGroupPrefixes
206
220
  ? services.logGroupPrefixes.split(',').map((p) => p.trim()).filter(Boolean)
207
221
  : [];
222
+ const isLocalStack = core.awsProfile === '__localstack__';
223
+ const isEnvVars = core.awsProfile === '__env__';
224
+ const resolvedProfile = isLocalStack || isEnvVars ? '' : core.awsProfile;
225
+ const resolvedEndpoint = isLocalStack ? (core.endpoint || 'http://localhost:4566') : undefined;
208
226
  const configContent = (0, core_1.generateDefaultConfig)(core.project, {
209
- aws: { profile: core.awsProfile, region: core.region },
210
- dynamodb: { enabled: databases.dynamoEnabled, includeTables },
227
+ aws: { profile: resolvedProfile, region: core.region, endpoint: resolvedEndpoint },
228
+ dynamodb: { enabled: services.dynamoEnabled, includeTables },
211
229
  postgres: { enabled: databases.pgEnabled, connectionString: databases.pgConnectionString ?? '' },
212
230
  mysql: { enabled: databases.mysqlEnabled, connectionString: databases.mysqlConnectionString ?? '' },
213
231
  mongodb: { enabled: databases.mongoEnabled, connectionString: databases.mongoConnectionString ?? '' },
@@ -46,6 +46,7 @@ exports.InfrawiseConfigSchema = zod_1.z.object({
46
46
  .object({
47
47
  profile: zod_1.z.string().optional().default('default'),
48
48
  region: zod_1.z.string().optional().default('us-east-1'),
49
+ endpoint: zod_1.z.string().optional(),
49
50
  })
50
51
  .optional()
51
52
  .default({}),
@@ -128,6 +129,7 @@ function generateDefaultConfig(projectName, options) {
128
129
  aws: {
129
130
  profile: options?.aws?.profile ?? 'default',
130
131
  region: options?.aws?.region ?? 'us-east-1',
132
+ ...(options?.aws?.endpoint ? { endpoint: options.aws.endpoint } : {}),
131
133
  },
132
134
  dynamodb: { enabled: options?.dynamodb?.enabled ?? true, includeTables: options?.dynamodb?.includeTables ?? [] },
133
135
  postgres: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infrawise",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "CLI-first infrastructure intelligence platform — analyzes DynamoDB, PostgreSQL, MySQL, MongoDB, SQS, SNS, SSM, Secrets Manager, Lambda, CloudWatch Logs and exposes findings as an MCP server for Claude Code",
5
5
  "keywords": [
6
6
  "infrastructure",