@dsmrt/axiom-cli 1.0.0 → 1.2.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.
@@ -3,6 +3,16 @@ import yargs from "yargs";
3
3
 
4
4
  // src/commands/config.ts
5
5
  import { loadConfig } from "@dsmrt/axiom-config";
6
+
7
+ // src/debug.ts
8
+ var isDebugEnabled = () => process.env.AXIOM_DEBUG === "true" || process.env.AXIOM_DEBUG === "1";
9
+ var debug = (message, ...args) => {
10
+ if (isDebugEnabled()) {
11
+ console.error(`[axiom:cli] ${message}`, ...args);
12
+ }
13
+ };
14
+
15
+ // src/commands/config.ts
6
16
  var Config = class {
7
17
  command = "config";
8
18
  describe = "print the config";
@@ -13,13 +23,230 @@ var Config = class {
13
23
  return args;
14
24
  };
15
25
  handler = async (args) => {
16
- console.log(JSON.stringify(await this.loadConfig(args)));
26
+ debug(`Config command handler called with env: ${args.env}`);
27
+ const config = await this.loadConfig(args);
28
+ debug(`Config loaded successfully, outputting as JSON`);
29
+ console.log(JSON.stringify(config));
17
30
  };
18
31
  loadConfig = async (args) => {
32
+ debug(`Loading config with env: ${args.env}`);
19
33
  return loadConfig(args);
20
34
  };
21
35
  };
22
36
 
