/ src / plugin-scaffold.test.ts
plugin-scaffold.test.ts
 1  /**
 2   * Tests for plugin scaffold: create new plugin directories.
 3   */
 4  
 5  import { describe, it, expect, afterEach } from 'vitest';
 6  import * as fs from 'node:fs';
 7  import * as os from 'node:os';
 8  import * as path from 'node:path';
 9  import { createPluginScaffold } from './plugin-scaffold.js';
10  
11  describe('createPluginScaffold', () => {
12    const createdDirs: string[] = [];
13  
14    afterEach(() => {
15      for (const dir of createdDirs) {
16        try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
17      }
18      createdDirs.length = 0;
19    });
20  
21    it('creates all expected files', () => {
22      const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
23      createdDirs.push(dir);
24  
25      const result = createPluginScaffold('my-test', { dir });
26      expect(result.name).toBe('my-test');
27      expect(result.dir).toBe(dir);
28      expect(result.files).toContain('opencli-plugin.json');
29      expect(result.files).toContain('package.json');
30      expect(result.files).toContain('hello.ts');
31      expect(result.files).toContain('greet.ts');
32      expect(result.files).toContain('README.md');
33  
34      // All files exist
35      for (const f of result.files) {
36        expect(fs.existsSync(path.join(dir, f))).toBe(true);
37      }
38    });
39  
40    it('generates valid opencli-plugin.json', () => {
41      const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
42      createdDirs.push(dir);
43  
44      createPluginScaffold('test-manifest', { dir, description: 'Test desc' });
45      const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'opencli-plugin.json'), 'utf-8'));
46      expect(manifest.name).toBe('test-manifest');
47      expect(manifest.version).toBe('0.1.0');
48      expect(manifest.description).toBe('Test desc');
49      expect(manifest.opencli).toMatch(/^>=/);
50    });
51  
52    it('generates ESM package.json', () => {
53      const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
54      createdDirs.push(dir);
55  
56      createPluginScaffold('test-pkg', { dir });
57      const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
58      expect(pkg.type).toBe('module');
59      expect(pkg.peerDependencies?.['@jackwener/opencli']).toBeDefined();
60    });
61  
62    it('generates a TS sample that matches the current plugin API', () => {
63      const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
64      createdDirs.push(dir);
65  
66      createPluginScaffold('test-ts', { dir });
67      const tsSample = fs.readFileSync(path.join(dir, 'greet.ts'), 'utf-8');
68  
69      expect(tsSample).toContain(`import { cli, Strategy } from '@jackwener/opencli/registry';`);
70      expect(tsSample).toContain(`strategy: Strategy.PUBLIC`);
71      expect(tsSample).toContain(`help: 'Name to greet'`);
72      expect(tsSample).toContain(`func: async (_page, kwargs)`);
73      expect(tsSample).not.toContain('async run(');
74    });
75  
76    it('documents a supported local install flow', () => {
77      const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
78      createdDirs.push(dir);
79  
80      createPluginScaffold('test-readme', { dir });
81      const readme = fs.readFileSync(path.join(dir, 'README.md'), 'utf-8');
82  
83      expect(readme).toContain(`opencli plugin install file://${dir}`);
84    });
85  
86    it('rejects invalid names', () => {
87      expect(() => createPluginScaffold('Bad_Name')).toThrow('Invalid plugin name');
88      expect(() => createPluginScaffold('123start')).toThrow('Invalid plugin name');
89    });
90  
91    it('rejects non-empty directory', () => {
92      const dir = path.join(os.tmpdir(), `opencli-scaffold-${Date.now()}`);
93      createdDirs.push(dir);
94      fs.mkdirSync(dir, { recursive: true });
95      fs.writeFileSync(path.join(dir, 'existing.txt'), 'x');
96      expect(() => createPluginScaffold('test', { dir })).toThrow('not empty');
97    });
98  });