terruvim-cli 0.0.1 → 0.1.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 CHANGED
@@ -20,7 +20,7 @@ $ npm install -g terruvim-cli
20
20
  $ terruvim COMMAND
21
21
  running command...
22
22
  $ terruvim (--version)
23
- terruvim-cli/0.0.1 linux-x64 node-v22.21.1
23
+ terruvim-cli/0.1.0 linux-x64 node-v24.13.0
24
24
  $ terruvim --help [COMMAND]
25
25
  USAGE
26
26
  $ terruvim COMMAND
@@ -29,8 +29,8 @@ USAGE
29
29
  <!-- usagestop -->
30
30
  # Commands
31
31
  <!-- commands -->
32
+ * [`terruvim create [NAME]`](#terruvim-create-name)
32
33
  * [`terruvim help [COMMAND]`](#terruvim-help-command)
33
- * [`terruvim init [NAME]`](#terruvim-init-name)
34
34
  * [`terruvim plugins`](#terruvim-plugins)
35
35
  * [`terruvim plugins add PLUGIN`](#terruvim-plugins-add-plugin)
36
36
  * [`terruvim plugins:inspect PLUGIN...`](#terruvim-pluginsinspect-plugin)
@@ -41,46 +41,48 @@ USAGE
41
41
  * [`terruvim plugins uninstall [PLUGIN]`](#terruvim-plugins-uninstall-plugin)
42
42
  * [`terruvim plugins unlink [PLUGIN]`](#terruvim-plugins-unlink-plugin)
43
43
  * [`terruvim plugins update`](#terruvim-plugins-update)
44
+ * [`terruvim setup`](#terruvim-setup)
45
+ * [`terruvim up`](#terruvim-up)
44
46
 
45
- ## `terruvim help [COMMAND]`
47
+ ## `terruvim create [NAME]`
46
48
 
47
- Display help for terruvim.
49
+ Scaffold a new Terruvim project via Interactive Wizard
48
50
 
49
51
  ```
50
52
  USAGE
51
- $ terruvim help [COMMAND...] [-n]
53
+ $ terruvim create [NAME] [-f]
52
54
 
53
55
  ARGUMENTS
54
- [COMMAND...] Command to show help for.
56
+ [NAME] Name of the project directory
55
57
 
56
58
  FLAGS
57
- -n, --nested-commands Include all nested commands in the output.
59
+ -f, --force Overwrite existing files
58
60
 
59
61
  DESCRIPTION
60
- Display help for terruvim.
62
+ Scaffold a new Terruvim project via Interactive Wizard
61
63
  ```
62
64
 
63
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.36/src/commands/help.ts)_
65
+ _See code: [src/commands/create.ts](https://github.com/vlad-vorobei/terruvim-cli/blob/v0.1.0/src/commands/create.ts)_
64
66
 
65
- ## `terruvim init [NAME]`
67
+ ## `terruvim help [COMMAND]`
66
68
 
67
- Scaffold a new Terruvim project via Interactive Wizard
69
+ Display help for terruvim.
68
70
 
69
71
  ```
70
72
  USAGE
71
- $ terruvim init [NAME] [-f]
73
+ $ terruvim help [COMMAND...] [-n]
72
74
 
73
75
  ARGUMENTS
74
- [NAME] Name of the project directory
76
+ [COMMAND...] Command to show help for.
75
77
 
76
78
  FLAGS
77
- -f, --force Overwrite existing files
79
+ -n, --nested-commands Include all nested commands in the output.
78
80
 
79
81
  DESCRIPTION
80
- Scaffold a new Terruvim project via Interactive Wizard
82
+ Display help for terruvim.
81
83
  ```
82
84
 
83
- _See code: [src/commands/init.ts](https://github.com/vlad-vorobei/terruvim-cli/blob/v0.0.1/src/commands/init.ts)_
85
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.36/src/commands/help.ts)_
84
86
 
85
87
  ## `terruvim plugins`
86
88
 
@@ -371,4 +373,32 @@ DESCRIPTION
371
373
  ```
372
374
 
373
375
  _See code: [@oclif/plugin-plugins](https://github.com/oclif/plugin-plugins/blob/v5.4.54/src/commands/plugins/update.ts)_
376
+
377
+ ## `terruvim setup`
378
+
379
+ Initialize Pulumi stacks based on environment configs
380
+
381
+ ```
382
+ USAGE
383
+ $ terruvim setup
384
+
385
+ DESCRIPTION
386
+ Initialize Pulumi stacks based on environment configs
387
+ ```
388
+
389
+ _See code: [src/commands/setup.ts](https://github.com/vlad-vorobei/terruvim-cli/blob/v0.1.0/src/commands/setup.ts)_
390
+
391
+ ## `terruvim up`
392
+
393
+ Deploy Terruvim infrastructure (Auto-bootstrapping secrets)
394
+
395
+ ```
396
+ USAGE
397
+ $ terruvim up
398
+
399
+ DESCRIPTION
400
+ Deploy Terruvim infrastructure (Auto-bootstrapping secrets)
401
+ ```
402
+
403
+ _See code: [src/commands/up.ts](https://github.com/vlad-vorobei/terruvim-cli/blob/v0.1.0/src/commands/up.ts)_
374
404
  <!-- commandsstop -->
package/bin/dev.cmd CHANGED
File without changes
package/bin/run.cmd CHANGED
File without changes
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class Init extends Command {
2
+ export default class Create extends Command {
3
3
  static description: string;
4
4
  static args: {
5
5
  name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
@@ -13,7 +13,7 @@ const AWS_REGIONS = [
13
13
  'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1',
14
14
  'ca-central-1', 'sa-east-1'
15
15
  ];
16
- export default class Init extends Command {
16
+ export default class Create extends Command {
17
17
  static description = 'Scaffold a new Terruvim project via Interactive Wizard';
18
18
  static args = {
19
19
  name: Args.string({ description: 'Name of the project directory', required: false }),
@@ -22,7 +22,7 @@ export default class Init extends Command {
22
22
  force: Flags.boolean({ char: 'f', description: 'Overwrite existing files' }),
23
23
  };
24
24
  async run() {
25
- const { args, flags } = await this.parse(Init);
25
+ const { args, flags } = await this.parse(Create);
26
26
  this.log('🚀 Welcome to Terruvim Project Wizard\n');
27
27
  let projectName = args.name || '';
28
28
  let projectDir = '';
@@ -94,7 +94,7 @@ export default class Init extends Command {
94
94
  message: 'Which architecture do you want to use?',
95
95
  type: 'list',
96
96
  choices: [
97
- { name: 'Django Project (Default)', value: 'default', checked: true },
97
+ { name: 'DEMO', value: 'aws-default', checked: true },
98
98
  { name: 'Monolith (Simple)', value: 'monolith', disabled: true },
99
99
  { name: 'Microservices (Advanced)', value: 'microservices', disabled: true },
100
100
  ],
@@ -221,9 +221,9 @@ export default class Init extends Command {
221
221
  }
222
222
  this.log('🐳 Docker Requirement:');
223
223
  this.log(' Ensure Docker is running! It is required for initial local builds.\n');
224
- this.log('🔮 Pulumi Setup:');
225
- this.log(' 1. Sign up or Login: https://app.pulumi.com/signup');
226
- this.log(' 2. Authenticate CLI: $ pulumi login\n');
224
+ this.log('2. Setup Infrastructure Stacks:');
225
+ this.log(' Run this command inside the project to initialize Pulumi stacks:');
226
+ this.log(' $ terruvim setup\n');
227
227
  if (answers.providerName === 'aws') {
228
228
  this.log('☁️ AWS Configuration:');
229
229
  this.log(' You need an AWS Account and IAM User credentials with infrastructure access.');
@@ -0,0 +1,5 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Setup extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,107 @@
1
+ import { Command } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import fs from 'fs-extra';
4
+ import { execa } from 'execa';
5
+ import ora from 'ora';
6
+ import inquirer from 'inquirer';
7
+ export default class Setup extends Command {
8
+ static description = 'Initialize Pulumi stacks based on environment configs';
9
+ async run() {
10
+ this.log('🛠 Terruvim Project Setup\n');
11
+ const projectDir = process.cwd();
12
+ if (!fs.existsSync(path.join(projectDir, 'Pulumi.yaml'))) {
13
+ this.error('Pulumi.yaml not found! Please run this command inside a Terruvim project directory.');
14
+ }
15
+ const pulumiYaml = await fs.readFile(path.join(projectDir, 'Pulumi.yaml'), 'utf8');
16
+ const nameMatch = pulumiYaml.match(/name:\s+(.+)/);
17
+ if (!nameMatch) {
18
+ this.error('Could not parse project name from Pulumi.yaml');
19
+ }
20
+ const projectName = nameMatch[1].trim();
21
+ this.log(`📌 Project identified: ${projectName}\n`);
22
+ const loginSpinner = ora('Checking Pulumi authentication...').start();
23
+ try {
24
+ await execa('pulumi', ['whoami']);
25
+ loginSpinner.succeed('Authenticated in Pulumi');
26
+ }
27
+ catch (error) {
28
+ loginSpinner.warn('Not logged in to Pulumi.');
29
+ const { shouldLogin } = await inquirer.prompt([{
30
+ name: 'shouldLogin',
31
+ type: 'confirm',
32
+ message: 'Do you want to log in now?',
33
+ default: true
34
+ }]);
35
+ if (shouldLogin) {
36
+ try {
37
+ await execa('pulumi', ['login'], { stdio: 'inherit' });
38
+ this.log('\n');
39
+ }
40
+ catch (e) {
41
+ this.error('Login failed. Please run "pulumi login" manually.');
42
+ }
43
+ }
44
+ else {
45
+ this.error('You must be logged in to initialize stacks.');
46
+ }
47
+ }
48
+ const envsDir = path.join(projectDir, 'envs');
49
+ if (!fs.existsSync(envsDir)) {
50
+ this.error('envs directory not found. Is this a valid Terruvim project?');
51
+ }
52
+ const files = await fs.readdir(envsDir);
53
+ const envs = files
54
+ .filter(f => f.startsWith('infrastructure.') && f.endsWith('.json') && f !== 'infrastructure.json')
55
+ .map(f => {
56
+ return f.replace('infrastructure.', '').replace('.json', '');
57
+ });
58
+ if (envs.length === 0) {
59
+ this.warn('No environment specific configs found (e.g. infrastructure.dev.json). Skipping stack creation.');
60
+ return;
61
+ }
62
+ this.log(`🔎 Found environments: ${envs.join(', ')}\n`);
63
+ const availableStacks = [];
64
+ for (const env of envs) {
65
+ const stackName = `${projectName}-${env}`;
66
+ availableStacks.push(stackName); // Зберігаємо для вибору
67
+ const stackSpinner = ora(`Checking stack: ${stackName}...`).start();
68
+ try {
69
+ await execa('pulumi', ['stack', 'select', stackName], { stdio: 'ignore' });
70
+ stackSpinner.succeed(`Stack ${stackName} already exists.`);
71
+ }
72
+ catch (e) {
73
+ stackSpinner.text = `Creating stack: ${stackName}...`;
74
+ try {
75
+ await execa('pulumi', ['stack', 'init', stackName], { stdio: 'ignore' });
76
+ stackSpinner.succeed(`Stack ${stackName} created successfully!`);
77
+ }
78
+ catch (initError) {
79
+ stackSpinner.fail(`Failed to create stack ${stackName}`);
80
+ this.log(initError.stderr || initError.message);
81
+ }
82
+ }
83
+ }
84
+ this.log('\n✅ Setup complete! All stacks are initialized.\n');
85
+ if (availableStacks.length > 0) {
86
+ const { selectedStack } = await inquirer.prompt([
87
+ {
88
+ name: 'selectedStack',
89
+ type: 'list',
90
+ message: 'Which stack do you want to select as active now?',
91
+ choices: availableStacks,
92
+ default: availableStacks.find(s => s.includes('dev')) || availableStacks[0] // Пріоритет dev
93
+ }
94
+ ]);
95
+ const selectSpinner = ora(`Selecting stack ${selectedStack}...`).start();
96
+ try {
97
+ await execa('pulumi', ['stack', 'select', selectedStack]);
98
+ selectSpinner.succeed(`Active stack set to: ${selectedStack}`);
99
+ }
100
+ catch (error) {
101
+ selectSpinner.fail('Failed to select stack.');
102
+ }
103
+ }
104
+ this.log('\n🚀 You are ready to deploy!');
105
+ this.log(`👉 Run: terruvim up`);
106
+ }
107
+ }
@@ -0,0 +1,5 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Up extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,105 @@
1
+ import { Command } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import fs from 'fs-extra';
4
+ import { execa } from 'execa';
5
+ import inquirer from 'inquirer';
6
+ import ora from 'ora';
7
+ export default class Up extends Command {
8
+ static description = 'Deploy Terruvim infrastructure (Auto-bootstrapping secrets)';
9
+ async run() {
10
+ await this.parse(Up);
11
+ const projectDir = process.cwd();
12
+ this.log('🚀 Terruvim Deploy Sequence\n');
13
+ let currentStack = '';
14
+ try {
15
+ const { stdout } = await execa('pulumi', ['stack', '--show-name'], { cwd: projectDir });
16
+ currentStack = stdout.trim();
17
+ }
18
+ catch (e) {
19
+ this.error('Could not determine active Pulumi stack. Run "terruvim setup" or "pulumi stack select" first.');
20
+ }
21
+ const parts = currentStack.split('-');
22
+ const env = parts[parts.length - 1]; // беремо останню частину як env
23
+ const projectName = parts.slice(0, -1).join('-'); // все інше - ім'я проекту
24
+ this.log(`📌 Context: Project [${projectName}] | Stack [${currentStack}] | Env [${env}]`);
25
+ const resourcesPath = path.join(projectDir, 'envs', 'resources.json');
26
+ const infrastructurePath = path.join(projectDir, 'envs', `infrastructure.${env}.json`);
27
+ const secretId = `${projectName}/${env}/global-secret-base`;
28
+ const awsSpinner = ora(`Checking AWS Secret Manager: ${secretId}...`).start();
29
+ let secretExists = false;
30
+ try {
31
+ await execa('aws', ['secretsmanager', 'describe-secret', '--secret-id', secretId]);
32
+ secretExists = true;
33
+ awsSpinner.succeed('AWS Secret Manager found.');
34
+ }
35
+ catch (error) {
36
+ awsSpinner.warn('AWS Secret Manager NOT found (First run detected).');
37
+ }
38
+ if (!secretExists) {
39
+ const hasResourcesFile = await fs.pathExists(resourcesPath);
40
+ if (!hasResourcesFile) {
41
+ this.warn(`Resources file (${resourcesPath}) is missing, but secrets are also missing.`);
42
+ }
43
+ const { confirmBootstrap } = await inquirer.prompt([{
44
+ name: 'confirmBootstrap',
45
+ type: 'confirm',
46
+ message: `Secret Manager is missing. Do you want to bootstrap it now? \n (This will run 'pulumi up' to create base infrastructure, then inject resources)`,
47
+ default: true
48
+ }]);
49
+ if (!confirmBootstrap) {
50
+ this.log('Aborted by user.');
51
+ return this.exit(0);
52
+ }
53
+ const bootstrapSpinner = ora('Bootstrapping Base Infrastructure (Creating Secrets)...').start();
54
+ try {
55
+ await execa('pulumi', ['up', '--yes', '--skip-preview'], {
56
+ cwd: projectDir,
57
+ // stdio: 'inherit' // For debugging
58
+ });
59
+ bootstrapSpinner.succeed('Base infrastructure bootstrapped.');
60
+ }
61
+ catch (error) {
62
+ bootstrapSpinner.fail('Bootstrap failed.');
63
+ this.error(error);
64
+ }
65
+ try {
66
+ await execa('aws', ['secretsmanager', 'describe-secret', '--secret-id', secretId]);
67
+ }
68
+ catch (error) {
69
+ this.error(`Bootstrap finished, but AWS Secret ${secretId} is still missing. Check your Pulumi code.`);
70
+ }
71
+ if (hasResourcesFile) {
72
+ const migrationSpinner = ora('Migrating resources configuration...').start();
73
+ try {
74
+ const resourcesData = await fs.readJson(resourcesPath);
75
+ if (await fs.pathExists(infrastructurePath)) {
76
+ const infraData = await fs.readJson(infrastructurePath);
77
+ infraData['📦[resources]'] = resourcesData;
78
+ await fs.writeJson(infrastructurePath, infraData, { spaces: 2 });
79
+ await fs.remove(resourcesPath);
80
+ migrationSpinner.succeed(`Configuration migrated to ${infrastructurePath} and resources.json deleted.`);
81
+ }
82
+ else {
83
+ migrationSpinner.fail(`Target config ${infrastructurePath} not found!`);
84
+ this.exit(1);
85
+ }
86
+ }
87
+ catch (error) {
88
+ migrationSpinner.fail('Migration failed.');
89
+ this.error(error);
90
+ }
91
+ }
92
+ this.log('\n✅ Bootstrap complete. Ready for full deployment.');
93
+ this.log('Running final Pulumi Preview...');
94
+ }
95
+ try {
96
+ await execa('pulumi', ['up'], {
97
+ cwd: projectDir,
98
+ stdio: 'inherit'
99
+ });
100
+ }
101
+ catch (error) {
102
+ this.exit(1);
103
+ }
104
+ }
105
+ }
package/dist/index.d.ts CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "commands": {
3
- "init": {
3
+ "create": {
4
4
  "aliases": [],
5
5
  "args": {
6
6
  "name": {
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "hasDynamicHelp": false,
23
23
  "hiddenAliases": [],
24
- "id": "init",
24
+ "id": "create",
25
25
  "pluginAlias": "terruvim-cli",
26
26
  "pluginName": "terruvim-cli",
27
27
  "pluginType": "core",
@@ -31,9 +31,49 @@
31
31
  "relativePath": [
32
32
  "dist",
33
33
  "commands",
34
- "init.js"
34
+ "create.js"
35
+ ]
36
+ },
37
+ "setup": {
38
+ "aliases": [],
39
+ "args": {},
40
+ "description": "Initialize Pulumi stacks based on environment configs",
41
+ "flags": {},
42
+ "hasDynamicHelp": false,
43
+ "hiddenAliases": [],
44
+ "id": "setup",
45
+ "pluginAlias": "terruvim-cli",
46
+ "pluginName": "terruvim-cli",
47
+ "pluginType": "core",
48
+ "strict": true,
49
+ "enableJsonFlag": false,
50
+ "isESM": true,
51
+ "relativePath": [
52
+ "dist",
53
+ "commands",
54
+ "setup.js"
55
+ ]
56
+ },
57
+ "up": {
58
+ "aliases": [],
59
+ "args": {},
60
+ "description": "Deploy Terruvim infrastructure (Auto-bootstrapping secrets)",
61
+ "flags": {},
62
+ "hasDynamicHelp": false,
63
+ "hiddenAliases": [],
64
+ "id": "up",
65
+ "pluginAlias": "terruvim-cli",
66
+ "pluginName": "terruvim-cli",
67
+ "pluginType": "core",
68
+ "strict": true,
69
+ "enableJsonFlag": false,
70
+ "isESM": true,
71
+ "relativePath": [
72
+ "dist",
73
+ "commands",
74
+ "up.js"
35
75
  ]
36
76
  }
37
77
  },
38
- "version": "0.0.1"
78
+ "version": "0.1.0"
39
79
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "terruvim-cli",
3
3
  "description": "CLI tool to scaffold and deploy Terruvim infrastructure on AWS using Pulumi",
4
- "version": "0.0.1",
4
+ "version": "0.1.0",
5
5
  "author": "Vlad Vorobei <vladyslavvorobei@gmail.com>",
6
6
  "bin": {
7
7
  "terruvim-cli": "./bin/run.js"
@@ -83,4 +83,4 @@
83
83
  "version": "oclif readme && git add README.md"
84
84
  },
85
85
  "types": "dist/index.d.ts"
86
- }
86
+ }