cistack 3.2.0 → 4.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/.github/workflows/ci.yml +70 -0
- package/README.md +202 -130
- package/bin/ciflow.js +1 -1
- package/index.d.ts +90 -0
- package/package.json +4 -2
- package/src/analyzers/codebase.js +43 -6
- package/src/analyzers/monorepo.js +7 -7
- package/src/analyzers/workflow.js +10 -2
- package/src/config/loader.js +65 -10
- package/src/detectors/framework.js +29 -25
- package/src/detectors/hosting.js +25 -10
- package/src/detectors/language.js +2 -2
- package/src/detectors/release.js +29 -8
- package/src/detectors/testing.js +18 -2
- package/src/generators/dependabot.js +24 -3
- package/src/generators/release.js +25 -10
- package/src/generators/workflow.js +316 -89
- package/src/index.js +21 -16
- package/src/utils/helpers.js +21 -7
- package/tests/run.js +808 -0
package/src/index.js
CHANGED
|
@@ -42,17 +42,16 @@ class CIFlow {
|
|
|
42
42
|
try {
|
|
43
43
|
// ── 1. Load cistack.config.js ─────────────────────────────────────
|
|
44
44
|
const configLoader = new ConfigLoader(this.projectPath);
|
|
45
|
-
const userConfig = configLoader.load();
|
|
45
|
+
const userConfig = await configLoader.load();
|
|
46
46
|
if (Object.keys(userConfig).length > 0) {
|
|
47
47
|
spinner.info(chalk.cyan('cistack.config.js loaded'));
|
|
48
|
-
spinner.start('Scanning project...');
|
|
49
|
-
// Allow config to override outputDir
|
|
50
48
|
if (userConfig.outputDir) {
|
|
51
49
|
this.outputDir = path.join(this.projectPath, userConfig.outputDir);
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
// ── 2. Analyse the codebase ───────────────────────────────────────
|
|
54
|
+
if (!spinner.isSpinning) spinner.start('Scanning project...');
|
|
56
55
|
const analyzer = new CodebaseAnalyzer(this.projectPath, { verbose: this.verbose });
|
|
57
56
|
const codebaseInfo = await analyzer.analyse();
|
|
58
57
|
spinner.succeed(chalk.green('Project scanned'));
|
|
@@ -81,13 +80,22 @@ class CIFlow {
|
|
|
81
80
|
frameworks,
|
|
82
81
|
languages,
|
|
83
82
|
testing,
|
|
83
|
+
releaseInfo,
|
|
84
84
|
envVars,
|
|
85
85
|
monorepoPackages,
|
|
86
|
+
lockFiles: codebaseInfo.lockFiles,
|
|
87
|
+
defaultBranch: codebaseInfo.defaultBranch,
|
|
88
|
+
currentBranch: codebaseInfo.currentBranch,
|
|
86
89
|
_config: userConfig,
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
// ── 5. Print summary ───────────────────────────────────────────────
|
|
90
|
-
this._printSummary(
|
|
93
|
+
this._printSummary(
|
|
94
|
+
finalConfig,
|
|
95
|
+
finalConfig.releaseInfo || releaseInfo,
|
|
96
|
+
finalConfig.envVars || envVars,
|
|
97
|
+
finalConfig.monorepoPackages || monorepoPackages
|
|
98
|
+
);
|
|
91
99
|
|
|
92
100
|
// ── 6. Optional interactive confirmation ──────────────────────────
|
|
93
101
|
if (this.prompt) {
|
|
@@ -104,10 +112,11 @@ class CIFlow {
|
|
|
104
112
|
const dependabotGen = new DependabotGenerator(codebaseInfo);
|
|
105
113
|
const dependabotFile = dependabotGen.generate();
|
|
106
114
|
|
|
107
|
-
// ── 9. Generate release.yml (if release tooling detected)
|
|
115
|
+
// ── 9. Generate release.yml (if release tooling detected or configured) ─
|
|
108
116
|
let releaseWorkflow = null;
|
|
109
|
-
|
|
110
|
-
|
|
117
|
+
const combinedReleaseInfo = finalConfig.releaseInfo || releaseInfo;
|
|
118
|
+
if (combinedReleaseInfo) {
|
|
119
|
+
const releaseGen = new ReleaseGenerator(combinedReleaseInfo, finalConfig, this.projectPath);
|
|
111
120
|
releaseWorkflow = releaseGen.generate();
|
|
112
121
|
if (releaseWorkflow) workflows.push(releaseWorkflow);
|
|
113
122
|
}
|
|
@@ -388,19 +397,15 @@ class CIFlow {
|
|
|
388
397
|
ensureDir(githubDir);
|
|
389
398
|
|
|
390
399
|
if (exists && !this.force) {
|
|
400
|
+
// dependabot.yml has a fixed schema, simpler to just overwrite or keep if identical
|
|
391
401
|
const existing = fs.readFileSync(filePath, 'utf8');
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (changes.length === 0) {
|
|
402
|
+
if (existing.trim() === dependabotFile.content.trim()) {
|
|
395
403
|
console.log(chalk.dim(` ○ No changes: dependabot.yml`));
|
|
396
404
|
return;
|
|
397
405
|
}
|
|
398
|
-
|
|
399
|
-
writeFile(filePath,
|
|
400
|
-
console.log(chalk.yellow(` ↻
|
|
401
|
-
for (const c of changes) {
|
|
402
|
-
console.log(chalk.dim(` • ${c}`));
|
|
403
|
-
}
|
|
406
|
+
|
|
407
|
+
writeFile(filePath, dependabotFile.content);
|
|
408
|
+
console.log(chalk.yellow(` ↻ Updated: dependabot.yml (schema mismatch for smart-merge)`));
|
|
404
409
|
} else {
|
|
405
410
|
writeFile(filePath, dependabotFile.content);
|
|
406
411
|
console.log(chalk.green(` ✔ Written: .github/dependabot.yml`));
|
package/src/utils/helpers.js
CHANGED
|
@@ -129,16 +129,17 @@ function _mergeJob(existJob, genJob, jobId) {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
// Merge steps
|
|
132
|
+
// Merge steps
|
|
133
133
|
if (genJob.steps) {
|
|
134
|
-
const existStepsByName = {};
|
|
135
|
-
for (const s of (existJob.steps || [])) {
|
|
136
|
-
if (s.name) existStepsByName[s.name] = s;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
134
|
const mergedSteps = [];
|
|
140
135
|
for (const genStep of genJob.steps) {
|
|
141
|
-
|
|
136
|
+
// Find matches by name, uses, or id
|
|
137
|
+
const existStep = (existJob.steps || []).find(s =>
|
|
138
|
+
(genStep.name && s.name === genStep.name) ||
|
|
139
|
+
(genStep.id && s.id === genStep.id) ||
|
|
140
|
+
(genStep.uses && s.uses === genStep.uses)
|
|
141
|
+
);
|
|
142
|
+
|
|
142
143
|
if (!existStep) {
|
|
143
144
|
mergedSteps.push(genStep);
|
|
144
145
|
jobChanges.push(` job "${jobId}" → added step "${genStep.name}"`);
|
|
@@ -153,6 +154,19 @@ function _mergeJob(existJob, genJob, jobId) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
}
|
|
157
|
+
// Append any existing steps that were NOT matched by a generated step
|
|
158
|
+
// This ensures user customizations are preserved.
|
|
159
|
+
for (const existStep of (existJob.steps || [])) {
|
|
160
|
+
const isMatched = genJob.steps.some((genStep) =>
|
|
161
|
+
(genStep.name && existStep.name === genStep.name) ||
|
|
162
|
+
(genStep.id && existStep.id === genStep.id) ||
|
|
163
|
+
(genStep.uses && existStep.uses === genStep.uses)
|
|
164
|
+
);
|
|
165
|
+
if (!isMatched) {
|
|
166
|
+
mergedSteps.push(existStep);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
merged.steps = mergedSteps;
|
|
157
171
|
}
|
|
158
172
|
|