ship18ion 1.1.0 → 1.1.3

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
@@ -27,11 +27,16 @@ program
27
27
  const config = await (0, config_1.loadConfig)(cwd);
28
28
  const spinner = (0, ora_1.default)('Initializing...').start();
29
29
  try {
30
+ let framework = 'unknown';
30
31
  if (!options.ci) {
31
- const framework = await (0, detector_1.detectFramework)(cwd);
32
+ framework = await (0, detector_1.detectFramework)(cwd);
32
33
  spinner.text = `Detected Framework: ${chalk_1.default.cyan(framework.toUpperCase())}`;
33
34
  await new Promise(r => setTimeout(r, 800)); // Brief pause to show framework
34
35
  }
36
+ else {
37
+ // Even in CI, simple detection is useful for reporting if needed, or we just skip
38
+ framework = await (0, detector_1.detectFramework)(cwd);
39
+ }
35
40
  const results = await (0, runner_1.runChecks)(config, cwd, (stage) => {
36
41
  if (!options.ci)
37
42
  spinner.text = stage;
@@ -39,7 +44,7 @@ program
39
44
  spinner.succeed(chalk_1.default.green('Checks completed!'));
40
45
  console.log('');
41
46
  // Uses console reporter for both normal and CI for now (it handles exit codes)
42
- (0, console_1.reportConsole)(results, cwd);
47
+ (0, console_1.reportConsole)(results, cwd, framework);
43
48
  }
44
49
  catch (e) {
45
50
  spinner.fail(chalk_1.default.red('Error running checks'));
@@ -13,7 +13,9 @@ async function runChecks(config, cwd, onProgress) {
13
13
  if (onProgress)
14
14
  onProgress('Scanning files...');
15
15
  const files = await (0, scanner_1.scanFiles)(cwd, config.ignore);
16
- const ctx = { config, files, cwd };
16
+ // Framework detection
17
+ const framework = await (0, detector_1.detectFramework)(cwd);
18
+ const ctx = { config, files, cwd, framework };
17
19
  const results = [];
18
20
  // Run all checks
19
21
  if (onProgress)
@@ -31,8 +33,7 @@ async function runChecks(config, cwd, onProgress) {
31
33
  if (onProgress)
32
34
  onProgress('Inspecting build artifacts...');
33
35
  results.push(...await (0, build_1.checkBuild)(ctx));
34
- // Framework detection
35
- const framework = await (0, detector_1.detectFramework)(cwd);
36
+ // Framework specific checks
36
37
  if (framework === 'nextjs') {
37
38
  if (onProgress)
38
39
  onProgress('Running Next.js specific checks...');
@@ -17,7 +17,10 @@ function getCategory(ruleId) {
17
17
  const prefix = ruleId.split('-')[0];
18
18
  return CATEGORIES[prefix] || { icon: '❓', label: 'Other' };
19
19
  }
20
- function reportConsole(results, cwd) {
20
+ function reportConsole(results, cwd, framework) {
21
+ if (framework) {
22
+ console.log(chalk_1.default.blue(`ℹ️ Framework: ${framework.toUpperCase()}`));
23
+ }
21
24
  if (results.length === 0) {
22
25
  console.log(chalk_1.default.green('\n✅ Production Readiness Check Passed!\n'));
23
26
  return;
@@ -46,12 +46,12 @@ async function checkBuild(ctx) {
46
46
  // We search inside the found build directories
47
47
  for (const dir of foundBuildDirs) {
48
48
  const mapFiles = await (0, glob_1.glob)(`${dir}/**/*.map`, { cwd: ctx.cwd, absolute: true });
49
- for (const file of mapFiles) {
49
+ if (mapFiles.length > 0) {
50
50
  results.push({
51
51
  status: 'warn',
52
- message: `Source map found in build output (${dir}) (information leak)`,
52
+ message: `Found ${mapFiles.length} source map files in '${dir}' (e.g. ${path_1.default.basename(mapFiles[0])}). Ensure these are not exposed publicly.`,
53
53
  ruleId: 'build-source-map',
54
- file
54
+ file: dir // Point to the directory itself
55
55
  });
56
56
  }
57
57
  // 2. Check for .env files in build output
package/dist/rules/git.js CHANGED
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.checkGit = checkGit;
4
7
  const child_process_1 = require("child_process");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
5
10
  async function checkGit(ctx) {
6
11
  const results = [];
7
12
  try {
13
+ // ... (Existing git checks) ...
8
14
  // Check for uncommitted changes
9
15
  const status = (0, child_process_1.execSync)('git status --porcelain', { cwd: ctx.cwd, encoding: 'utf-8' });
10
16
  if (status.trim().length > 0) {
@@ -18,17 +24,67 @@ async function checkGit(ctx) {
18
24
  const branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { cwd: ctx.cwd, encoding: 'utf-8' }).trim();
19
25
  const allowedBranches = ['main', 'master', 'staging', 'production', 'prod'];
20
26
  if (!allowedBranches.includes(branch)) {
27
+ // Warn, but maybe less aggressively? Keeping as warn.
21
28
  results.push({
22
29
  status: 'warn',
23
30
  message: `You are on branch '${branch}'. Production builds typically come from main/master.`,
24
31
  ruleId: 'git-branch',
25
32
  });
26
33
  }
34
+ // --- New: .gitignore Check ---
35
+ const gitignorePath = path_1.default.join(ctx.cwd, '.gitignore');
36
+ if (fs_1.default.existsSync(gitignorePath)) {
37
+ const content = fs_1.default.readFileSync(gitignorePath, 'utf-8');
38
+ const lines = content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
39
+ // Helper to check if item is ignored (naive grep)
40
+ const isIgnored = (item) => lines.some(l => l.includes(item));
41
+ const requiredIgnores = ['node_modules', '.env'];
42
+ if (ctx.framework === 'nextjs') {
43
+ requiredIgnores.push('.next');
44
+ }
45
+ else if (ctx.framework !== 'unknown') {
46
+ // For other frameworks, maybe 'dist' or 'build'
47
+ if (!isIgnored('dist') && !isIgnored('build')) {
48
+ // We can't strictly require one, but warn if NEITHER is found?
49
+ // Let's stick to safe defaults.
50
+ }
51
+ }
52
+ for (const item of requiredIgnores) {
53
+ if (!isIgnored(item)) {
54
+ results.push({
55
+ status: 'warn',
56
+ message: `.gitignore is missing '${item}'. This is critical for security and repo size.`,
57
+ ruleId: 'git-ignore-missing',
58
+ file: gitignorePath
59
+ });
60
+ }
61
+ }
62
+ // Check for specific dangerous files not being ignored
63
+ const dangerousPatterns = ['firebase.json', 'serviceAccountKey.json', '*.pem', '*.key'];
64
+ // This is tricky because firebase.json CAN be committed. serviceAccountKey.json should NOT.
65
+ if (!isIgnored('serviceAccountKey.json')) {
66
+ // Only warn if the FILE actually exists? Or just warn generic?
67
+ // Best to warn if the file exists AND isn't ignored.
68
+ if (fs_1.default.existsSync(path_1.default.join(ctx.cwd, 'serviceAccountKey.json'))) {
69
+ results.push({
70
+ status: 'fail',
71
+ message: 'serviceAccountKey.json exists but is NOT in .gitignore!',
72
+ ruleId: 'git-ignore-auth',
73
+ file: gitignorePath
74
+ });
75
+ }
76
+ }
77
+ }
78
+ else {
79
+ results.push({
80
+ status: 'warn',
81
+ message: 'No .gitignore file found! node_modules and secrets might be committed.',
82
+ ruleId: 'git-no-ignore',
83
+ });
84
+ }
27
85
  }
28
86
  catch (e) {
29
87
  // Not a git repo or git not found
30
- // Silently fail or warn?
31
- // results.push({ status: 'warn', message: 'Not a git repository or git command failed.', ruleId: 'git-error' });
32
88
  }
33
89
  return results;
34
90
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ship18ion",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
4
4
  "description": "",
5
5
  "main": "dist/cli/index.js",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -27,10 +27,14 @@ program
27
27
  const spinner = ora('Initializing...').start();
28
28
 
29
29
  try {
30
+ let framework: string = 'unknown';
30
31
  if (!options.ci) {
31
- const framework = await detectFramework(cwd);
32
+ framework = await detectFramework(cwd);
32
33
  spinner.text = `Detected Framework: ${chalk.cyan(framework.toUpperCase())}`;
33
34
  await new Promise(r => setTimeout(r, 800)); // Brief pause to show framework
35
+ } else {
36
+ // Even in CI, simple detection is useful for reporting if needed, or we just skip
37
+ framework = await detectFramework(cwd);
34
38
  }
35
39
 
36
40
  const results = await runChecks(config, cwd, (stage) => {
@@ -41,7 +45,7 @@ program
41
45
  console.log('');
42
46
 
43
47
  // Uses console reporter for both normal and CI for now (it handles exit codes)
44
- reportConsole(results, cwd);
48
+ reportConsole(results, cwd, framework);
45
49
  } catch (e) {
46
50
  spinner.fail(chalk.red('Error running checks'));
47
51
  console.error(e);
@@ -17,7 +17,10 @@ export async function runChecks(
17
17
  ): Promise<RuleResult[]> {
18
18
  if (onProgress) onProgress('Scanning files...');
19
19
  const files = await scanFiles(cwd, config.ignore);
20
- const ctx: RuleContext = { config, files, cwd };
20
+ // Framework detection
21
+ const framework = await detectFramework(cwd);
22
+
23
+ const ctx: RuleContext = { config, files, cwd, framework };
21
24
 
22
25
  const results: RuleResult[] = [];
23
26
 
@@ -37,8 +40,7 @@ export async function runChecks(
37
40
  if (onProgress) onProgress('Inspecting build artifacts...');
38
41
  results.push(...await checkBuild(ctx));
39
42
 
40
- // Framework detection
41
- const framework = await detectFramework(cwd);
43
+ // Framework specific checks
42
44
  if (framework === 'nextjs') {
43
45
  if (onProgress) onProgress('Running Next.js specific checks...');
44
46
  results.push(...await checkNextJs(ctx));
@@ -1,4 +1,5 @@
1
1
  import { Ship18ionConfig } from './config';
2
+ import { FrameworkType } from './detector';
2
3
 
3
4
  export interface RuleResult {
4
5
  status: 'pass' | 'fail' | 'warn';
@@ -12,4 +13,5 @@ export interface RuleContext {
12
13
  config: Ship18ionConfig;
13
14
  files: string[];
14
15
  cwd: string;
16
+ framework: FrameworkType;
15
17
  }
@@ -15,7 +15,11 @@ function getCategory(ruleId: string) {
15
15
  return CATEGORIES[prefix] || { icon: '❓', label: 'Other' };
16
16
  }
17
17
 
18
- export function reportConsole(results: RuleResult[], cwd: string) {
18
+ export function reportConsole(results: RuleResult[], cwd: string, framework?: string) {
19
+ if (framework) {
20
+ console.log(chalk.blue(`ℹ️ Framework: ${framework.toUpperCase()}`));
21
+ }
22
+
19
23
  if (results.length === 0) {
20
24
  console.log(chalk.green('\n✅ Production Readiness Check Passed!\n'));
21
25
  return;
@@ -51,12 +51,12 @@ export async function checkBuild(ctx: RuleContext): Promise<RuleResult[]> {
51
51
  for (const dir of foundBuildDirs) {
52
52
  const mapFiles = await glob(`${dir}/**/*.map`, { cwd: ctx.cwd, absolute: true });
53
53
 
54
- for (const file of mapFiles) {
54
+ if (mapFiles.length > 0) {
55
55
  results.push({
56
56
  status: 'warn',
57
- message: `Source map found in build output (${dir}) (information leak)`,
57
+ message: `Found ${mapFiles.length} source map files in '${dir}' (e.g. ${path.basename(mapFiles[0])}). Ensure these are not exposed publicly.`,
58
58
  ruleId: 'build-source-map',
59
- file
59
+ file: dir // Point to the directory itself
60
60
  });
61
61
  }
62
62
 
package/src/rules/git.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import { execSync } from 'child_process';
2
2
  import { RuleContext, RuleResult } from '../engine/types';
3
3
 
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
4
7
  export async function checkGit(ctx: RuleContext): Promise<RuleResult[]> {
5
8
  const results: RuleResult[] = [];
6
9
 
7
10
  try {
11
+ // ... (Existing git checks) ...
8
12
  // Check for uncommitted changes
9
13
  const status = execSync('git status --porcelain', { cwd: ctx.cwd, encoding: 'utf-8' });
10
14
  if (status.trim().length > 0) {
@@ -19,6 +23,7 @@ export async function checkGit(ctx: RuleContext): Promise<RuleResult[]> {
19
23
  const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: ctx.cwd, encoding: 'utf-8' }).trim();
20
24
  const allowedBranches = ['main', 'master', 'staging', 'production', 'prod'];
21
25
  if (!allowedBranches.includes(branch)) {
26
+ // Warn, but maybe less aggressively? Keeping as warn.
22
27
  results.push({
23
28
  status: 'warn',
24
29
  message: `You are on branch '${branch}'. Production builds typically come from main/master.`,
@@ -26,10 +31,64 @@ export async function checkGit(ctx: RuleContext): Promise<RuleResult[]> {
26
31
  });
27
32
  }
28
33
 
34
+ // --- New: .gitignore Check ---
35
+ const gitignorePath = path.join(ctx.cwd, '.gitignore');
36
+ if (fs.existsSync(gitignorePath)) {
37
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
38
+ const lines = content.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
39
+
40
+ // Helper to check if item is ignored (naive grep)
41
+ const isIgnored = (item: string) => lines.some(l => l.includes(item));
42
+
43
+ const requiredIgnores = ['node_modules', '.env'];
44
+ if (ctx.framework === 'nextjs') {
45
+ requiredIgnores.push('.next');
46
+ } else if (ctx.framework !== 'unknown') {
47
+ // For other frameworks, maybe 'dist' or 'build'
48
+ if (!isIgnored('dist') && !isIgnored('build')) {
49
+ // We can't strictly require one, but warn if NEITHER is found?
50
+ // Let's stick to safe defaults.
51
+ }
52
+ }
53
+
54
+ for (const item of requiredIgnores) {
55
+ if (!isIgnored(item)) {
56
+ results.push({
57
+ status: 'warn',
58
+ message: `.gitignore is missing '${item}'. This is critical for security and repo size.`,
59
+ ruleId: 'git-ignore-missing',
60
+ file: gitignorePath
61
+ });
62
+ }
63
+ }
64
+
65
+ // Check for specific dangerous files not being ignored
66
+ const dangerousPatterns = ['firebase.json', 'serviceAccountKey.json', '*.pem', '*.key'];
67
+ // This is tricky because firebase.json CAN be committed. serviceAccountKey.json should NOT.
68
+
69
+ if (!isIgnored('serviceAccountKey.json')) {
70
+ // Only warn if the FILE actually exists? Or just warn generic?
71
+ // Best to warn if the file exists AND isn't ignored.
72
+ if (fs.existsSync(path.join(ctx.cwd, 'serviceAccountKey.json'))) {
73
+ results.push({
74
+ status: 'fail',
75
+ message: 'serviceAccountKey.json exists but is NOT in .gitignore!',
76
+ ruleId: 'git-ignore-auth',
77
+ file: gitignorePath
78
+ });
79
+ }
80
+ }
81
+
82
+ } else {
83
+ results.push({
84
+ status: 'warn',
85
+ message: 'No .gitignore file found! node_modules and secrets might be committed.',
86
+ ruleId: 'git-no-ignore',
87
+ });
88
+ }
89
+
29
90
  } catch (e) {
30
91
  // Not a git repo or git not found
31
- // Silently fail or warn?
32
- // results.push({ status: 'warn', message: 'Not a git repository or git command failed.', ruleId: 'git-error' });
33
92
  }
34
93
 
35
94
  return results;