ship18ion 1.0.0 → 1.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/dist/cli/index.js CHANGED
@@ -10,25 +10,40 @@ const config_1 = require("../engine/config");
10
10
  const runner_1 = require("../engine/runner");
11
11
  const console_1 = require("../reporters/console");
12
12
  const program = new commander_1.Command();
13
- program
14
- .name('ship18ion')
15
- .description('Production Readiness Inspector')
16
- .version('0.1.0');
13
+ const figlet_1 = __importDefault(require("figlet"));
14
+ const gradient_string_1 = __importDefault(require("gradient-string"));
15
+ const ora_1 = __importDefault(require("ora"));
16
+ const detector_1 = require("../engine/detector");
17
17
  program
18
18
  .command('check', { isDefault: true })
19
19
  .description('Run production readiness checks')
20
20
  .option('--ci', 'Run in CI mode (minimal output, exit codes)')
21
21
  .action(async (options) => {
22
- // console.log(chalk.blue('Starting ship18ion checks...'));
22
+ if (!options.ci) {
23
+ console.log(gradient_string_1.default.pastel.multiline(figlet_1.default.textSync('SHIP18ION')));
24
+ console.log(chalk_1.default.dim('Production Readiness Inspector\n'));
25
+ }
23
26
  const cwd = process.cwd();
24
27
  const config = await (0, config_1.loadConfig)(cwd);
28
+ const spinner = (0, ora_1.default)('Initializing...').start();
25
29
  try {
26
- const results = await (0, runner_1.runChecks)(config, cwd);
30
+ if (!options.ci) {
31
+ const framework = await (0, detector_1.detectFramework)(cwd);
32
+ spinner.text = `Detected Framework: ${chalk_1.default.cyan(framework.toUpperCase())}`;
33
+ await new Promise(r => setTimeout(r, 800)); // Brief pause to show framework
34
+ }
35
+ const results = await (0, runner_1.runChecks)(config, cwd, (stage) => {
36
+ if (!options.ci)
37
+ spinner.text = stage;
38
+ });
39
+ spinner.succeed(chalk_1.default.green('Checks completed!'));
40
+ console.log('');
27
41
  // Uses console reporter for both normal and CI for now (it handles exit codes)
28
42
  (0, console_1.reportConsole)(results, cwd);
29
43
  }
30
44
  catch (e) {
31
- console.error(chalk_1.default.red('Error running checks:'), e);
45
+ spinner.fail(chalk_1.default.red('Error running checks'));
46
+ console.error(e);
32
47
  process.exit(1);
33
48
  }
34
49
  });
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectFramework = detectFramework;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ async function detectFramework(cwd) {
10
+ const pkgPath = path_1.default.join(cwd, 'package.json');
11
+ if (!fs_1.default.existsSync(pkgPath)) {
12
+ return 'unknown';
13
+ }
14
+ try {
15
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf-8'));
16
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
17
+ if (deps['next'])
18
+ return 'nextjs';
19
+ if (deps['@remix-run/react'])
20
+ return 'remix';
21
+ if (deps['vite'])
22
+ return 'vite';
23
+ if (deps['@nestjs/core'])
24
+ return 'nestjs';
25
+ if (deps['express'])
26
+ return 'express';
27
+ if (deps['fastify'])
28
+ return 'fastify';
29
+ return 'Node.js / Generic';
30
+ }
31
+ catch (e) {
32
+ return 'unknown';
33
+ }
34
+ }
@@ -8,17 +8,38 @@ const security_1 = require("../rules/security");
8
8
  const build_1 = require("../rules/build");
9
9
  const nextjs_1 = require("../rules/frameworks/nextjs");
10
10
  const git_1 = require("../rules/git");
