cistack 1.0.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/src/index.js ADDED
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const chalk = require('chalk');
6
+ const ora = require('ora');
7
+ const inquirer = require('inquirer');
8
+
9
+ const CodebaseAnalyzer = require('./analyzers/codebase');
10
+ const HostingDetector = require('./detectors/hosting');
11
+ const FrameworkDetector = require('./detectors/framework');
12
+ const LanguageDetector = require('./detectors/language');
13
+ const TestingDetector = require('./detectors/testing');
14
+ const WorkflowGenerator = require('./generators/workflow');
15
+ const { ensureDir, writeFile, banner } = require('./utils/helpers');
16
+
17
+ class CIFlow {
18
+ constructor(options) {
19
+ this.options = options;
20
+ this.projectPath = options.projectPath;
21
+ this.outputDir = path.join(options.projectPath, options.outputDir);
22
+ this.dryRun = options.dryRun || false;
23
+ this.force = options.force || false;
24
+ this.prompt = options.prompt !== false;
25
+ this.verbose = options.verbose || false;
26
+ }
27
+
28
+ async run() {
29
+ banner();
30
+
31
+ const spinner = ora({ text: 'Scanning project...', color: 'cyan' }).start();
32
+
33
+ try {
34
+ // ── 1. Analyse the codebase ──────────────────────────────────────────
35
+ const analyzer = new CodebaseAnalyzer(this.projectPath, { verbose: this.verbose });
36
+ const codebaseInfo = await analyzer.analyse();
37
+ spinner.succeed(chalk.green('Project scanned'));
38
+
39
+ if (this.verbose) {
40
+ console.log('\n' + chalk.dim(JSON.stringify(codebaseInfo, null, 2)));
41
+ }
42
+
43
+ // ── 2. Detect everything ─────────────────────────────────────────────
44
+ spinner.start('Detecting stack...');
45
+ const [hosting, frameworks, languages, testing] = await Promise.all([
46
+ new HostingDetector(this.projectPath, codebaseInfo).detect(),
47
+ new FrameworkDetector(this.projectPath, codebaseInfo).detect(),
48
+ new LanguageDetector(this.projectPath, codebaseInfo).detect(),
49
+ new TestingDetector(this.projectPath, codebaseInfo).detect(),
50
+ ]);
51
+ spinner.succeed(chalk.green('Stack detected'));
52
+
53
+ // ── 3. Print summary ─────────────────────────────────────────────────
54
+ this._printSummary({ hosting, frameworks, languages, testing });
55
+
56
+ // ── 4. Optional interactive confirmation ─────────────────────────────
57
+ let finalConfig = { hosting, frameworks, languages, testing };
58
+ if (this.prompt) {
59
+ finalConfig = await this._interactiveConfirm(finalConfig);
60
+ }
61
+
62
+ // ── 5. Generate workflow(s) ──────────────────────────────────────────
63
+ spinner.start('Generating workflow(s)...');
64
+ const generator = new WorkflowGenerator(finalConfig, this.projectPath);
65
+ const workflows = generator.generate();
66
+ spinner.succeed(chalk.green(`Generated ${workflows.length} workflow(s)`));
67
+
68
+ // ── 6. Write files ───────────────────────────────────────────────────
69
+ if (this.dryRun) {
70
+ console.log('\n' + chalk.yellow('── DRY RUN – files not written ──\n'));
71
+ for (const wf of workflows) {
72
+ console.log(chalk.bold.cyan(`\n📄 ${wf.filename}`));
73
+ console.log(chalk.dim('─'.repeat(60)));
74
+ console.log(wf.content);
75
+ }
76
+ } else {
77
+ await this._writeWorkflows(workflows);
78
+ }
79
+
80
+ console.log('\n' + chalk.bold.green('✅ Done! Your GitHub Actions pipeline is ready.'));
81
+ if (!this.dryRun) {
82
+ console.log(chalk.dim(` → ${this.outputDir}\n`));
83
+ }
84
+ } catch (err) {
85
+ spinner.fail(chalk.red('Failed: ' + err.message));
86
+ if (this.verbose) console.error(err);
87
+ process.exit(1);
88
+ }
89
+ }
90
+
91
+ // ── helpers ──────────────────────────────────────────────────────────────
92
+
93
+ _printSummary({ hosting, frameworks, languages, testing }) {
94
+ const line = (label, value) =>
95
+ console.log(` ${chalk.dim(label.padEnd(18))} ${chalk.cyan(value || chalk.italic('none detected'))}`);
96
+
97
+ console.log('\n' + chalk.bold(' 📊 Detected Stack'));
98
+ console.log(chalk.dim(' ' + '─'.repeat(40)));
99
+ line('Languages:', languages.map((l) => l.name).join(', '));
100
+ line('Frameworks:', frameworks.map((f) => f.name).join(', '));
101
+ line('Hosting:', hosting.map((h) => h.name).join(', ') || 'none');
102
+ line('Testing:', testing.map((t) => t.name).join(', ') || 'none');
103
+ console.log('');
104
+ }
105
+
106
+ async _interactiveConfirm(config) {
107
+ const { confirmed } = await inquirer.prompt([
108
+ {
109
+ type: 'confirm',
110
+ name: 'confirmed',
111
+ message: 'Does this look correct? Generate pipeline with these settings?',
112
+ default: true,
113
+ },
114
+ ]);
115
+
116
+ if (!confirmed) {
117
+ console.log(chalk.yellow('\nCustomisation mode – answer the prompts below:\n'));
118
+
119
+ const hostingChoices = ['firebase', 'vercel', 'netlify', 'aws', 'gcp', 'azure', 'heroku', 'render', 'railway', 'none'];
120
+ const { customHosting } = await inquirer.prompt([
121
+ {
122
+ type: 'checkbox',
123
+ name: 'customHosting',
124
+ message: 'Select hosting platform(s):',
125
+ choices: hostingChoices,
126
+ default: config.hosting.map((h) => h.name.toLowerCase()),
127
+ },
128
+ ]);
129
+
130
+ config.hosting = customHosting
131
+ .filter((h) => h !== 'none')
132
+ .map((h) => ({ name: h, confidence: 1.0, manual: true }));
133
+ }
134
+
135
+ return config;
136
+ }
137
+
138
+ async _writeWorkflows(workflows) {
139
+ ensureDir(this.outputDir);
140
+
141
+ for (const wf of workflows) {
142
+ const filePath = path.join(this.outputDir, wf.filename);
143
+ const exists = fs.existsSync(filePath);
144
+
145
+ if (exists && !this.force) {
146
+ const { overwrite } = await inquirer.prompt([
147
+ {
148
+ type: 'confirm',
149
+ name: 'overwrite',
150
+ message: `${wf.filename} already exists. Overwrite?`,
151
+ default: false,
152
+ },
153
+ ]);
154
+ if (!overwrite) {
155
+ console.log(chalk.dim(` Skipped ${wf.filename}`));
156
+ continue;
157
+ }
158
+ }
159
+
160
+ writeFile(filePath, wf.content);
161
+ console.log(chalk.green(` ✔ Written: ${wf.filename}`));
162
+ }
163
+ }
164
+ }
165
+
166
+ module.exports = CIFlow;
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const chalk = require('chalk');
6
+
7
+ function ensureDir(dirPath) {
8
+ if (!fs.existsSync(dirPath)) {
9
+ fs.mkdirSync(dirPath, { recursive: true });
10
+ }
11
+ }
12
+
13
+ function writeFile(filePath, content) {
14
+ ensureDir(path.dirname(filePath));
15
+ fs.writeFileSync(filePath, content, 'utf8');
16
+ }
17
+
18
+ function banner() {
19
+ console.log('\n' + chalk.bold.cyan(' ██████╗██╗███████╗██╗ ██████╗ ██╗ ██╗'));
20
+ console.log(chalk.bold.cyan(' ██╔════╝██║██╔════╝██║ ██╔═══██╗██║ ██║'));
21
+ console.log(chalk.bold.cyan(' ██║ ██║█████╗ ██║ ██║ ██║██║ █╗ ██║'));
22
+ console.log(chalk.bold.cyan(' ██║ ██║██╔══╝ ██║ ██║ ██║██║███╗██║'));
23
+ console.log(chalk.bold.cyan(' ╚██████╗██║██║ ███████╗╚██████╔╝╚███╔███╔╝'));
24
+ console.log(chalk.bold.cyan(' ╚═════╝╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝ '));
25
+ console.log('');
26
+ console.log(' ' + chalk.dim('GitHub Actions pipeline generator'));
27
+ console.log(' ' + chalk.dim('─'.repeat(44)));
28
+ console.log('');
29
+ }
30
+
31
+ module.exports = { ensureDir, writeFile, banner };