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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cistack",
3
- "version": "5.4.0",
3
+ "version": "6.0.0",
4
4
  "description": "Automatically generate GitHub Actions CI/CD pipelines by analysing your codebase",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
@@ -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.ts', 'next.config.mjs'], { buildDir: '.next', priority: 10 }),
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
@@ -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.configs.has('jest.config.js') || this.configs.has('jest.config.ts')) conf += 0.4;
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.configs.has('vitest.config.js') || this.configs.has('vitest.config.ts')) conf += 0.4;
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.configs.has('.mocharc.js') || this.configs.has('.mocharc.yml')) conf += 0.4;
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.configs.has('cypress.config.js') || this.configs.has('cypress.config.ts')) conf += 0.4;
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.configs.has('playwright.config.js') || this.configs.has('playwright.config.ts')) conf += 0.4;
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.equal(lighthouseBuildStep.run, 'yarn workspace ${{ matrix.name }} run ${{ matrix.buildScript }}');
916
+ assert(!rootWorkflow.jobs.lighthouse);
884
917
  });
885
918
 
886
919
  test('Bun monorepo matrix commands are scoped to the workspace path', () => {