37
+ // src/commands/init.ts
38
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
39
+ import { join } from "path";
40
+ import inquirer from "inquirer";
41
+ var Init = class {
42
+ command = "init";
43
+ describe = "Initialize Axiom configuration files";
44
+ builder = (args) => {
45
+ args.option("force", {
46
+ alias: "f",
47
+ type: "boolean",
48
+ description: "Overwrite existing config files",
49
+ default: false
50
+ }).option("name", {
51
+ type: "string",
52
+ description: "Project name"
53
+ }).option("account", {
54
+ type: "string",
55
+ description: "AWS account ID"
56
+ }).option("region", {
57
+ type: "string",
58
+ description: "AWS region"
59
+ }).option("profile", {
60
+ type: "string",
61
+ description: "AWS profile name"
62
+ }).option("custom-types", {
63
+ type: "boolean",
64
+ description: "Create custom types file for extending config"
65
+ });
66
+ return args;
67
+ };
68
+ handler = async (args) => {
69
+ debug(`Init command handler called with args: ${JSON.stringify(args)}`);
70
+ const cwd = process.cwd();
71
+ if (!existsSync(cwd)) {
72
+ mkdirSync(cwd, { recursive: true });
73
+ }
74
+ const configPath = join(cwd, ".axiom.ts");
75
+ const devConfigPath = join(cwd, ".axiom.dev.ts");
76
+ const typesPath = join(cwd, "axiom.config.d.ts");
77
+ if (!args.force) {
78
+ const existingFiles = [];
79
+ if (existsSync(configPath)) existingFiles.push(".axiom.ts");
80
+ if (existsSync(devConfigPath)) existingFiles.push(".axiom.dev.ts");
81
+ if (existsSync(typesPath)) existingFiles.push("axiom.config.d.ts");
82
+ if (existingFiles.length > 0) {
83
+ console.error(
84
+ `\u274C Config files already exist: ${existingFiles.join(", ")}`
85
+ );
86
+ console.error(" Use --force to overwrite them.");
87
+ process.exit(1);
88
+ }
89
+ }
90
+ const answers = await inquirer.prompt([
91
+ {
92
+ type: "input",
93
+ name: "name",
94
+ message: "Project name:",
95
+ default: args.name || "my-app",
96
+ when: !args.name
97
+ },
98
+ {
99
+ type: "input",
100
+ name: "account",
101
+ message: "AWS account ID:",
102
+ default: args.account || "123456789012",
103
+ when: !args.account,
104
+ validate: (input) => {
105
+ if (/^\d{12}$/.test(input)) return true;
106
+ return "AWS account ID must be 12 digits";
107
+ }
108
+ },
109
+ {
110
+ type: "input",
111
+ name: "region",
112
+ message: "AWS region:",
113
+ default: args.region || "us-east-1",
114
+ when: !args.region
115
+ },
116
+ {
117
+ type: "input",
118
+ name: "profile",
119
+ message: "AWS profile name:",
120
+ default: args.profile || "default",
121
+ when: !args.profile
122
+ },
123
+ {
124
+ type: "confirm",
125
+ name: "createCustomTypes",
126
+ message: "Would you like to add custom config properties?",
127
+ default: true,
128
+ when: args.customTypes === void 0
129
+ }
130
+ ]);
131
+ const config = {
132
+ name: args.name || answers.name,
133
+ account: args.account || answers.account,
134
+ region: args.region || answers.region,
135
+ profile: args.profile || answers.profile,
136
+ createCustomTypes: args.customTypes !== void 0 ? args.customTypes : answers.createCustomTypes
137
+ };
138
+ debug(`Creating config files with config: ${JSON.stringify(config)}`);
139
+ if (config.createCustomTypes) {
140
+ const typesContent = this.generateTypesFile();
141
+ writeFileSync(typesPath, typesContent);
142
+ console.log(`\u2705 Created ${typesPath}`);
143
+ debug(`Created types file: ${typesPath}`);
144
+ }
145
+ const baseConfigContent = this.generateBaseConfig(
146
+ config,
147
+ config.createCustomTypes
148
+ );
149
+ writeFileSync(configPath, baseConfigContent);
150
+ console.log(`\u2705 Created ${configPath}`);
151
+ debug(`Created base config: ${configPath}`);
152
+ const devConfigContent = this.generateDevConfig(config.createCustomTypes);
153
+ writeFileSync(devConfigPath, devConfigContent);
154
+ console.log(`\u2705 Created ${devConfigPath}`);
155
+ debug(`Created dev config: ${devConfigPath}`);
156
+ console.log("\n\u{1F389} Axiom configuration initialized successfully!");
157
+ console.log("\n\u{1F4DD} Next steps:");
158
+ console.log(" 1. Edit .axiom.ts to customize your base configuration");
159
+ console.log(
160
+ " 2. Edit .axiom.dev.ts to customize your development configuration"
161
+ );
162
+ if (config.createCustomTypes) {
163
+ console.log(
164
+ " 3. Edit axiom.config.d.ts to add your custom config properties"
165
+ );
166
+ }
167
+ console.log(
168
+ ` ${config.createCustomTypes ? "4" : "3"}. Run 'axiom config' to verify your configuration`
169
+ );
170
+ };
171
+ generateTypesFile() {
172
+ return `/**
173
+ * Custom Axiom Configuration Types
174
+ *
175
+ * Extend this interface to add custom properties to your Axiom config.
176
+ * These properties will be type-safe when using loadConfig() in your code.
177
+ *
178
+ * Example:
179
+ *
180
+ * export interface CustomConfig {
181
+ * apiUrl: string;
182
+ * apiKey: string;
183
+ * maxRetries: number;
184
+ * features: {
185
+ * authentication: boolean;
186
+ * analytics: boolean;
187
+ * };
188
+ * }
189
+ */
190
+
191
+ export interface CustomConfig {
192
+ // Add your custom properties here
193
+ // Example:
194
+ // apiUrl: string;
195
+ // maxRetries: number;
196
+ }
197
+ `;
198
+ }
199
+ generateBaseConfig(config, hasCustomTypes) {
200
+ const importStatement = hasCustomTypes ? `import type { Config } from "@dsmrt/axiom-config";
201
+ import type { CustomConfig } from "./axiom.config";
202
+
203
+ type AxiomConfig = Config & CustomConfig;
204
+
205
+ const config: AxiomConfig = {` : `import type { Config } from "@dsmrt/axiom-config";
206
+
207
+ const config: Config = {`;
208
+ return `${importStatement}
209
+ name: "${config.name}",
210
+ env: "prod",
211
+ aws: {
212
+ account: "${config.account}",
213
+ region: "${config.region}",
214
+ profile: "${config.profile}",
215
+ },
216
+ baseParameterPath: "/${config.name}/prod",${hasCustomTypes ? `
217
+
218
+ // Add your custom config properties here
219
+ // Example:
220
+ // apiUrl: "https://api.example.com",
221
+ // maxRetries: 3,` : ""}
222
+ };
223
+
224
+ export default config;
225
+ `;
226
+ }
227
+ generateDevConfig(hasCustomTypes) {
228
+ const importStatement = hasCustomTypes ? `import type { Config } from "@dsmrt/axiom-config";
229
+ import type { CustomConfig } from "./axiom.config";
230
+
231
+ type AxiomConfig = Config & CustomConfig;
232
+
233
+ const config: Partial<AxiomConfig> = {` : `import type { Config } from "@dsmrt/axiom-config";
234
+
235
+ const config: Partial<Config> = {`;
236
+ return `${importStatement}
237
+ env: "dev",
238
+ baseParameterPath: undefined, // Will be auto-generated as /{name}/dev${hasCustomTypes ? `,
239
+
240
+ // Override custom config properties for dev environment
241
+ // Example:
242
+ // apiUrl: "https://api.dev.example.com",` : ""}
243
+ };
244
+
245
+ export default config;
246
+ `;
247
+ }
248
+ };
249
+
23
250
  // src/commands/params/base.ts
24
251
  var ParamsCommand = class {
25
252
  command = "params";
@@ -45,7 +272,7 @@ import {
45
272
  AssumeRoleCommand,
46
273
  STSClient
47
274
  } from "@aws-sdk/client-sts";
48
- import inquirer from "inquirer";
275
+ import inquirer2 from "inquirer";
49
276
 
50
277
  // src/cache.ts
51
278
  import fs from "fs";
@@ -137,7 +364,7 @@ var roleAssumerCallable = (config) => {
137
364
  };
138
365
  };
139
366
  var mfaCodeProvider = async (mfaSerial) => {
140
- const mfaCode = await inquirer.prompt({
367
+ const mfaCode = await inquirer2.prompt({
141
368
  name: "code",
142
369
  message: `Enter MFA code for ${mfaSerial}: `,
143
370
  type: "password"
@@ -193,7 +420,16 @@ Example:
193
420
  return args;
194
421
  };
195
422
  handler = async (args) => {
423
+ debug(
424
+ `Get command handler called with env: ${args.env}, path: ${args.path}`
425
+ );
196
426
  const config = await loadConfig2({ env: args.env });
427
+ debug(
428
+ `Config loaded successfully, base parameter path: ${config.aws?.baseParameterPath}`
429
+ );
430
+ debug(
431
+ `Creating SSM client with region: ${config.aws.region}, profile: ${config.aws.profile}`
432
+ );
197
433
  const collection = new ParameterCollection(
198
434
  config.aws?.baseParameterPath,
199
435
  new SSMClient({
@@ -206,7 +442,9 @@ Example:
206
442
  })
207
443
  })
208
444
  );
445
+ debug(`Fetching parameters from SSM...`);
209
446
  const params = await collection.get();
447
+ debug(`Retrieved ${params.size} parameters`);
210
448
  params.forEach((parameter) => {
211
449
  console.log(
212
450
  chalk.gray(
@@ -226,7 +464,7 @@ import {
226
464
  } from "@aws-sdk/client-ssm";
227
465
  import { loadConfig as loadConfig3 } from "@dsmrt/axiom-config";
228
466
  import chalk2 from "chalk";
229
- import inquirer2 from "inquirer";
467
+ import inquirer3 from "inquirer";
230
468
 
231
469
  // src/commands/params/utils.ts
232
470
  var buildPath = (config, path) => {
@@ -276,30 +514,50 @@ Example: "/root/myParam" or "service/secret"`
276
514
  });
