servcraft 0.1.7 → 0.3.1

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.
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
+ import { DryRunManager } from './dry-run.js';
4
5
 
5
6
  export function toPascalCase(str: string): string {
6
7
  return str
@@ -52,6 +53,18 @@ export async function ensureDir(dirPath: string): Promise<void> {
52
53
  }
53
54
 
54
55
  export async function writeFile(filePath: string, content: string): Promise<void> {
56
+ const dryRun = DryRunManager.getInstance();
57
+
58
+ if (dryRun.isEnabled()) {
59
+ dryRun.addOperation({
60
+ type: 'create',
61
+ path: dryRun.relativePath(filePath),
62
+ content,
63
+ size: content.length,
64
+ });
65
+ return;
66
+ }
67
+
55
68
  await ensureDir(path.dirname(filePath));
56
69
  await fs.writeFile(filePath, content, 'utf-8');
57
70
  }
@@ -0,0 +1,32 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft add', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should show error when not in a servcraft project', () => {
8
+ try {
9
+ execSync(`node ${cliPath} add auth`, { encoding: 'utf-8', stdio: 'pipe' });
10
+ } catch (err) {
11
+ const error = err as { stdout?: string; stderr?: string };
12
+ const output = error.stdout || error.stderr || '';
13
+ expect(output).toContain('Failed to validate project');
14
+ }
15
+ });
16
+
17
+ it('should show error for invalid module name', () => {
18
+ try {
19
+ execSync(`node ${cliPath} add invalid-module-xyz`, { encoding: 'utf-8', stdio: 'pipe' });
20
+ } catch (err) {
21
+ const error = err as { stdout?: string; stderr?: string };
22
+ const output = error.stdout || error.stderr || '';
23
+ expect(output).toContain('Module "invalid-module-xyz" not found');
24
+ }
25
+ });
26
+
27
+ it('should show help with --help flag', () => {
28
+ const output = execSync(`node ${cliPath} add --help`, { encoding: 'utf-8' });
29
+ expect(output).toContain('Add a pre-built module');
30
+ expect(output).toContain('--dry-run');
31
+ });
32
+ });
@@ -0,0 +1,35 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft completion', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should generate bash completion script', () => {
8
+ const output = execSync(`node ${cliPath} completion bash`, { encoding: 'utf-8' });
9
+ expect(output).toContain('_servcraft_completions');
10
+ expect(output).toContain('complete -F _servcraft_completions servcraft');
11
+ });
12
+
13
+ it('should generate zsh completion script', () => {
14
+ const output = execSync(`node ${cliPath} completion zsh`, { encoding: 'utf-8' });
15
+ expect(output).toContain('#compdef servcraft');
16
+ expect(output).toContain('_servcraft');
17
+ });
18
+
19
+ it('should show error for unsupported shell', () => {
20
+ try {
21
+ execSync(`node ${cliPath} completion fish`, { encoding: 'utf-8', stdio: 'pipe' });
22
+ } catch (err) {
23
+ const error = err as { stdout?: string; stderr?: string };
24
+ const output = error.stdout || error.stderr || '';
25
+ expect(output).toContain('Unsupported shell: fish');
26
+ expect(output).toContain('Supported shells: bash, zsh');
27
+ }
28
+ });
29
+
30
+ it('should show help with --help flag', () => {
31
+ const output = execSync(`node ${cliPath} completion --help`, { encoding: 'utf-8' });
32
+ expect(output).toContain('Generate shell completion scripts');
33
+ expect(output).toContain('Shell type (bash or zsh)');
34
+ });
35
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft doctor', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should run diagnostics', () => {
8
+ const output = execSync(`node ${cliPath} doctor`, { encoding: 'utf-8' });
9
+ expect(output).toContain('ServCraft Doctor');
10
+ expect(output).toContain('Node.js');
11
+ expect(output).toContain('package.json');
12
+ });
13
+
14
+ it('should check Node.js version', () => {
15
+ const output = execSync(`node ${cliPath} doctor`, { encoding: 'utf-8' });
16
+ expect(output).toMatch(/Node\.js.*v\d+\.\d+\.\d+/);
17
+ });
18
+
19
+ it('should display summary', () => {
20
+ const output = execSync(`node ${cliPath} doctor`, { encoding: 'utf-8' });
21
+ expect(output).toMatch(/\d+ passed/);
22
+ });
23
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect, afterAll } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+
6
+ describe('--dry-run option', () => {
7
+ const cliPath = './dist/cli/index.js';
8
+ const testDir = 'dry-test-' + Date.now();
9
+
10
+ afterAll(() => {
11
+ if (fs.existsSync(testDir)) {
12
+ fs.rmSync(testDir, { recursive: true, force: true });
13
+ }
14
+ });
15
+
16
+ it('should preview init without creating files', () => {
17
+ const output = execSync(`node ${cliPath} init ${testDir} --yes --dry-run`, {
18
+ encoding: 'utf-8',
19
+ });
20
+
21
+ expect(output).toContain('DRY RUN MODE');
22
+ expect(output).toContain('Dry Run - Preview of changes');
23
+ expect(output).toContain('package.json');
24
+ expect(output).toContain('Total operations');
25
+
26
+ // Check that package.json was not actually created
27
+ const pkgPath = path.join(testDir, 'package.json');
28
+ expect(fs.existsSync(pkgPath)).toBe(false);
29
+ });
30
+
31
+ it('should show file count in dry-run', () => {
32
+ const testName = 'test-app-' + Date.now();
33
+ const output = execSync(`node ${cliPath} init ${testName} --yes --dry-run`, {
34
+ encoding: 'utf-8',
35
+ });
36
+ expect(output).toMatch(/Total operations: \d+/);
37
+ expect(output).toMatch(/\d+ create/);
38
+ });
39
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('error handling', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should show helpful error for invalid module', () => {
8
+ try {
9
+ execSync(`node ${cliPath} add invalid-module-xyz`, { encoding: 'utf-8', stdio: 'pipe' });
10
+ } catch (err) {
11
+ const error = err as { stdout?: string; stderr?: string };
12
+ const output = error.stdout || error.stderr || '';
13
+ expect(output).toContain('Module "invalid-module-xyz" not found');
14
+ expect(output).toContain('Suggestions');
15
+ expect(output).toContain('servcraft list');
16
+ }
17
+ });
18
+
19
+ it('should show error with documentation link', () => {
20
+ try {
21
+ execSync(`node ${cliPath} add nonexistent`, { encoding: 'utf-8', stdio: 'pipe' });
22
+ } catch (err) {
23
+ const error = err as { stdout?: string; stderr?: string };
24
+ const output = error.stdout || error.stderr || '';
25
+ expect(output).toContain('Documentation:');
26
+ expect(output).toContain('github.com');
27
+ }
28
+ });
29
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft generate', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should generate controller with dry-run', () => {
8
+ const uniqueName = 'testuser' + Date.now();
9
+ const output = execSync(`node ${cliPath} generate controller ${uniqueName} --dry-run`, {
10
+ encoding: 'utf-8',
11
+ });
12
+
13
+ expect(output).toContain('DRY RUN MODE');
14
+ expect(output).toContain(`${uniqueName}.controller.ts`);
15
+ expect(output).toContain('Total operations');
16
+ });
17
+
18
+ it('should generate service with dry-run', () => {
19
+ const uniqueName = 'testproduct' + Date.now();
20
+ const output = execSync(`node ${cliPath} g s ${uniqueName} --dry-run`, {
21
+ encoding: 'utf-8',
22
+ });
23
+
24
+ expect(output).toContain('DRY RUN MODE');
25
+ expect(output).toContain(`${uniqueName}.service.ts`);
26
+ });
27
+
28
+ it('should generate module with dry-run', () => {
29
+ const uniqueName = 'testorder' + Date.now();
30
+ const output = execSync(`node ${cliPath} g m ${uniqueName} --dry-run`, {
31
+ encoding: 'utf-8',
32
+ });
33
+
34
+ expect(output).toContain('DRY RUN MODE');
35
+ expect(output).toContain(`${uniqueName}.controller.ts`);
36
+ expect(output).toContain(`${uniqueName}.service.ts`);
37
+ expect(output).toContain(`${uniqueName}.routes.ts`);
38
+ });
39
+ });
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+ import fs from 'fs';
4
+
5
+ describe('servcraft init', () => {
6
+ const cliPath = './dist/cli/index.js';
7
+ const testProjects: string[] = [];
8
+
9
+ afterEach(() => {
10
+ testProjects.forEach((dir) => {
11
+ if (fs.existsSync(dir)) {
12
+ fs.rmSync(dir, { recursive: true, force: true });
13
+ }
14
+ });
15
+ testProjects.length = 0;
16
+ });
17
+
18
+ it('should create project structure with --dry-run', () => {
19
+ const projectName = 'test-init-' + Date.now();
20
+
21
+ const output = execSync(`node ${cliPath} init ${projectName} --yes --dry-run`, {
22
+ encoding: 'utf-8',
23
+ });
24
+
25
+ expect(output).toContain('Servcraft Project Generator');
26
+ expect(output).toContain('package.json');
27
+ expect(output).toContain('src/');
28
+ expect(output).toContain('DRY RUN MODE');
29
+ });
30
+
31
+ it('should support --js flag with dry-run', () => {
32
+ const projectName = 'test-js-' + Date.now();
33
+
34
+ const output = execSync(`node ${cliPath} init ${projectName} --yes --js --dry-run`, {
35
+ encoding: 'utf-8',
36
+ });
37
+
38
+ expect(output).toContain('package.json');
39
+ expect(output).toContain('DRY RUN MODE');
40
+ });
41
+
42
+ it('should support --cjs flag with dry-run', () => {
43
+ const projectName = 'test-cjs-' + Date.now();
44
+
45
+ const output = execSync(`node ${cliPath} init ${projectName} --yes --cjs --dry-run`, {
46
+ encoding: 'utf-8',
47
+ });
48
+
49
+ expect(output).toContain('package.json');
50
+ expect(output).toContain('DRY RUN MODE');
51
+ });
52
+
53
+ it('should support --esm flag with dry-run', () => {
54
+ const projectName = 'test-esm-' + Date.now();
55
+
56
+ const output = execSync(`node ${cliPath} init ${projectName} --yes --esm --dry-run`, {
57
+ encoding: 'utf-8',
58
+ });
59
+
60
+ expect(output).toContain('package.json');
61
+ expect(output).toContain('module');
62
+ });
63
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft list', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should display available modules', () => {
8
+ const output = execSync(`node ${cliPath} list`, { encoding: 'utf-8' });
9
+ expect(output).toContain('Available Modules');
10
+ expect(output).toContain('Core');
11
+ expect(output).toContain('Security');
12
+ });
13
+
14
+ it('should support --json output', () => {
15
+ const output = execSync(`node ${cliPath} list --json`, { encoding: 'utf-8' });
16
+ const data = JSON.parse(output);
17
+ expect(data).toHaveProperty('available');
18
+ expect(Array.isArray(data.available)).toBe(true);
19
+ });
20
+
21
+ it('should work with alias ls', () => {
22
+ const output = execSync(`node ${cliPath} ls`, { encoding: 'utf-8' });
23
+ expect(output).toContain('Available Modules');
24
+ });
25
+ });
@@ -0,0 +1,28 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft remove', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should show error when not in a servcraft project', () => {
8
+ try {
9
+ execSync(`node ${cliPath} remove auth`, { encoding: 'utf-8', stdio: 'pipe' });
10
+ } catch (err) {
11
+ const error = err as { stdout?: string; stderr?: string };
12
+ const output = error.stdout || error.stderr || '';
13
+ expect(output).toContain('Failed to validate project');
14
+ }
15
+ });
16
+
17
+ it('should show help with --help flag', () => {
18
+ const output = execSync(`node ${cliPath} remove --help`, { encoding: 'utf-8' });
19
+ expect(output).toContain('Remove an installed module');
20
+ expect(output).toContain('--yes');
21
+ expect(output).toContain('--keep-env');
22
+ });
23
+
24
+ it('should work with alias rm', () => {
25
+ const output = execSync(`node ${cliPath} rm --help`, { encoding: 'utf-8' });
26
+ expect(output).toContain('Remove an installed module');
27
+ });
28
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { execSync } from 'child_process';
3
+
4
+ describe('servcraft update', () => {
5
+ const cliPath = './dist/cli/index.js';
6
+
7
+ it('should show error when not in a servcraft project', () => {
8
+ try {
9
+ execSync(`node ${cliPath} update`, { encoding: 'utf-8', stdio: 'pipe' });
10
+ } catch (err) {
11
+ const error = err as { stdout?: string; stderr?: string };
12
+ const output = error.stdout || error.stderr || '';
13
+ expect(output).toContain('Failed to validate project');
14
+ }
15
+ });
16
+
17
+ it('should show help with --help flag', () => {
18
+ const output = execSync(`node ${cliPath} update --help`, { encoding: 'utf-8' });
19
+ expect(output).toContain('Update installed modules');
20
+ expect(output).toContain('--check');
21
+ expect(output).toContain('--yes');
22
+ });
23
+
24
+ it('should support --check flag', () => {
25
+ try {
26
+ execSync(`node ${cliPath} update --check`, { encoding: 'utf-8', stdio: 'pipe' });
27
+ } catch (err) {
28
+ const error = err as { stdout?: string; stderr?: string };
29
+ const output = error.stdout || error.stderr || '';
30
+ // Either shows error (not in project) or check output
31
+ expect(output.length).toBeGreaterThan(0);
32
+ }
33
+ });
34
+ });