@hyperdrive.bot/cli 1.0.2
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 +1598 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +3 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/account/add.d.ts +16 -0
- package/dist/commands/account/add.js +185 -0
- package/dist/commands/account/list.d.ts +6 -0
- package/dist/commands/account/list.js +37 -0
- package/dist/commands/account/remove.d.ts +11 -0
- package/dist/commands/account/remove.js +57 -0
- package/dist/commands/auth/login.d.ts +16 -0
- package/dist/commands/auth/login.js +178 -0
- package/dist/commands/auth/logout.d.ts +6 -0
- package/dist/commands/auth/logout.js +39 -0
- package/dist/commands/auth/refresh.d.ts +6 -0
- package/dist/commands/auth/refresh.js +66 -0
- package/dist/commands/auth/status.d.ts +6 -0
- package/dist/commands/auth/status.js +63 -0
- package/dist/commands/ci/account/create.d.ts +16 -0
- package/dist/commands/ci/account/create.js +158 -0
- package/dist/commands/ci/account/delete.d.ts +14 -0
- package/dist/commands/ci/account/delete.js +88 -0
- package/dist/commands/ci/account/list.d.ts +10 -0
- package/dist/commands/ci/account/list.js +65 -0
- package/dist/commands/config/get.d.ts +9 -0
- package/dist/commands/config/get.js +37 -0
- package/dist/commands/config/set.d.ts +10 -0
- package/dist/commands/config/set.js +48 -0
- package/dist/commands/config/show.d.ts +6 -0
- package/dist/commands/config/show.js +10 -0
- package/dist/commands/deployment/create.d.ts +30 -0
- package/dist/commands/deployment/create.js +188 -0
- package/dist/commands/deployment/get.d.ts +13 -0
- package/dist/commands/deployment/get.js +101 -0
- package/dist/commands/deployment/launch.d.ts +15 -0
- package/dist/commands/deployment/launch.js +105 -0
- package/dist/commands/deployment/list.d.ts +11 -0
- package/dist/commands/deployment/list.js +91 -0
- package/dist/commands/domain/current.d.ts +6 -0
- package/dist/commands/domain/current.js +18 -0
- package/dist/commands/domain/list.d.ts +6 -0
- package/dist/commands/domain/list.js +42 -0
- package/dist/commands/domain/switch.d.ts +9 -0
- package/dist/commands/domain/switch.js +40 -0
- package/dist/commands/example.d.ts +13 -0
- package/dist/commands/example.js +24 -0
- package/dist/commands/git/connect.d.ts +10 -0
- package/dist/commands/git/connect.js +56 -0
- package/dist/commands/git/disconnect.d.ts +11 -0
- package/dist/commands/git/disconnect.js +93 -0
- package/dist/commands/git/list.d.ts +10 -0
- package/dist/commands/git/list.js +53 -0
- package/dist/commands/git/sync.d.ts +18 -0
- package/dist/commands/git/sync.js +235 -0
- package/dist/commands/init.d.ts +188 -0
- package/dist/commands/init.js +817 -0
- package/dist/commands/jira/connect.d.ts +9 -0
- package/dist/commands/jira/connect.js +141 -0
- package/dist/commands/jira/status.d.ts +9 -0
- package/dist/commands/jira/status.js +118 -0
- package/dist/commands/module/analyze.d.ts +29 -0
- package/dist/commands/module/analyze.js +201 -0
- package/dist/commands/module/create.d.ts +42 -0
- package/dist/commands/module/create.js +498 -0
- package/dist/commands/module/destroy.d.ts +11 -0
- package/dist/commands/module/destroy.js +77 -0
- package/dist/commands/module/get.d.ts +10 -0
- package/dist/commands/module/get.js +43 -0
- package/dist/commands/module/link.d.ts +15 -0
- package/dist/commands/module/link.js +175 -0
- package/dist/commands/module/list.d.ts +9 -0
- package/dist/commands/module/list.js +51 -0
- package/dist/commands/module/reanalyze.d.ts +30 -0
- package/dist/commands/module/reanalyze.js +206 -0
- package/dist/commands/module/update.d.ts +27 -0
- package/dist/commands/module/update.js +102 -0
- package/dist/commands/parameter/add.d.ts +15 -0
- package/dist/commands/parameter/add.js +99 -0
- package/dist/commands/parameter/backfill.d.ts +12 -0
- package/dist/commands/parameter/backfill.js +113 -0
- package/dist/commands/parameter/clear.d.ts +14 -0
- package/dist/commands/parameter/clear.js +95 -0
- package/dist/commands/parameter/list.d.ts +14 -0
- package/dist/commands/parameter/list.js +92 -0
- package/dist/commands/parameter/pull.d.ts +14 -0
- package/dist/commands/parameter/pull.js +124 -0
- package/dist/commands/parameter/remove.d.ts +15 -0
- package/dist/commands/parameter/remove.js +90 -0
- package/dist/commands/parameter/sync.d.ts +14 -0
- package/dist/commands/parameter/sync.js +153 -0
- package/dist/commands/parameter/update.d.ts +15 -0
- package/dist/commands/parameter/update.js +100 -0
- package/dist/commands/stage/create.d.ts +28 -0
- package/dist/commands/stage/create.js +312 -0
- package/dist/commands/stage/list.d.ts +9 -0
- package/dist/commands/stage/list.js +63 -0
- package/dist/commands/test-api.d.ts +9 -0
- package/dist/commands/test-api.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/auth-service.d.ts +84 -0
- package/dist/services/auth-service.js +240 -0
- package/dist/services/git.d.ts +46 -0
- package/dist/services/git.js +409 -0
- package/dist/services/hyperdrive-sigv4.d.ts +449 -0
- package/dist/services/hyperdrive-sigv4.js +375 -0
- package/dist/services/hyperdrive.d.ts +87 -0
- package/dist/services/hyperdrive.js +108 -0
- package/dist/services/log-tailer.d.ts +95 -0
- package/dist/services/log-tailer.js +242 -0
- package/dist/services/tenant-service.d.ts +106 -0
- package/dist/services/tenant-service.js +332 -0
- package/dist/utils/account-flow.d.ts +74 -0
- package/dist/utils/account-flow.js +228 -0
- package/dist/utils/auth-flow.d.ts +146 -0
- package/dist/utils/auth-flow.js +477 -0
- package/dist/utils/git-flow.d.ts +72 -0
- package/dist/utils/git-flow.js +232 -0
- package/dist/utils/jira-flow.d.ts +71 -0
- package/dist/utils/jira-flow.js +120 -0
- package/dist/utils/summary-display.d.ts +59 -0
- package/dist/utils/summary-display.js +140 -0
- package/dist/utils/validation.d.ts +15 -0
- package/dist/utils/validation.js +32 -0
- package/oclif.manifest.json +2819 -0
- package/package.json +112 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
6
|
+
export default class ParameterAdd extends Command {
|
|
7
|
+
static description = 'Add a new parameter to SSM Parameter Store';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --key="API_URL" --value="https://api.example.com" --stage="dev"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --key="DB_HOST" --value="localhost" --specific',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --key="PORT" --value="3000"',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
accountId: Flags.string({
|
|
15
|
+
description: 'AWS Account ID',
|
|
16
|
+
env: 'AWS_ACCOUNT_ID',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
domain: Flags.string({
|
|
20
|
+
char: 'd',
|
|
21
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
22
|
+
}),
|
|
23
|
+
key: Flags.string({
|
|
24
|
+
char: 'k',
|
|
25
|
+
description: 'Parameter key/name',
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
28
|
+
moduleSlug: Flags.string({
|
|
29
|
+
char: 'm',
|
|
30
|
+
default() {
|
|
31
|
+
if (existsSync('.hyperdrive.json')) {
|
|
32
|
+
const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
|
|
33
|
+
return hyperdriveConfig.slug;
|
|
34
|
+
}
|
|
35
|
+
return '';
|
|
36
|
+
},
|
|
37
|
+
description: 'Module slug',
|
|
38
|
+
required: true,
|
|
39
|
+
}),
|
|
40
|
+
specific: Flags.boolean({
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Make parameter specific to this project (default: global)',
|
|
43
|
+
}),
|
|
44
|
+
stage: Flags.string({
|
|
45
|
+
char: 's',
|
|
46
|
+
default: 'all',
|
|
47
|
+
description: 'Stage for the parameter (default: all stages)',
|
|
48
|
+
}),
|
|
49
|
+
value: Flags.string({
|
|
50
|
+
char: 'v',
|
|
51
|
+
description: 'Parameter value',
|
|
52
|
+
required: true,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
async run() {
|
|
56
|
+
const { flags } = await this.parse(ParameterAdd);
|
|
57
|
+
const confirmation = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
default: true,
|
|
60
|
+
message: chalk.yellow(`Add parameter ${chalk.cyan(flags.key)} = ${chalk.green(flags.value)} to ${flags.specific ? 'project-specific' : 'global'} parameters for stage ${chalk.cyan(flags.stage)}?`),
|
|
61
|
+
name: 'confirm',
|
|
62
|
+
type: 'confirm',
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
if (!confirmation.confirm) {
|
|
66
|
+
this.log(chalk.red('Operation cancelled.'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
71
|
+
// Encode single parameter in key=value format
|
|
72
|
+
const paramContent = `${flags.key}=${flags.value}`;
|
|
73
|
+
const encodedContent = Buffer.from(paramContent).toString('base64');
|
|
74
|
+
const result = await service.parameterAdd({
|
|
75
|
+
accountId: flags.accountId,
|
|
76
|
+
encodedContent,
|
|
77
|
+
projectSlug: flags.moduleSlug, // API still expects projectSlug
|
|
78
|
+
specific: flags.specific,
|
|
79
|
+
stage: flags.stage,
|
|
80
|
+
});
|
|
81
|
+
if (result.errors && Array.isArray(result.errors) && result.errors.length > 0) {
|
|
82
|
+
this.log(chalk.red('ā Failed to add parameter:'));
|
|
83
|
+
for (const error of result.errors) {
|
|
84
|
+
this.log(chalk.red(` - ${error.key}: ${error.error}`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.log(chalk.green('ā
Parameter added successfully!'));
|
|
89
|
+
this.log(chalk.gray(` Key: ${flags.key}`));
|
|
90
|
+
this.log(chalk.gray(` Scope: ${flags.specific ? 'Project-specific' : 'Global'}`));
|
|
91
|
+
this.log(chalk.gray(` Stage: ${flags.stage}`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.error('Error:', error);
|
|
96
|
+
this.error('An error occurred while adding the parameter');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ParameterBackfill extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
accountId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
'dry-run': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
'no-dry-run': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Command, Flags, ux } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
5
|
+
export default class ParameterBackfill extends Command {
|
|
6
|
+
static description = 'Backfill existing SSM parameters to DynamoDB (one-time migration)';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --accountId="123456789012" --dry-run',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --accountId="123456789012"',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
accountId: Flags.string({
|
|
13
|
+
description: 'AWS Account ID to backfill from',
|
|
14
|
+
env: 'AWS_ACCOUNT_ID',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
domain: Flags.string({
|
|
18
|
+
char: 'd',
|
|
19
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
20
|
+
}),
|
|
21
|
+
'dry-run': Flags.boolean({
|
|
22
|
+
default: true,
|
|
23
|
+
description: 'Preview what would be imported without making changes (default: true)',
|
|
24
|
+
}),
|
|
25
|
+
'no-dry-run': Flags.boolean({
|
|
26
|
+
default: false,
|
|
27
|
+
description: 'Actually perform the backfill (writes to DynamoDB)',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { flags } = await this.parse(ParameterBackfill);
|
|
32
|
+
// Determine if this is a dry run
|
|
33
|
+
const dryRun = flags['no-dry-run'] ? false : flags['dry-run'];
|
|
34
|
+
if (!dryRun) {
|
|
35
|
+
this.log(chalk.yellow('ā ļø This will import SSM parameters into DynamoDB.'));
|
|
36
|
+
this.log(chalk.gray(' Existing DynamoDB records will NOT be overwritten.'));
|
|
37
|
+
this.log('');
|
|
38
|
+
const confirmation = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
default: false,
|
|
41
|
+
message: chalk.yellow('Are you sure you want to proceed with the backfill?'),
|
|
42
|
+
name: 'confirm',
|
|
43
|
+
type: 'confirm',
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
if (!confirmation.confirm) {
|
|
47
|
+
this.log(chalk.gray('Operation cancelled.'));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
53
|
+
this.log(chalk.blue(dryRun ? 'š Running dry run (preview only)...' : 'š„ Starting backfill...'));
|
|
54
|
+
this.log(chalk.gray(` Account: ${flags.accountId}`));
|
|
55
|
+
this.log('');
|
|
56
|
+
const result = await service.parameterBackfill({
|
|
57
|
+
accountId: flags.accountId,
|
|
58
|
+
dryRun,
|
|
59
|
+
});
|
|
60
|
+
if (result.totalFound === 0) {
|
|
61
|
+
this.log(chalk.yellow('ā ļø No parameters found in SSM for this tenant'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.log(chalk.green(`ā
${dryRun ? 'Dry run complete' : 'Backfill complete'}!`));
|
|
65
|
+
this.log('');
|
|
66
|
+
this.log(chalk.cyan('š Summary:'));
|
|
67
|
+
this.log(chalk.gray(` Total found in SSM: ${result.totalFound}`));
|
|
68
|
+
this.log(chalk.green(` ${dryRun ? 'Would import' : 'Imported'}: ${result.imported}`));
|
|
69
|
+
this.log(chalk.yellow(` Skipped (already exist): ${result.skipped}`));
|
|
70
|
+
this.log(chalk.red(` Errors: ${result.errors.length}`));
|
|
71
|
+
this.log('');
|
|
72
|
+
// Show parameters table if there are any
|
|
73
|
+
if (result.parameters.length > 0) {
|
|
74
|
+
this.log(chalk.cyan('š Parameters:'));
|
|
75
|
+
const tableData = result.parameters.slice(0, 50).map(p => ({
|
|
76
|
+
key: p.key,
|
|
77
|
+
project: p.projectSlug,
|
|
78
|
+
stage: p.stage,
|
|
79
|
+
status: p.imported ? chalk.green('ā') : chalk.yellow('skipped'),
|
|
80
|
+
}));
|
|
81
|
+
ux.table(tableData, {
|
|
82
|
+
key: { header: 'Key', minWidth: 30 },
|
|
83
|
+
project: { header: 'Project', minWidth: 15 },
|
|
84
|
+
stage: { header: 'Stage', minWidth: 10 },
|
|
85
|
+
status: { header: 'Status', minWidth: 10 },
|
|
86
|
+
});
|
|
87
|
+
if (result.parameters.length > 50) {
|
|
88
|
+
this.log(chalk.gray(` ... and ${result.parameters.length - 50} more`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Show errors if any
|
|
92
|
+
if (result.errors.length > 0) {
|
|
93
|
+
this.log('');
|
|
94
|
+
this.log(chalk.red('ā Errors:'));
|
|
95
|
+
for (const error of result.errors.slice(0, 10)) {
|
|
96
|
+
this.log(chalk.red(` ${error.path}: ${error.error}`));
|
|
97
|
+
}
|
|
98
|
+
if (result.errors.length > 10) {
|
|
99
|
+
this.log(chalk.gray(` ... and ${result.errors.length - 10} more errors`));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (dryRun && result.imported > 0) {
|
|
103
|
+
this.log('');
|
|
104
|
+
this.log(chalk.cyan('š” To perform the actual backfill, run:'));
|
|
105
|
+
this.log(chalk.white(` hd parameter backfill --accountId="${flags.accountId}" --no-dry-run`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('Error:', error);
|
|
110
|
+
this.error('An error occurred while backfilling parameters');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ParameterClear extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
accountId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
specific: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
6
|
+
export default class ParameterClear extends Command {
|
|
7
|
+
static description = 'Clear all parameters for a project and stage (DANGEROUS)';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --stage="dev"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --stage="prod" --specific --force',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
accountId: Flags.string({
|
|
14
|
+
description: 'AWS Account ID',
|
|
15
|
+
env: 'AWS_ACCOUNT_ID',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
domain: Flags.string({
|
|
19
|
+
char: 'd',
|
|
20
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
21
|
+
}),
|
|
22
|
+
force: Flags.boolean({
|
|
23
|
+
char: 'f',
|
|
24
|
+
default: false,
|
|
25
|
+
description: 'Skip confirmation prompt (DANGEROUS)',
|
|
26
|
+
}),
|
|
27
|
+
moduleSlug: Flags.string({
|
|
28
|
+
char: 'm',
|
|
29
|
+
default() {
|
|
30
|
+
if (existsSync('.hyperdrive.json')) {
|
|
31
|
+
const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
|
|
32
|
+
return hyperdriveConfig.slug;
|
|
33
|
+
}
|
|
34
|
+
return '';
|
|
35
|
+
},
|
|
36
|
+
description: 'Module slug',
|
|
37
|
+
required: true,
|
|
38
|
+
}),
|
|
39
|
+
specific: Flags.boolean({
|
|
40
|
+
default: false,
|
|
41
|
+
description: 'Clear only project-specific parameters (default: global)',
|
|
42
|
+
}),
|
|
43
|
+
stage: Flags.string({
|
|
44
|
+
char: 's',
|
|
45
|
+
default: 'all',
|
|
46
|
+
description: 'Stage to clear parameters for',
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
async run() {
|
|
50
|
+
const { flags } = await this.parse(ParameterClear);
|
|
51
|
+
if (!flags.force) {
|
|
52
|
+
this.log(chalk.red('ā ļø WARNING: This will delete ALL parameters for the specified scope!'));
|
|
53
|
+
const typeConfirmation = await inquirer.prompt([
|
|
54
|
+
{
|
|
55
|
+
message: chalk.red('Type "DELETE" to confirm:'),
|
|
56
|
+
name: 'confirmText',
|
|
57
|
+
validate: (input) => input === 'DELETE' || 'You must type DELETE to confirm',
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
if (typeConfirmation.confirmText !== 'DELETE') {
|
|
61
|
+
this.log(chalk.yellow('Operation cancelled.'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const confirmation = await inquirer.prompt([
|
|
65
|
+
{
|
|
66
|
+
default: false,
|
|
67
|
+
message: chalk.red(`Are you ABSOLUTELY SURE you want to clear ALL ${flags.specific ? 'project-specific' : 'global'} parameters for stage ${chalk.cyan(flags.stage)}?`),
|
|
68
|
+
name: 'confirm',
|
|
69
|
+
type: 'confirm',
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
if (!confirmation.confirm) {
|
|
73
|
+
this.log(chalk.yellow('Operation cancelled.'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
79
|
+
this.log(chalk.blue('šļø Clearing parameters...'));
|
|
80
|
+
await service.parameterClear({
|
|
81
|
+
accountId: flags.accountId,
|
|
82
|
+
projectSlug: flags.moduleSlug, // API still expects projectSlug
|
|
83
|
+
specific: flags.specific,
|
|
84
|
+
stage: flags.stage,
|
|
85
|
+
});
|
|
86
|
+
this.log(chalk.green('ā
All parameters cleared successfully!'));
|
|
87
|
+
this.log(chalk.gray(` Scope: ${flags.specific ? 'Project-specific' : 'Global'}`));
|
|
88
|
+
this.log(chalk.gray(` Stage: ${flags.stage}`));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('Error:', error);
|
|
92
|
+
this.error('An error occurred while clearing parameters');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ParameterList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
accountId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
'all-stages': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
8
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
specific: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command, Flags, ux } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
5
|
+
export default class ParameterList extends Command {
|
|
6
|
+
static description = 'List all parameters for a project and stage';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --stage="dev"',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --stage="prod" --specific',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --all-stages',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
accountId: Flags.string({
|
|
14
|
+
description: 'AWS Account ID',
|
|
15
|
+
env: 'AWS_ACCOUNT_ID',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
'all-stages': Flags.boolean({
|
|
19
|
+
default: false,
|
|
20
|
+
description: 'List parameters from all stages',
|
|
21
|
+
}),
|
|
22
|
+
domain: Flags.string({
|
|
23
|
+
char: 'd',
|
|
24
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
25
|
+
}),
|
|
26
|
+
moduleSlug: Flags.string({
|
|
27
|
+
char: 'm',
|
|
28
|
+
default() {
|
|
29
|
+
if (existsSync('.hyperdrive.json')) {
|
|
30
|
+
const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
|
|
31
|
+
return hyperdriveConfig.slug;
|
|
32
|
+
}
|
|
33
|
+
return '';
|
|
34
|
+
},
|
|
35
|
+
description: 'Module slug',
|
|
36
|
+
required: true,
|
|
37
|
+
}),
|
|
38
|
+
specific: Flags.boolean({
|
|
39
|
+
default: false,
|
|
40
|
+
description: 'List only project-specific parameters',
|
|
41
|
+
}),
|
|
42
|
+
stage: Flags.string({
|
|
43
|
+
char: 's',
|
|
44
|
+
default: 'all',
|
|
45
|
+
description: 'Stage to list parameters for',
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
async run() {
|
|
49
|
+
const { flags } = await this.parse(ParameterList);
|
|
50
|
+
try {
|
|
51
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
52
|
+
this.log(chalk.blue('š Fetching parameters...'));
|
|
53
|
+
const result = await service.parameterList({
|
|
54
|
+
accountId: flags.accountId,
|
|
55
|
+
projectSlug: flags.moduleSlug, // API still expects projectSlug
|
|
56
|
+
specific: flags.specific,
|
|
57
|
+
stage: flags.stage,
|
|
58
|
+
});
|
|
59
|
+
if (!result.parameters || Object.keys(result.parameters).length === 0) {
|
|
60
|
+
this.log(chalk.yellow('ā ļø No parameters found'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.log(chalk.green(`ā
Found ${Object.keys(result.parameters).length} parameter(s)\n`));
|
|
64
|
+
// Display parameters in a table
|
|
65
|
+
const tableData = Object.entries(result.parameters).map(([key, value]) => ({
|
|
66
|
+
key,
|
|
67
|
+
value: value.substring(0, 50) + (value.length > 50 ? '...' : ''),
|
|
68
|
+
}));
|
|
69
|
+
ux.table(tableData, {
|
|
70
|
+
key: {
|
|
71
|
+
header: 'Parameter Key',
|
|
72
|
+
minWidth: 20,
|
|
73
|
+
},
|
|
74
|
+
value: {
|
|
75
|
+
header: 'Value',
|
|
76
|
+
minWidth: 30,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
// Display summary
|
|
80
|
+
this.log(chalk.gray('\nš Summary:'));
|
|
81
|
+
if (result.pathsParametersCount) {
|
|
82
|
+
for (const [path, count] of Object.entries(result.pathsParametersCount)) {
|
|
83
|
+
this.log(chalk.gray(` ${path}: ${count} parameter(s)`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error('Error:', error);
|
|
89
|
+
this.error('An error occurred while listing parameters');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ParameterPull extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
accountId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
backup: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
8
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
file: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
6
|
+
export default class ParameterPull extends Command {
|
|
7
|
+
static description = 'Pull parameters from SSM Parameter Store to a .env file';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --stage="dev"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --stage="prod" --file=".env.prod"',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --stage="dev" --no-backup',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
accountId: Flags.string({
|
|
15
|
+
description: 'AWS Account ID',
|
|
16
|
+
env: 'AWS_ACCOUNT_ID',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
backup: Flags.boolean({
|
|
20
|
+
allowNo: true,
|
|
21
|
+
default: true,
|
|
22
|
+
description: 'Create .env.backup before overwriting (default: true)',
|
|
23
|
+
}),
|
|
24
|
+
domain: Flags.string({
|
|
25
|
+
char: 'd',
|
|
26
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
27
|
+
}),
|
|
28
|
+
file: Flags.string({
|
|
29
|
+
char: 'f',
|
|
30
|
+
default: '.env',
|
|
31
|
+
description: 'Output file path',
|
|
32
|
+
}),
|
|
33
|
+
moduleSlug: Flags.string({
|
|
34
|
+
char: 'm',
|
|
35
|
+
default() {
|
|
36
|
+
if (existsSync('.hyperdrive.json')) {
|
|
37
|
+
const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
|
|
38
|
+
return hyperdriveConfig.slug;
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
},
|
|
42
|
+
description: 'Module slug',
|
|
43
|
+
required: true,
|
|
44
|
+
}),
|
|
45
|
+
stage: Flags.string({
|
|
46
|
+
char: 's',
|
|
47
|
+
description: 'Stage to pull parameters from',
|
|
48
|
+
required: true,
|
|
49
|
+
}),
|
|
50
|
+
};
|
|
51
|
+
async run() {
|
|
52
|
+
const { flags } = await this.parse(ParameterPull);
|
|
53
|
+
const { accountId, backup, domain, file, moduleSlug, stage } = flags;
|
|
54
|
+
const envPath = path.resolve(process.cwd(), file);
|
|
55
|
+
try {
|
|
56
|
+
const service = new HyperdriveSigV4Service(domain);
|
|
57
|
+
this.log(chalk.blue('š Pulling parameters from SSM...'));
|
|
58
|
+
this.log(chalk.gray(` Module: ${moduleSlug}`));
|
|
59
|
+
this.log(chalk.gray(` Stage: ${stage}`));
|
|
60
|
+
this.log(chalk.gray(` Output: ${file}`));
|
|
61
|
+
// Fetch global parameters for this stage
|
|
62
|
+
const globalResult = await service.parameterList({
|
|
63
|
+
accountId,
|
|
64
|
+
projectSlug: moduleSlug,
|
|
65
|
+
specific: false,
|
|
66
|
+
stage,
|
|
67
|
+
});
|
|
68
|
+
// Fetch project-specific parameters for this stage
|
|
69
|
+
const specificResult = await service.parameterList({
|
|
70
|
+
accountId,
|
|
71
|
+
projectSlug: moduleSlug,
|
|
72
|
+
specific: true,
|
|
73
|
+
stage,
|
|
74
|
+
});
|
|
75
|
+
// Merge: project-specific overrides global
|
|
76
|
+
const mergedParams = {
|
|
77
|
+
...globalResult.parameters,
|
|
78
|
+
...specificResult.parameters,
|
|
79
|
+
};
|
|
80
|
+
const paramCount = Object.keys(mergedParams).length;
|
|
81
|
+
const globalCount = Object.keys(globalResult.parameters).length;
|
|
82
|
+
const specificCount = Object.keys(specificResult.parameters).length;
|
|
83
|
+
if (paramCount === 0) {
|
|
84
|
+
this.warn(chalk.yellow('No parameters found for this stage/module combination.'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.log(chalk.gray(` Found: ${globalCount} global, ${specificCount} project-specific`));
|
|
88
|
+
// Create backup if file exists and backup flag is true
|
|
89
|
+
if (backup && existsSync(envPath)) {
|
|
90
|
+
const backupPath = `${envPath}.backup`;
|
|
91
|
+
copyFileSync(envPath, backupPath);
|
|
92
|
+
this.log(chalk.gray(` Backup: ${backupPath}`));
|
|
93
|
+
}
|
|
94
|
+
// Convert to .env format
|
|
95
|
+
const envContent = Object.entries(mergedParams)
|
|
96
|
+
.map(([key, value]) => {
|
|
97
|
+
// Check if value is already wrapped in matching quotes
|
|
98
|
+
const isAlreadySingleQuoted = value.startsWith("'") && value.endsWith("'") && value.length >= 2;
|
|
99
|
+
const isAlreadyDoubleQuoted = value.startsWith('"') && value.endsWith('"') && value.length >= 2;
|
|
100
|
+
if (isAlreadySingleQuoted || isAlreadyDoubleQuoted) {
|
|
101
|
+
// Value is already quoted, use as-is
|
|
102
|
+
return `${key}=${value}`;
|
|
103
|
+
}
|
|
104
|
+
// Only quote if value contains spaces, newlines, or shell special chars that need protection
|
|
105
|
+
const needsQuotes = /[\s\n\r]/.test(value) || value.includes('$') || value.includes('`');
|
|
106
|
+
if (needsQuotes) {
|
|
107
|
+
// Use single quotes to avoid shell expansion, escape any single quotes in the value
|
|
108
|
+
const escapedValue = value.replace(/'/g, "'\\''");
|
|
109
|
+
return `${key}='${escapedValue}'`;
|
|
110
|
+
}
|
|
111
|
+
return `${key}=${value}`;
|
|
112
|
+
})
|
|
113
|
+
.sort()
|
|
114
|
+
.join('\n');
|
|
115
|
+
// Write to file
|
|
116
|
+
writeFileSync(envPath, envContent + '\n', 'utf8');
|
|
117
|
+
this.log(chalk.green(`\nā
Successfully pulled ${paramCount} parameters to ${file}`));
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error('Error:', error);
|
|
121
|
+
this.error('An error occurred while pulling parameters');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ParameterRemove extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
accountId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
key: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
specific: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|