๐Ÿ“ฆ google-gemini / gemini-cli

๐Ÿ“„ skill-creator-scripts.test.ts ยท 105 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105/**
 * @license
 * Copyright 2026 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { TestRig } from './test-helper.js';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { execSync } from 'node:child_process';

describe('skill-creator scripts e2e', () => {
  let rig: TestRig;
  const initScript = path.resolve(
    'packages/core/src/skills/builtin/skill-creator/scripts/init_skill.cjs',
  );
  const validateScript = path.resolve(
    'packages/core/src/skills/builtin/skill-creator/scripts/validate_skill.cjs',
  );
  const packageScript = path.resolve(
    'packages/core/src/skills/builtin/skill-creator/scripts/package_skill.cjs',
  );

  beforeEach(() => {
    rig = new TestRig();
  });

  afterEach(async () => {
    await rig.cleanup();
  });

  it('should initialize, validate, and package a skill', async () => {
    await rig.setup('skill-creator scripts e2e');
    const skillName = 'e2e-test-skill';
    const tempDir = rig.testDir!;

    // 1. Initialize
    execSync(`node "${initScript}" ${skillName} --path "${tempDir}"`, {
      stdio: 'inherit',
    });
    const skillDir = path.join(tempDir, skillName);

    expect(fs.existsSync(skillDir)).toBe(true);
    expect(fs.existsSync(path.join(skillDir, 'SKILL.md'))).toBe(true);
    expect(
      fs.existsSync(path.join(skillDir, 'scripts/example_script.cjs')),
    ).toBe(true);

    // 2. Validate (should have warning initially due to TODOs)
    const validateOutputInitial = execSync(
      `node "${validateScript}" "${skillDir}" 2>&1`,
      { encoding: 'utf8' },
    );
    expect(validateOutputInitial).toContain('โš ๏ธ  Found unresolved TODO');

    // 3. Package (should fail due to TODOs)
    try {
      execSync(`node "${packageScript}" "${skillDir}" "${tempDir}"`, {
        stdio: 'pipe',
      });
      throw new Error('Packaging should have failed due to TODOs');
    } catch (err: unknown) {
      expect((err as Error).message).toContain('Command failed');
    }

    // 4. Fix SKILL.md (remove TODOs)
    let content = fs.readFileSync(path.join(skillDir, 'SKILL.md'), 'utf8');
    // More aggressive global replace for all TODO patterns
    content = content.replace(/TODO:[^\n]*/g, 'Fixed');
    content = content.replace(/\[TODO:[^\]]*\]/g, 'Fixed');
    fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);

    // Also remove TODOs from example scripts
    const exampleScriptPath = path.join(skillDir, 'scripts/example_script.cjs');
    let scriptContent = fs.readFileSync(exampleScriptPath, 'utf8');
    scriptContent = scriptContent.replace(/TODO:[^\n]*/g, 'Fixed');
    fs.writeFileSync(exampleScriptPath, scriptContent);

    // 4. Validate again (should pass now)
    const validateOutput = execSync(`node "${validateScript}" "${skillDir}"`, {
      encoding: 'utf8',
    });
    expect(validateOutput).toContain('Skill is valid!');

    // 5. Package
    execSync(`node "${packageScript}" "${skillDir}" "${tempDir}"`, {
      stdio: 'inherit',
    });
    const skillFile = path.join(tempDir, `${skillName}.skill`);
    expect(fs.existsSync(skillFile)).toBe(true);

    // 6. Verify zip content (should NOT have nested directory)
    // Use unzip -l if available, otherwise fallback to tar -tf (common on Windows)
    let zipList: string;
    try {
      zipList = execSync(`unzip -l "${skillFile}"`, { encoding: 'utf8' });
    } catch {
      zipList = execSync(`tar -tf "${skillFile}"`, { encoding: 'utf8' });
    }
    expect(zipList).toContain('SKILL.md');
    expect(zipList).not.toContain(`${skillName}/SKILL.md`);
  });
});