277
515
  };
278
516
  handler = async (args) => {
517
+ debug(
518
+ `Set command handler called with path: ${args.path}, secure: ${args.secure}, overwrite: ${args.overwrite}, force: ${args.force}`
519
+ );
279
520
  const config = await loadConfig3({ env: args.env });
521
+ debug(
522
+ `Config loaded successfully, base parameter path: ${config.aws?.baseParameterPath}`
523
+ );
524
+ const fullPath = buildPath(config, args.path);
525
+ debug(`Full parameter path: ${fullPath}`);
280
526
  if (args.force !== true) {
281
- const res = await inquirer2.prompt({
527
+ debug(`Prompting user for confirmation...`);
528
+ const res = await inquirer3.prompt({
282
529
  type: "confirm",
283
530
  name: "setParam",
284
531
  message: `Are you sure you want to set '${args.path}'?`
285
532
  });
286
533
  if (!res.setParam) {
534
+ debug(`User declined, aborting`);
287
535
  console.log("Doing nothing.");
288
536
  return;
289
537
  }
538
+ debug(`User confirmed`);
539
+ } else {
540
+ debug(`Force flag set, skipping confirmation`);
290
541
  }
542
+ debug(
543
+ `Creating SSM client with region: ${config.aws.region}, profile: ${config.aws.profile}`
544
+ );
291
545
  const client = new SSMClient2({
292
546
  region: config.aws.region,
293
547
  credentials: await CachedCredentialProvider(config.aws)
294
548
  });
549
+ debug(
550
+ `Sending parameter to SSM with type: ${args.secure ? "SecureString" : "String"}`
551
+ );
295
552
  const params = await client.send(
296
553
  new PutParameterCommand({
297
- Name: buildPath(config, args.path),
554
+ Name: fullPath,
298
555
  Value: args.value,
299
556
  Type: args.secure ? ParameterType.SECURE_STRING : ParameterType.STRING,
300
557
  Overwrite: args.overwrite
301
558
  })
302
559
  );
560
+ debug(`Parameter set successfully, version: ${params.Version}`);
303
561
  console.log(chalk2.green("Version: "), chalk2.white.bold(params.Version));
304
562
  };
305
563
  };
@@ -307,7 +565,7 @@ Example: "/root/myParam" or "service/secret"`
307
565
  // src/commands/params/delete.ts
308
566
  import { DeleteParameterCommand, SSMClient as SSMClient3 } from "@aws-sdk/client-ssm";
309
567
  import { loadConfig as loadConfig4 } from "@dsmrt/axiom-config";
310
- import inquirer3 from "inquirer";
568
+ import inquirer4 from "inquirer";
311
569
  var log = console.log;
312
570
  var DeleteCommand = class {
313
571
  command = "delete <path>";
@@ -329,33 +587,51 @@ Example: "/root/myParam" or "service/secret"`,
329
587
  return args;
330
588
  };
