servcraft 0.2.0 → 0.4.2
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/.github/workflows/ci.yml +9 -4
- package/README.md +70 -2
- package/ROADMAP.md +124 -47
- package/dist/cli/index.cjs +1331 -407
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +1298 -389
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/completion.ts +146 -0
- package/src/cli/commands/doctor.ts +116 -1
- package/src/cli/commands/generate.ts +52 -7
- package/src/cli/commands/list.ts +1 -1
- package/src/cli/commands/scaffold.ts +211 -0
- package/src/cli/commands/templates.ts +147 -0
- package/src/cli/commands/update.ts +221 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/templates/controller-test.ts +110 -0
- package/src/cli/templates/integration-test.ts +139 -0
- package/src/cli/templates/service-test.ts +100 -0
- package/src/cli/utils/template-loader.ts +80 -0
- package/tests/cli/add.test.ts +32 -0
- package/tests/cli/completion.test.ts +35 -0
- package/tests/cli/doctor.test.ts +23 -0
- package/tests/cli/dry-run.test.ts +39 -0
- package/tests/cli/errors.test.ts +29 -0
- package/tests/cli/generate.test.ts +39 -0
- package/tests/cli/init.test.ts +63 -0
- package/tests/cli/list.test.ts +25 -0
- package/tests/cli/remove.test.ts +28 -0
- package/tests/cli/update.test.ts +34 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export function serviceTestTemplate(name: string, pascalName: string, camelName: string): string {
|
|
2
|
+
return `import { describe, it, expect, beforeEach } from 'vitest';
|
|
3
|
+
import { ${pascalName}Service } from '../${name}.service.js';
|
|
4
|
+
|
|
5
|
+
describe('${pascalName}Service', () => {
|
|
6
|
+
let service: ${pascalName}Service;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
service = new ${pascalName}Service();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('getAll', () => {
|
|
13
|
+
it('should return all ${name}', async () => {
|
|
14
|
+
const result = await service.getAll();
|
|
15
|
+
|
|
16
|
+
expect(result).toBeDefined();
|
|
17
|
+
expect(Array.isArray(result)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should apply pagination', async () => {
|
|
21
|
+
const result = await service.getAll({ page: 1, limit: 10 });
|
|
22
|
+
|
|
23
|
+
expect(result).toBeDefined();
|
|
24
|
+
expect(result.length).toBeLessThanOrEqual(10);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('getById', () => {
|
|
29
|
+
it('should return a ${camelName} by id', async () => {
|
|
30
|
+
// TODO: Create test ${camelName} first
|
|
31
|
+
const id = '1';
|
|
32
|
+
const result = await service.getById(id);
|
|
33
|
+
|
|
34
|
+
expect(result).toBeDefined();
|
|
35
|
+
expect(result.id).toBe(id);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return null for non-existent id', async () => {
|
|
39
|
+
const result = await service.getById('999999');
|
|
40
|
+
|
|
41
|
+
expect(result).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('create', () => {
|
|
46
|
+
it('should create a new ${camelName}', async () => {
|
|
47
|
+
const data = {
|
|
48
|
+
// TODO: Add required fields
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const result = await service.create(data);
|
|
52
|
+
|
|
53
|
+
expect(result).toBeDefined();
|
|
54
|
+
expect(result.id).toBeDefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should throw error for invalid data', async () => {
|
|
58
|
+
await expect(service.create({} as any)).rejects.toThrow();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('update', () => {
|
|
63
|
+
it('should update a ${camelName}', async () => {
|
|
64
|
+
// TODO: Create test ${camelName} first
|
|
65
|
+
const id = '1';
|
|
66
|
+
const updates = {
|
|
67
|
+
// TODO: Add fields to update
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const result = await service.update(id, updates);
|
|
71
|
+
|
|
72
|
+
expect(result).toBeDefined();
|
|
73
|
+
expect(result.id).toBe(id);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should return null for non-existent id', async () => {
|
|
77
|
+
const result = await service.update('999999', {});
|
|
78
|
+
|
|
79
|
+
expect(result).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('delete', () => {
|
|
84
|
+
it('should delete a ${camelName}', async () => {
|
|
85
|
+
// TODO: Create test ${camelName} first
|
|
86
|
+
const id = '1';
|
|
87
|
+
const result = await service.delete(id);
|
|
88
|
+
|
|
89
|
+
expect(result).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return false for non-existent id', async () => {
|
|
93
|
+
const result = await service.delete('999999');
|
|
94
|
+
|
|
95
|
+
expect(result).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getProjectRoot } from './helpers.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Template loader that checks for custom templates in priority order:
|
|
7
|
+
* 1. Project templates (.servcraft/templates/)
|
|
8
|
+
* 2. User templates (~/.servcraft/templates/)
|
|
9
|
+
* 3. Built-in templates (fallback)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type TemplateType =
|
|
13
|
+
| 'controller'
|
|
14
|
+
| 'service'
|
|
15
|
+
| 'repository'
|
|
16
|
+
| 'types'
|
|
17
|
+
| 'schemas'
|
|
18
|
+
| 'routes'
|
|
19
|
+
| 'module-index'
|
|
20
|
+
| 'controller-test'
|
|
21
|
+
| 'service-test'
|
|
22
|
+
| 'integration-test';
|
|
23
|
+
|
|
24
|
+
interface TemplateFunction {
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
[key: string]: (...args: any[]) => string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Load a custom template if available, otherwise return null
|
|
31
|
+
*/
|
|
32
|
+
export async function loadCustomTemplate(
|
|
33
|
+
templateType: TemplateType
|
|
34
|
+
): Promise<TemplateFunction | null> {
|
|
35
|
+
const projectRoot = getProjectRoot();
|
|
36
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
37
|
+
|
|
38
|
+
const locations = [
|
|
39
|
+
path.join(projectRoot, '.servcraft', 'templates', `${templateType}.ts`),
|
|
40
|
+
path.join(homeDir, '.servcraft', 'templates', `${templateType}.ts`),
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
for (const location of locations) {
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(location);
|
|
46
|
+
// Template file exists, dynamically import it
|
|
47
|
+
const templateModule = (await import(`file://${location}`)) as TemplateFunction;
|
|
48
|
+
|
|
49
|
+
// Look for the template function (e.g., controllerTemplate, serviceTemplate)
|
|
50
|
+
const functionName = `${templateType.replace(/-/g, '')}Template`;
|
|
51
|
+
|
|
52
|
+
if (templateModule[functionName]) {
|
|
53
|
+
return templateModule;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// File doesn't exist or import failed, try next location
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get template function with fallback to built-in
|
|
66
|
+
*/
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
export async function getTemplate<T extends (...args: any[]) => string>(
|
|
69
|
+
templateType: TemplateType,
|
|
70
|
+
builtInTemplate: T
|
|
71
|
+
): Promise<T> {
|
|
72
|
+
const customTemplate = await loadCustomTemplate(templateType);
|
|
73
|
+
|
|
74
|
+
if (customTemplate) {
|
|
75
|
+
const functionName = `${templateType.replace(/-/g, '')}Template`;
|
|
76
|
+
return customTemplate[functionName] as T;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return builtInTemplate;
|
|
80
|
+
}
|
|
@@ -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
|
+
});
|