cistack 5.4.0 → 6.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/package.json +1 -1
- package/src/analyzers/codebase.js +53 -0
- package/src/detectors/framework.js +6 -6
- package/src/detectors/testing.js +37 -5
- package/src/generators/workflow.js +5 -61
- package/tests/run.js +38 -5
package/package.json
CHANGED
|
@@ -127,25 +127,78 @@ class CodebaseAnalyzer {
|
|
|
127
127
|
'composer.json',
|
|
128
128
|
// Build tools
|
|
129
129
|
'vite.config.js',
|
|
130
|
+
'vite.config.cjs',
|
|
131
|
+
'vite.config.mjs',
|
|
130
132
|
'vite.config.ts',
|
|
133
|
+
'vite.config.cts',
|
|
134
|
+
'vite.config.mts',
|
|
131
135
|
'webpack.config.js',
|
|
136
|
+
'webpack.config.cjs',
|
|
137
|
+
'webpack.config.mjs',
|
|
132
138
|
'webpack.config.ts',
|
|
139
|
+
'webpack.config.cts',
|
|
140
|
+
'webpack.config.mts',
|
|
133
141
|
'rollup.config.js',
|
|
142
|
+
'rollup.config.cjs',
|
|
143
|
+
'rollup.config.mjs',
|
|
144
|
+
'rollup.config.ts',
|
|
145
|
+
'rollup.config.cts',
|
|
146
|
+
'rollup.config.mts',
|
|
147
|
+
'next.config.js',
|
|
148
|
+
'next.config.cjs',
|
|
149
|
+
'next.config.mjs',
|
|
150
|
+
'next.config.ts',
|
|
151
|
+
'next.config.cts',
|
|
152
|
+
'next.config.mts',
|
|
153
|
+
'nuxt.config.js',
|
|
154
|
+
'nuxt.config.cjs',
|
|
155
|
+
'nuxt.config.mjs',
|
|
156
|
+
'nuxt.config.ts',
|
|
157
|
+
'nuxt.config.cts',
|
|
158
|
+
'nuxt.config.mts',
|
|
159
|
+
'svelte.config.js',
|
|
160
|
+
'svelte.config.cjs',
|
|
161
|
+
'svelte.config.mjs',
|
|
162
|
+
'svelte.config.ts',
|
|
163
|
+
'svelte.config.cts',
|
|
164
|
+
'svelte.config.mts',
|
|
165
|
+
'astro.config.js',
|
|
166
|
+
'astro.config.cjs',
|
|
134
167
|
'turbo.json',
|
|
135
168
|
'nx.json',
|
|
136
169
|
'lerna.json',
|
|
137
170
|
'rush.json',
|
|
138
171
|
// Test
|
|
139
172
|
'jest.config.js',
|
|
173
|
+
'jest.config.cjs',
|
|
174
|
+
'jest.config.mjs',
|
|
140
175
|
'jest.config.ts',
|
|
176
|
+
'jest.config.cts',
|
|
177
|
+
'jest.config.mts',
|
|
141
178
|
'vitest.config.js',
|
|
179
|
+
'vitest.config.cjs',
|
|
180
|
+
'vitest.config.mjs',
|
|
142
181
|
'vitest.config.ts',
|
|
182
|
+
'vitest.config.cts',
|
|
183
|
+
'vitest.config.mts',
|
|
143
184
|
'cypress.config.js',
|
|
185
|
+
'cypress.config.cjs',
|
|
186
|
+
'cypress.config.mjs',
|
|
144
187
|
'cypress.config.ts',
|
|
188
|
+
'cypress.config.cts',
|
|
189
|
+
'cypress.config.mts',
|
|
145
190
|
'playwright.config.js',
|
|
191
|
+
'playwright.config.cjs',
|
|
192
|
+
'playwright.config.mjs',
|
|
146
193
|
'playwright.config.ts',
|
|
194
|
+
'playwright.config.cts',
|
|
195
|
+
'playwright.config.mts',
|
|
147
196
|
'.mocharc.js',
|
|
197
|
+
'.mocharc.cjs',
|
|
198
|
+
'.mocharc.mjs',
|
|
148
199
|
'.mocharc.yml',
|
|
200
|
+
'.mocharc.yaml',
|
|
201
|
+
'.mocharc.json',
|
|
149
202
|
'phpunit.xml',
|
|
150
203
|
'pytest.ini',
|
|
151
204
|
'conftest.py',
|
|
@@ -19,16 +19,16 @@ class FrameworkDetector {
|
|
|
19
19
|
async detect() {
|
|
20
20
|
const results = [
|
|
21
21
|
// JS / TS frontend
|
|
22
|
-
this._check('Next.js', ['next'], ['next.config.js', 'next.config.
|
|
23
|
-
this._check('Nuxt', ['nuxt', 'nuxt3'], ['nuxt.config.js', 'nuxt.config.ts'], { buildDir: '.nuxt', priority: 10 }),
|
|
24
|
-
this._check('SvelteKit', ['@sveltejs/kit'], ['svelte.config.js'], { buildDir: '.svelte-kit', priority: 10 }),
|
|
22
|
+
this._check('Next.js', ['next'], ['next.config.js', 'next.config.cjs', 'next.config.mjs', 'next.config.ts', 'next.config.cts', 'next.config.mts'], { buildDir: '.next', priority: 10 }),
|
|
23
|
+
this._check('Nuxt', ['nuxt', 'nuxt3'], ['nuxt.config.js', 'nuxt.config.cjs', 'nuxt.config.mjs', 'nuxt.config.ts', 'nuxt.config.cts', 'nuxt.config.mts'], { buildDir: '.nuxt', priority: 10 }),
|
|
24
|
+
this._check('SvelteKit', ['@sveltejs/kit'], ['svelte.config.js', 'svelte.config.cjs', 'svelte.config.mjs', 'svelte.config.ts', 'svelte.config.cts', 'svelte.config.mts'], { buildDir: '.svelte-kit', priority: 10 }),
|
|
25
25
|
this._check('Remix', ['@remix-run/react', '@remix-run/node'], [], { priority: 10 }),
|
|
26
|
-
this._check('Astro', ['astro'], ['astro.config.mjs', 'astro.config.ts'], { buildDir: 'dist', priority: 10 }),
|
|
27
|
-
this._check('Vite', ['vite'], ['vite.config.js', 'vite.config.ts'], { buildDir: 'dist', priority: 5 }),
|
|
26
|
+
this._check('Astro', ['astro'], ['astro.config.js', 'astro.config.cjs', 'astro.config.mjs', 'astro.config.ts'], { buildDir: 'dist', priority: 10 }),
|
|
27
|
+
this._check('Vite', ['vite'], ['vite.config.js', 'vite.config.cjs', 'vite.config.mjs', 'vite.config.ts', 'vite.config.cts', 'vite.config.mts'], { buildDir: 'dist', priority: 5 }),
|
|
28
28
|
this._check('React', ['react', 'react-dom'], [], { buildDir: 'build', priority: 1 }),
|
|
29
29
|
this._check('Vue', ['vue'], [], { buildDir: 'dist', priority: 1 }),
|
|
30
30
|
this._check('Angular', ['@angular/core'], [], { buildDir: 'dist', priority: 1 }),
|
|
31
|
-
this._check('Svelte', ['svelte'], ['svelte.config.js'], { buildDir: 'public', priority: 1 }),
|
|
31
|
+
this._check('Svelte', ['svelte'], ['svelte.config.js', 'svelte.config.cjs', 'svelte.config.mjs', 'svelte.config.ts', 'svelte.config.cts', 'svelte.config.mts'], { buildDir: 'public', priority: 1 }),
|
|
32
32
|
this._check('Gatsby', ['gatsby'], [], { buildDir: 'public', priority: 1 }),
|
|
33
33
|
this._check('Ember', ['ember-cli'], [], { priority: 1 }),
|
|
34
34
|
// Node / backend
|
package/src/detectors/testing.js
CHANGED
|
@@ -46,7 +46,14 @@ class TestingDetector {
|
|
|
46
46
|
_checkJest() {
|
|
47
47
|
let conf = 0;
|
|
48
48
|
if (this.deps['jest'] || this.deps['@jest/core']) conf += 0.5;
|
|
49
|
-
if (this.
|
|
49
|
+
if (this._hasConfig([
|
|
50
|
+
'jest.config.js',
|
|
51
|
+
'jest.config.cjs',
|
|
52
|
+
'jest.config.mjs',
|
|
53
|
+
'jest.config.ts',
|
|
54
|
+
'jest.config.cts',
|
|
55
|
+
'jest.config.mts',
|
|
56
|
+
])) conf += 0.4;
|
|
50
57
|
if (this.scripts.test && this.scripts.test.includes('jest')) conf += 0.2;
|
|
51
58
|
return { name: 'Jest', confidence: Math.min(conf, 1), command: this._testScript('jest') || 'npx jest --coverage', type: 'unit' };
|
|
52
59
|
}
|
|
@@ -54,21 +61,35 @@ class TestingDetector {
|
|
|
54
61
|
_checkVitest() {
|
|
55
62
|
let conf = 0;
|
|
56
63
|
if (this.deps['vitest']) conf += 0.6;
|
|
57
|
-
if (this.
|
|
64
|
+
if (this._hasConfig([
|
|
65
|
+
'vitest.config.js',
|
|
66
|
+
'vitest.config.cjs',
|
|
67
|
+
'vitest.config.mjs',
|
|
68
|
+
'vitest.config.ts',
|
|
69
|
+
'vitest.config.cts',
|
|
70
|
+
'vitest.config.mts',
|
|
71
|
+
])) conf += 0.4;
|
|
58
72
|
return { name: 'Vitest', confidence: Math.min(conf, 1), command: this._testScript('vitest') || 'npx vitest run --coverage', type: 'unit' };
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
_checkMocha() {
|
|
62
76
|
let conf = 0;
|
|
63
77
|
if (this.deps['mocha']) conf += 0.5;
|
|
64
|
-
if (this.
|
|
78
|
+
if (this._hasConfig(['.mocharc.js', '.mocharc.cjs', '.mocharc.mjs', '.mocharc.yml', '.mocharc.yaml', '.mocharc.json'])) conf += 0.4;
|
|
65
79
|
return { name: 'Mocha', confidence: Math.min(conf, 1), command: 'npx mocha', type: 'unit' };
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
_checkCypress() {
|
|
69
83
|
let conf = 0;
|
|
70
84
|
if (this.deps['cypress']) conf += 0.6;
|
|
71
|
-
if (this.
|
|
85
|
+
if (this._hasConfig([
|
|
86
|
+
'cypress.config.js',
|
|
87
|
+
'cypress.config.cjs',
|
|
88
|
+
'cypress.config.mjs',
|
|
89
|
+
'cypress.config.ts',
|
|
90
|
+
'cypress.config.cts',
|
|
91
|
+
'cypress.config.mts',
|
|
92
|
+
])) conf += 0.4;
|
|
72
93
|
if (this.files.has('cypress/e2e') || [...this.files].some((f) => f.startsWith('cypress/'))) conf += 0.2;
|
|
73
94
|
return { name: 'Cypress', confidence: Math.min(conf, 1), command: 'npx cypress run', type: 'e2e' };
|
|
74
95
|
}
|
|
@@ -76,7 +97,14 @@ class TestingDetector {
|
|
|
76
97
|
_checkPlaywright() {
|
|
77
98
|
let conf = 0;
|
|
78
99
|
if (this.deps['@playwright/test']) conf += 0.6;
|
|
79
|
-
if (this.
|
|
100
|
+
if (this._hasConfig([
|
|
101
|
+
'playwright.config.js',
|
|
102
|
+
'playwright.config.cjs',
|
|
103
|
+
'playwright.config.mjs',
|
|
104
|
+
'playwright.config.ts',
|
|
105
|
+
'playwright.config.cts',
|
|
106
|
+
'playwright.config.mts',
|
|
107
|
+
])) conf += 0.4;
|
|
80
108
|
return { name: 'Playwright', confidence: Math.min(conf, 1), command: 'npx playwright test', type: 'e2e' };
|
|
81
109
|
}
|
|
82
110
|
|
|
@@ -148,6 +176,10 @@ class TestingDetector {
|
|
|
148
176
|
if (this.packageManager === 'bun') return `bun run ${name}`;
|
|
149
177
|
return `npm run ${name}`;
|
|
150
178
|
}
|
|
179
|
+
|
|
180
|
+
_hasConfig(candidates) {
|
|
181
|
+
return candidates.some((candidate) => this.configs.has(candidate));
|
|
182
|
+
}
|
|
151
183
|
}
|
|
152
184
|
|
|
153
185
|
module.exports = TestingDetector;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
3
5
|
const yaml = require('js-yaml');
|
|
4
6
|
const { version } = require('../../package.json');
|
|
5
7
|
|
|
@@ -45,9 +47,6 @@ class WorkflowGenerator {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
_detectRuntimeVersions() {
|
|
48
|
-
const fs = require('fs');
|
|
49
|
-
const path = require('path');
|
|
50
|
-
|
|
51
50
|
// 1. Node.js
|
|
52
51
|
if (!this.primaryLang.nodeVersion) {
|
|
53
52
|
const nvmrcPath = path.join(this.projectPath, '.nvmrc');
|
|
@@ -189,29 +188,6 @@ class WorkflowGenerator {
|
|
|
189
188
|
};
|
|
190
189
|
}
|
|
191
190
|
|
|
192
|
-
// ── lighthouse job ──────────────────────────────────────────────────
|
|
193
|
-
if (jobs.build && this.frameworks.some(f => ['Next.js', 'React', 'Vue', 'Svelte', 'Nuxt'].includes(f.name))) {
|
|
194
|
-
jobs.lighthouse = {
|
|
195
|
-
name: '⚡ Lighthouse Audit',
|
|
196
|
-
'runs-on': 'ubuntu-latest',
|
|
197
|
-
needs: ['build'],
|
|
198
|
-
if: "github.event_name == 'pull_request'",
|
|
199
|
-
steps: [
|
|
200
|
-
this._stepCheckout(),
|
|
201
|
-
{
|
|
202
|
-
name: 'Run Lighthouse on build output',
|
|
203
|
-
uses: 'treosh/lighthouse-ci-action@v11',
|
|
204
|
-
with: {
|
|
205
|
-
uploadArtifacts: true,
|
|
206
|
-
temporaryPublicStorage: true,
|
|
207
|
-
configPath: './.lighthouserc.json',
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
'continue-on-error': true,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
191
|
// ── e2e job ───────────────────────────────────────────────────────────
|
|
216
192
|
if (this.e2eTests.length > 0) {
|
|
217
193
|
const e2eTest = this.e2eTests[0];
|
|
@@ -340,38 +316,6 @@ class WorkflowGenerator {
|
|
|
340
316
|
},
|
|
341
317
|
};
|
|
342
318
|
|
|
343
|
-
if (buildablePackages.length > 0 && this.frameworks.some(f => ['Next.js', 'React', 'Vue', 'Svelte', 'Nuxt'].includes(f.name))) {
|
|
344
|
-
workflow.jobs.lighthouse = {
|
|
345
|
-
name: '⚡ Lighthouse (Root)',
|
|
346
|
-
'runs-on': 'ubuntu-latest',
|
|
347
|
-
strategy: {
|
|
348
|
-
matrix: {
|
|
349
|
-
include: buildablePackages,
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
steps: [
|
|
353
|
-
this._stepCheckout(),
|
|
354
|
-
...this._setupSteps(lang),
|
|
355
|
-
{
|
|
356
|
-
...this._stepInstallDeps(lang),
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
name: 'Build workspace',
|
|
360
|
-
run: this._workspaceRunCommand(lang, '${{ matrix.buildScript }}'),
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
name: 'Lighthouse',
|
|
364
|
-
uses: 'treosh/lighthouse-ci-action@v11',
|
|
365
|
-
with: {
|
|
366
|
-
uploadArtifacts: true,
|
|
367
|
-
temporaryPublicStorage: true,
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
],
|
|
371
|
-
'continue-on-error': true,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
319
|
return this._toYaml(
|
|
376
320
|
workflow,
|
|
377
321
|
`# Generated by cistack v${version} — https://github.com/cistack\n# Monorepo CI — matrix over all workspaces\n\n`
|
|
@@ -886,9 +830,9 @@ class WorkflowGenerator {
|
|
|
886
830
|
{
|
|
887
831
|
name: 'Validate Vercel credentials',
|
|
888
832
|
run: [
|
|
889
|
-
'test -n "$VERCEL_TOKEN" || (echo "Missing VERCEL_TOKEN secret. Add it in GitHub Actions secrets." && exit 1)',
|
|
890
|
-
'test -n "$VERCEL_ORG_ID" || (echo "Missing VERCEL_ORG_ID secret. Add it in GitHub Actions secrets." && exit 1)',
|
|
891
|
-
'test -n "$VERCEL_PROJECT_ID" || (echo "Missing VERCEL_PROJECT_ID secret. Add it in GitHub Actions secrets." && exit 1)',
|
|
833
|
+
'test -n "$VERCEL_TOKEN" || (echo "Missing VERCEL_TOKEN secret. Add it in GitHub Actions secrets, or Dependabot secrets for Dependabot PRs." && exit 1)',
|
|
834
|
+
'test -n "$VERCEL_ORG_ID" || (echo "Missing VERCEL_ORG_ID secret. Add it in GitHub Actions secrets, or Dependabot secrets for Dependabot PRs." && exit 1)',
|
|
835
|
+
'test -n "$VERCEL_PROJECT_ID" || (echo "Missing VERCEL_PROJECT_ID secret. Add it in GitHub Actions secrets, or Dependabot secrets for Dependabot PRs." && exit 1)',
|
|
892
836
|
].join('\n'),
|
|
893
837
|
env: vercelEnv,
|
|
894
838
|
},
|
package/tests/run.js
CHANGED
|
@@ -151,6 +151,22 @@ test('FrameworkDetector detects Spring Boot from build.gradle.kts', async () =>
|
|
|
151
151
|
assert(frameworks.some((framework) => framework.name === 'Spring Boot'));
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
test('FrameworkDetector recognizes modern framework config files like next.config.mjs', async () => {
|
|
155
|
+
const projectDir = makeTempDir();
|
|
156
|
+
writeFiles(projectDir, {
|
|
157
|
+
'package.json': json({
|
|
158
|
+
name: 'next-config-mjs-app',
|
|
159
|
+
version: '1.0.0',
|
|
160
|
+
}),
|
|
161
|
+
'next.config.mjs': 'export default {};\n',
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const info = await new CodebaseAnalyzer(projectDir).analyse();
|
|
165
|
+
const frameworks = await new FrameworkDetector(projectDir, info).detect();
|
|
166
|
+
|
|
167
|
+
assert(frameworks.some((framework) => framework.name === 'Next.js'));
|
|
168
|
+
});
|
|
169
|
+
|
|
154
170
|
test('HostingDetector recognizes Azure pipelines in azure/pipelines.yml', async () => {
|
|
155
171
|
const projectDir = makeTempDir();
|
|
156
172
|
writeFiles(projectDir, {
|
|
@@ -274,6 +290,24 @@ test('ReleaseDetector reads release-it CJS config and honors npm.publish false',
|
|
|
274
290
|
assert.equal(release.requiresNpmToken, false);
|
|
275
291
|
});
|
|
276
292
|
|
|
293
|
+
test('TestingDetector recognizes modern config file extensions like vitest.config.mjs', async () => {
|
|
294
|
+
const projectDir = makeTempDir();
|
|
295
|
+
writeFiles(projectDir, {
|
|
296
|
+
'package.json': json({
|
|
297
|
+
name: 'modern-vitest-app',
|
|
298
|
+
version: '1.0.0',
|
|
299
|
+
}),
|
|
300
|
+
'vitest.config.mjs': 'export default {};\n',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const info = await new CodebaseAnalyzer(projectDir).analyse();
|
|
304
|
+
const testing = await new TestingDetector(projectDir, info).detect();
|
|
305
|
+
const vitest = testing.find((entry) => entry.name === 'Vitest');
|
|
306
|
+
|
|
307
|
+
assert(vitest);
|
|
308
|
+
assert(vitest.confidence > 0);
|
|
309
|
+
});
|
|
310
|
+
|
|
277
311
|
test('bun.lock is recognized as Bun across codebase, testing, and release detection', async () => {
|
|
278
312
|
const projectDir = makeTempDir();
|
|
279
313
|
writeFiles(projectDir, {
|
|
@@ -440,9 +474,9 @@ test('Vercel deploy workflows validate secrets before running vercel pull', () =
|
|
|
440
474
|
const pullStep = deploySteps.find((step) => step.name === 'Pull Vercel environment');
|
|
441
475
|
|
|
442
476
|
assert(validateStep);
|
|
443
|
-
assert(validateStep.run.includes('Missing VERCEL_TOKEN secret'));
|
|
444
|
-
assert(validateStep.run.includes('Missing VERCEL_ORG_ID secret'));
|
|
445
|
-
assert(validateStep.run.includes('Missing VERCEL_PROJECT_ID secret'));
|
|
477
|
+
assert(validateStep.run.includes('Missing VERCEL_TOKEN secret. Add it in GitHub Actions secrets, or Dependabot secrets for Dependabot PRs.'));
|
|
478
|
+
assert(validateStep.run.includes('Missing VERCEL_ORG_ID secret. Add it in GitHub Actions secrets, or Dependabot secrets for Dependabot PRs.'));
|
|
479
|
+
assert(validateStep.run.includes('Missing VERCEL_PROJECT_ID secret. Add it in GitHub Actions secrets, or Dependabot secrets for Dependabot PRs.'));
|
|
446
480
|
assert.equal(pullStep.run, 'vercel pull --yes --environment=production --token="$VERCEL_TOKEN"');
|
|
447
481
|
});
|
|
448
482
|
|
|
@@ -871,7 +905,6 @@ test('Monorepo root CI installs dependencies at the repo root and does not hide
|
|
|
871
905
|
const lintStep = ciSteps.find((step) => step.name === 'Lint');
|
|
872
906
|
const testStep = ciSteps.find((step) => step.name === 'Test');
|
|
873
907
|
const buildStep = ciSteps.find((step) => step.name === 'Build');
|
|
874
|
-
const lighthouseBuildStep = rootWorkflow.jobs.lighthouse.steps.find((step) => step.name === 'Build workspace');
|
|
875
908
|
|
|
876
909
|
assert.equal(installStep.run, 'yarn install --frozen-lockfile');
|
|
877
910
|
assert.equal(lintStep.if, '${{ matrix.lintScript != \'\' }}');
|
|
@@ -880,7 +913,7 @@ test('Monorepo root CI installs dependencies at the repo root and does not hide
|
|
|
880
913
|
assert(!lintStep.run.includes('|| true'));
|
|
881
914
|
assert(!testStep.run.includes('|| true'));
|
|
882
915
|
assert(!buildStep.run.includes('|| true'));
|
|
883
|
-
assert
|
|
916
|
+
assert(!rootWorkflow.jobs.lighthouse);
|
|
884
917
|
});
|
|
885
918
|
|
|
886
919
|
test('Bun monorepo matrix commands are scoped to the workspace path', () => {
|