331
589
  handler = async (args) => {
590
+ debug(
591
+ `Delete command handler called with path: ${args.path}, force: ${args.force}`
592
+ );
332
593
  const config = await loadConfig4({ env: args.env });
594
+ debug(
595
+ `Config loaded successfully, base parameter path: ${config.aws?.baseParameterPath}`
596
+ );
333
597
  const path = buildPath(args, args.path);
598
+ debug(`Full parameter path: ${path}`);
599
+ debug(
600
+ `Creating SSM client with region: ${config.aws?.region}, profile: ${config.aws?.profile}`
601
+ );
334
602
  const client = new SSMClient3({
335
603
  region: config.aws?.region,
336
604
  credentials: await CachedCredentialProvider(config.aws)
337
605
  });
338
606
  if (path === args.aws.baseParameterPath) {
607
+ debug(`Delete blocked: path matches base parameter path`);
339
608
  throw new Error(
340
609
  `Deleting the path (path: ${path}) that matches the base path (awsSsmParameterPath: ${args.awsSsmParameterPath}) is prohibited.`
341
610
  );
342
611
  }
343
612
  if (args.force !== true) {
344
- const res = await inquirer3.prompt({
613
+ debug(`Prompting user for confirmation...`);
614
+ const res = await inquirer4.prompt({
345
615
  type: "confirm",
346
616
  name: "delete",
347
617
  message: `Are you sure you want to delete '${path}'?`
348
618
  });
349
619
  if (!res.delete) {
620
+ debug(`User declined, aborting`);
350
621
  log("Doing nothing.");
351
622
  return;
352
623
  }
624
+ debug(`User confirmed`);
625
+ } else {
626
+ debug(`Force flag set, skipping confirmation`);
353
627
  }
628
+ debug(`Sending delete command to SSM for: ${path}`);
354
629
  await client.send(
355
630
  new DeleteParameterCommand({
356
631
  Name: path
357
632
  })
358
633
  );
634
+ debug(`Parameter deleted successfully`);
359
635
  log("\u{1F44D}");
360
636
  };
361
637
  };
@@ -364,7 +640,17 @@ Example: "/root/myParam" or "service/secret"`,
364
640
  yargs(process.argv.slice(2)).env("AXIOM").scriptName("axiom").option("config", {
365
641
  alias: "c",
366
642
  string: true
367
- }).command(new Config()).command(new ParamsCommand()).strict().usage(
643
+ }).option("debug", {
644
+ alias: "d",
645
+ type: "boolean",
646
+ description: "Enable debug output",
647
+ default: false,
648
+ global: true
649
+ }).middleware((argv) => {
650
+ if (argv.debug) {
651
+ process.env.AXIOM_DEBUG = "true";
652
+ }
653
+ }).command(new Init()).command(new Config()).command(new ParamsCommand()).strict().usage(
368
654
  `
369
655
  Axiom - an AWS focused config cli
370
656
 
@@ -375,6 +661,7 @@ USAGE:
375
661
 
376
662
  export {
377
663
  Config,
664
+ Init,
378
665
  ParamsCommand,
379
666
  commonOptions,
380
667
  awsOptions,
package/dist/index.d.mts CHANGED
@@ -19,6 +19,24 @@ declare class Config<U extends ConfigOptions> implements CommandModule<object, U
19
19
  loadConfig: (args: ConfigOptions) => Promise<_dsmrt_axiom_config.ConfigContainer & object>;
20
20
  }
21
21
 
22
+ interface InitOptions {
23
+ force?: boolean;
24
+ name?: string;
25
+ account?: string;
26
+ region?: string;
27
+ profile?: string;
28
+ customTypes?: boolean;
29
+ }
30
+ declare class Init<U extends InitOptions> implements CommandModule<object, U> {
31
+ command: string;
32
+ describe: string;
33
+ builder: CommandBuilder<object, U>;
34
+ handler: (args: ArgumentsCamelCase<U>) => Promise<void>;
35
+ private generateTypesFile;
36
+ private generateBaseConfig;
37
+ private generateDevConfig;
38
+ }
39
+
22
40
  type Options = object;
23
41
  declare class ParamsCommand<U extends Options> implements CommandModule<object, U> {
24
42
  command: string;
@@ -74,4 +92,4 @@ declare const awsOptions: () => {
74
92
  [key: string]: Options$1;
75
93
  };
76
94
 
77
- export { Config, DeleteCommand, type DeleteOptions, GetCommand, type GetOptions, type Item, ParamsCommand, SetCommand, type SetOptions, awsOptions, commonOptions };
95
+ export { Config, DeleteCommand, type DeleteOptions, GetCommand, type GetOptions, Init, type Item, ParamsCommand, SetCommand, type SetOptions, awsOptions, commonOptions };
package/dist/index.d.ts CHANGED
@@ -19,6 +19,24 @@ declare class Config<U extends ConfigOptions> implements CommandModule<object, U
19
19
  loadConfig: (args: ConfigOptions) => Promise<_dsmrt_axiom_config.ConfigContainer & object>;
20
20
  }
21
21
 
22
+ interface InitOptions {
23
+ force?: boolean;
24
+ name?: string;
25
+ account?: string;
26
+ region?: string;
27
+ profile?: string;
28
+ customTypes?: boolean;
29
+ }
30
+ declare class Init<U extends InitOptions> implements CommandModule<object, U> {
31
+ command: string;
32
+ describe: string;
33
+ builder: CommandBuilder<object, U>;
34
+ handler: (args: ArgumentsCamelCase<U>) => Promise<void>;
35
+ private generateTypesFile;
36
+ private generateBaseConfig;
37
+ private generateDevConfig;
38
+ }
39
+
22
40
  type Options = object;
23
41
  declare class ParamsCommand<U extends Options> implements CommandModule<object, U> {
24
42
  command: string;
@@ -74,4 +92,4 @@ declare const awsOptions: () => {
74
92
  [key: string]: Options$1;
75
93
  };
76
94
 
77
- export { Config, DeleteCommand, type DeleteOptions, GetCommand, type GetOptions, type Item, ParamsCommand, SetCommand, type SetOptions, awsOptions, commonOptions };
95
+ export { Config, DeleteCommand, type DeleteOptions, GetCommand, type GetOptions, Init, type Item, ParamsCommand, SetCommand, type SetOptions, awsOptions, commonOptions };