11
- async function runChecks(config, cwd) {
11
+ const detector_1 = require("./detector");
12
+ async function runChecks(config, cwd, onProgress) {
13
+ if (onProgress)
14
+ onProgress('Scanning files...');
12
15
  const files = await (0, scanner_1.scanFiles)(cwd, config.ignore);
13
16
  const ctx = { config, files, cwd };
14
17
  const results = [];
15
18
  // Run all checks
19
+ if (onProgress)
20
+ onProgress('Checking environment variables...');
16
21
  results.push(...await (0, env_1.checkEnvVars)(ctx));
22
+ if (onProgress)
23
+ onProgress('Scanning for secrets...');
17
24
  results.push(...await (0, secrets_1.checkSecrets)(ctx));
25
+ if (onProgress)
26
+ onProgress('Analyzing security configurations...');
18
27
  results.push(...await (0, security_1.checkSecurity)(ctx));
28
+ if (onProgress)
29
+ onProgress('Verifying dependencies...');
19
30
  results.push(...await (0, build_1.checkDependencies)(ctx));
31
+ if (onProgress)
32
+ onProgress('Inspecting build artifacts...');
20
33
  results.push(...await (0, build_1.checkBuild)(ctx));
21
- results.push(...await (0, nextjs_1.checkNextJs)(ctx));
34
+ // Framework detection
35
+ const framework = await (0, detector_1.detectFramework)(cwd);
36
+ if (framework === 'nextjs') {
37
+ if (onProgress)
38
+ onProgress('Running Next.js specific checks...');
39
+ results.push(...await (0, nextjs_1.checkNextJs)(ctx));
40
+ }
41
+ if (onProgress)
42
+ onProgress('Checking git status...');
22
43
  results.push(...await (0, git_1.checkGit)(ctx));
23
44
  return results;
24
45
  }
@@ -3,7 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanFiles = scanFiles;
4
4
  const glob_1 = require("glob");
5
5
  async function scanFiles(cwd, ignore = []) {
6
- const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/.git/**'];
6
+ // Ignore build artifacts, node_modules, and git
7
+ const defaultIgnore = [
8
+ '**/node_modules/**',
9
+ '**/.git/**',
10
+ '**/dist/**',
11
+ '**/build/**',
12
+ '**/.next/**',
13
+ '**/.turbo/**',
14
+ '**/coverage/**'
15
+ ];
7
16
  // Scan for relevant files: JS/TS code, Configs (JSON/YAML), Env files
8
17
  return (0, glob_1.glob)('**/*.{js,ts,jsx,tsx,json,yaml,yml,env,env.*}', {
9
18
  cwd,
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.checkDependencies = checkDependencies;
7
7
  exports.checkBuild = checkBuild;
8
8
  const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
9
10
  async function checkDependencies(ctx) {
10
11
  const results = [];
11
12
  const packageJsons = ctx.files.filter(f => f.endsWith('package.json') && !f.includes('node_modules'));
@@ -31,36 +32,39 @@ async function checkDependencies(ctx) {
31
32
  }
32
33
  return results;
33
34
  }
35
+ const glob_1 = require("glob");
34
36
  async function checkBuild(ctx) {
35
37
  const results = [];
36
- // Check for source maps in potential build dirs (dist, build, out, .next)
37
- // Scanner ignores dist by default, but if we want to check build artifacts we might need to scan explicitly OR assumes user runs this in root.
38
- // If the scanner ignores 'dist', we won't see them.
39
- // So this check is effective only if scanner INCLUDES build dirs or we explicitly look for them.
40
- // Let's explicitly check common build folders in CWD if they exist, ignoring scanner's ignore list for this specific check?
41
- // Or just warn if we find .map files in the file list (meaning they were NOT ignored/cleaned).
42
- const mapFiles = ctx.files.filter(f => f.endsWith('.map'));
43
- for (const file of mapFiles) {
44
- // Only if it looks like a build artifact
45
- if (file.includes('/dist/') || file.includes('/build/') || file.includes('/.next/')) {
38
+ // Explicitly scan build folders (dist, build, .next, .output) for dangerous files
39
+ // The main scanner ignores these, so we check them separately here.
40
+ const buildDirs = ['dist', 'build', '.next', '.output'];
41
+ const foundBuildDirs = buildDirs.filter(d => fs_1.default.existsSync(path_1.default.join(ctx.cwd, d)));
42
+ if (foundBuildDirs.length === 0) {
43
+ return results;
44
+ }
45
+ // 1. Check for Source Maps (.map)
46
+ // We search inside the found build directories
47
+ for (const dir of foundBuildDirs) {
48
+ const mapFiles = await (0, glob_1.glob)(`${dir}/**/*.map`, { cwd: ctx.cwd, absolute: true });
49
+ for (const file of mapFiles) {
46
50
  results.push({
47
51
  status: 'warn',
48
- message: 'Source map found in build output (information leak)',
52
+ message: `Source map found in build output (${dir}) (information leak)`,
49
53
  ruleId: 'build-source-map',
50
54
  file
51
55
  });
52
56
  }
53
- }
54
- // Check for .env in build folders
55
- const envInBuild = ctx.files.filter(f => (f.endsWith('.env') || f.includes('.env.')) &&
56
- (f.includes('/dist/') || f.includes('/build/') || f.includes('/.next/')));
57
- for (const file of envInBuild) {
58
- results.push({
59
- status: 'fail',
60
- message: 'Environment file found in build output!',
61
- ruleId: 'build-env-leak',
62
- file
63
- });
57
+ // 2. Check for .env files in build output
58
+ // We look for .env* files inside the build dir
59
+ const envFiles = await (0, glob_1.glob)(`${dir}/**/*.env*`, { cwd: ctx.cwd, absolute: true });
60
+ for (const file of envFiles) {
61
+ results.push({
62
+ status: 'fail',
63
+ message: `Environment file found in build output (${dir})!`,
64
+ ruleId: 'build-env-leak',
65
+ file
66
+ });
67
+ }
64
68
  }
65
69
  return results;
66
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ship18ion",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "main": "dist/cli/index.js",
6
6
  "bin": {
@@ -27,11 +27,16 @@
27
27
  "@babel/parser": "^7.28.5",
28
28
  "@babel/traverse": "^7.28.5",
29
29
  "@types/babel__traverse": "^7.28.0",
30
+ "@types/figlet": "^1.7.0",
31
+ "@types/gradient-string": "^1.1.6",
30
32
  "@types/node": "^25.0.3",
31
33
  "chalk": "^4.1.2",
32
34
  "commander": "^14.0.2",
33
35
  "dotenv": "^17.2.3",
36
+ "figlet": "^1.9.4",
34
37
  "glob": "^13.0.0",
38
+ "gradient-string": "^3.0.0",
39
+ "ora": "^9.0.0",
35
40
  "typescript": "^5.9.3"
36
41
  }
37
42
  }
package/src/cli/index.ts CHANGED
@@ -7,26 +7,44 @@ import { reportConsole } from '../reporters/console';
7
7
 
8
8
  const program = new Command();
9
9
 
10
- program
11
- .name('ship18ion')
12
- .description('Production Readiness Inspector')
13
- .version('0.1.0');
10
+ import figlet from 'figlet';
11
+ import gradient from 'gradient-string';
12
+ import ora from 'ora';
13
+ import { detectFramework } from '../engine/detector';
14
14
 
15
15
  program
16
16
  .command('check', { isDefault: true })
17
17
  .description('Run production readiness checks')
18
18
  .option('--ci', 'Run in CI mode (minimal output, exit codes)')
19
19
  .action(async (options) => {
20
- // console.log(chalk.blue('Starting ship18ion checks...'));
20
+ if (!options.ci) {
21
+ console.log(gradient.pastel.multiline(figlet.textSync('SHIP18ION')));
22
+ console.log(chalk.dim('Production Readiness Inspector\n'));
23
+ }
24
+
21
25
  const cwd = process.cwd();
22
26
  const config = await loadConfig(cwd);
27
+ const spinner = ora('Initializing...').start();
23
28
 
24
29
  try {
25
- const results = await runChecks(config, cwd);
30
+ if (!options.ci) {
31
+ const framework = await detectFramework(cwd);
32
+ spinner.text = `Detected Framework: ${chalk.cyan(framework.toUpperCase())}`;
33
+ await new Promise(r => setTimeout(r, 800)); // Brief pause to show framework
34
+ }
35
+
36
+ const results = await runChecks(config, cwd, (stage) => {
37
+ if (!options.ci) spinner.text = stage;
38
+ });
39
+
40
+ spinner.succeed(chalk.green('Checks completed!'));
41
+ console.log('');
42
+
26
43
  // Uses console reporter for both normal and CI for now (it handles exit codes)
27
44
  reportConsole(results, cwd);
28
45
  } catch (e) {
29
- console.error(chalk.red('Error running checks:'), e);
46
+ spinner.fail(chalk.red('Error running checks'));
47
+ console.error(e);
30
48
  process.exit(1);
31
49
  }
32
50
  });
@@ -0,0 +1,27 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export type FrameworkType = 'nextjs' | 'remix' | 'vite' | 'nestjs' | 'express' | 'fastify' | 'Node.js / Generic' | 'unknown';
5
+
6
+ export async function detectFramework(cwd: string): Promise<FrameworkType> {
7
+ const pkgPath = path.join(cwd, 'package.json');
8
+ if (!fs.existsSync(pkgPath)) {
9
+ return 'unknown';
10
+ }
11
+
12
+ try {
13
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
14
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
15
+
16
+ if (deps['next']) return 'nextjs';
17
+ if (deps['@remix-run/react']) return 'remix';
18
+ if (deps['vite']) return 'vite';
19
+ if (deps['@nestjs/core']) return 'nestjs';
20
+ if (deps['express']) return 'express';
21
+ if (deps['fastify']) return 'fastify';
22
+
23
+ return 'Node.js / Generic';
24
+ } catch (e) {
25
+ return 'unknown';
26
+ }
27
+ }
@@ -8,19 +8,43 @@ import { checkDependencies, checkBuild } from '../rules/build';
8
8
  import { checkNextJs } from '../rules/frameworks/nextjs';
9
9
  import { checkGit } from '../rules/git';
10
10
 
11
- export async function runChecks(config: Ship18ionConfig, cwd: string): Promise<RuleResult[]> {
11
+ import { detectFramework } from './detector';
12
+
13
+ export async function runChecks(
14
+ config: Ship18ionConfig,
15
+ cwd: string,
16
+ onProgress?: (stage: string) => void
17
+ ): Promise<RuleResult[]> {
18
+ if (onProgress) onProgress('Scanning files...');
12
19
  const files = await scanFiles(cwd, config.ignore);
13
20
  const ctx: RuleContext = { config, files, cwd };
14
21
 
15
22
  const results: RuleResult[] = [];
16
23
 
17
24
  // Run all checks
25
+ if (onProgress) onProgress('Checking environment variables...');
18
26
  results.push(...await checkEnvVars(ctx));
27
+
28
+ if (onProgress) onProgress('Scanning for secrets...');
19
29
  results.push(...await checkSecrets(ctx));
30
+
31
+ if (onProgress) onProgress('Analyzing security configurations...');
20
32
  results.push(...await checkSecurity(ctx));
33
+
34
+ if (onProgress) onProgress('Verifying dependencies...');
21
35
  results.push(...await checkDependencies(ctx));
36
+
37
+ if (onProgress) onProgress('Inspecting build artifacts...');
22
38
  results.push(...await checkBuild(ctx));
23
- results.push(...await checkNextJs(ctx));
39
+
40
+ // Framework detection
41
+ const framework = await detectFramework(cwd);
42
+ if (framework === 'nextjs') {
43
+ if (onProgress) onProgress('Running Next.js specific checks...');
44
+ results.push(...await checkNextJs(ctx));
45
+ }
46
+
47
+ if (onProgress) onProgress('Checking git status...');
24
48
  results.push(...await checkGit(ctx));
25
49
 
26
50
  return results;
@@ -2,7 +2,16 @@ import { glob } from 'glob';
2
2
  import path from 'path';
3
3
 
4
4
  export async function scanFiles(cwd: string, ignore: string[] = []): Promise<string[]> {
5
- const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/.git/**'];
5
+ // Ignore build artifacts, node_modules, and git
6
+ const defaultIgnore = [
7
+ '**/node_modules/**',
8
+ '**/.git/**',
9
+ '**/dist/**',
10
+ '**/build/**',
11
+ '**/.next/**',
12
+ '**/.turbo/**',
13
+ '**/coverage/**'
14
+ ];
6
15
  // Scan for relevant files: JS/TS code, Configs (JSON/YAML), Env files
7
16
  return glob('**/*.{js,ts,jsx,tsx,json,yaml,yml,env,env.*}', {
8
17
  cwd,
@@ -31,43 +31,46 @@ export async function checkDependencies(ctx: RuleContext): Promise<RuleResult[]>
31
31
  return results;
32
32
  }
33
33
 
34
+ import { glob } from 'glob';
35
+
34
36
  export async function checkBuild(ctx: RuleContext): Promise<RuleResult[]> {
35
37
  const results: RuleResult[] = [];
36
38
 
37
- // Check for source maps in potential build dirs (dist, build, out, .next)
38
- // Scanner ignores dist by default, but if we want to check build artifacts we might need to scan explicitly OR assumes user runs this in root.
39
- // If the scanner ignores 'dist', we won't see them.
40
- // So this check is effective only if scanner INCLUDES build dirs or we explicitly look for them.
39
+ // Explicitly scan build folders (dist, build, .next, .output) for dangerous files
40
+ // The main scanner ignores these, so we check them separately here.
41
+
42
+ const buildDirs = ['dist', 'build', '.next', '.output'];
43
+ const foundBuildDirs = buildDirs.filter(d => fs.existsSync(path.join(ctx.cwd, d)));
41
44
 
42
- // Let's explicitly check common build folders in CWD if they exist, ignoring scanner's ignore list for this specific check?
43
- // Or just warn if we find .map files in the file list (meaning they were NOT ignored/cleaned).
45
+ if (foundBuildDirs.length === 0) {
46
+ return results;
47
+ }
44
48
 
45
- const mapFiles = ctx.files.filter(f => f.endsWith('.map'));
46
- for (const file of mapFiles) {
47
- // Only if it looks like a build artifact
48
- if (file.includes('/dist/') || file.includes('/build/') || file.includes('/.next/')) {
49
+ // 1. Check for Source Maps (.map)
50
+ // We search inside the found build directories
51
+ for (const dir of foundBuildDirs) {
52
+ const mapFiles = await glob(`${dir}/**/*.map`, { cwd: ctx.cwd, absolute: true });
53
+
54
+ for (const file of mapFiles) {
49
55
  results.push({
50
56
  status: 'warn',
51
- message: 'Source map found in build output (information leak)',
57
+ message: `Source map found in build output (${dir}) (information leak)`,
52
58
  ruleId: 'build-source-map',
53
59
  file
54
60
  });
55
61
  }
56
- }
57
62
 
58
- // Check for .env in build folders
59
- const envInBuild = ctx.files.filter(f =>
60
- (f.endsWith('.env') || f.includes('.env.')) &&
61
- (f.includes('/dist/') || f.includes('/build/') || f.includes('/.next/'))
62
- );
63
-
64
- for (const file of envInBuild) {
65
- results.push({
66
- status: 'fail',
67
- message: 'Environment file found in build output!',
68
- ruleId: 'build-env-leak',
69
- file
70
- });
63
+ // 2. Check for .env files in build output
64
+ // We look for .env* files inside the build dir
65
+ const envFiles = await glob(`${dir}/**/*.env*`, { cwd: ctx.cwd, absolute: true });
66
+ for (const file of envFiles) {
67
+ results.push({
68
+ status: 'fail',
69
+ message: `Environment file found in build output (${dir})!`,
70
+ ruleId: 'build-env-leak',
71
+ file
72
+ });
73
+ }
71
74
  }
72
75
 
73
76
  return results;