diff --git a/packages/cli/src/commands/extensions/examples/context/gemini-extension.json b/packages/cli/src/commands/extensions/examples/context/gemini-extension.json index c3fee9836e..64f3f535ac 100644 --- a/packages/cli/src/commands/extensions/examples/context/gemini-extension.json +++ b/packages/cli/src/commands/extensions/examples/context/gemini-extension.json @@ -1,5 +1,4 @@ { "name": "context-example", - "version": "1.0.0", - "contextFileName": "GEMINI.md" + "version": "1.0.0" } diff --git a/packages/cli/src/commands/extensions/new.test.ts b/packages/cli/src/commands/extensions/new.test.ts index 7d9480fcb5..62c9edcece 100644 --- a/packages/cli/src/commands/extensions/new.test.ts +++ b/packages/cli/src/commands/extensions/new.test.ts @@ -30,15 +30,22 @@ describe('extensions new command', () => { it('should fail if no path is provided', async () => { const parser = yargs([]).command(newCommand).fail(false).locale('en'); await expect(parser.parseAsync('new')).rejects.toThrow( - 'Not enough non-option arguments: got 0, need at least 2', + 'Not enough non-option arguments: got 0, need at least 1', ); }); - it('should fail if no template is provided', async () => { - const parser = yargs([]).command(newCommand).fail(false).locale('en'); - await expect(parser.parseAsync('new /some/path')).rejects.toThrow( - 'Not enough non-option arguments: got 1, need at least 2', - ); + it('should create directory when no template is provided', async () => { + mockedFs.access.mockRejectedValue(new Error('ENOENT')); + mockedFs.mkdir.mockResolvedValue(undefined); + + const parser = yargs([]).command(newCommand).fail(false); + + await parser.parseAsync('new /some/path'); + + expect(mockedFs.mkdir).toHaveBeenCalledWith('/some/path', { + recursive: true, + }); + expect(mockedFs.cp).not.toHaveBeenCalled(); }); it('should create directory and copy files when path does not exist', async () => { diff --git a/packages/cli/src/commands/extensions/new.ts b/packages/cli/src/commands/extensions/new.ts index 0403c122fb..c6455496d2 100644 --- a/packages/cli/src/commands/extensions/new.ts +++ b/packages/cli/src/commands/extensions/new.ts @@ -4,15 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { access, cp, mkdir, readdir } from 'node:fs/promises'; -import { join, dirname } from 'node:path'; +import { access, cp, mkdir, readdir, writeFile } from 'node:fs/promises'; +import { join, dirname, basename } from 'node:path'; import type { CommandModule } from 'yargs'; import { fileURLToPath } from 'node:url'; import { getErrorMessage } from '../../utils/errors.js'; interface NewArgs { path: string; - template: string; + template?: string; } const __filename = fileURLToPath(import.meta.url); @@ -29,13 +29,17 @@ async function pathExists(path: string) { } } -async function copyDirectory(template: string, path: string) { +async function createDirectory(path: string) { if (await pathExists(path)) { throw new Error(`Path already exists: ${path}`); } + await mkdir(path, { recursive: true }); +} + +async function copyDirectory(template: string, path: string) { + await createDirectory(path); const examplePath = join(EXAMPLES_PATH, template); - await mkdir(path, { recursive: true }); const entries = await readdir(examplePath, { withFileTypes: true }); for (const entry of entries) { const srcPath = join(examplePath, entry.name); @@ -46,10 +50,24 @@ async function copyDirectory(template: string, path: string) { async function handleNew(args: NewArgs) { try { - await copyDirectory(args.template, args.path); - console.log( - `Successfully created new extension from template "${args.template}" at ${args.path}.`, - ); + if (args.template) { + await copyDirectory(args.template, args.path); + console.log( + `Successfully created new extension from template "${args.template}" at ${args.path}.`, + ); + } else { + await createDirectory(args.path); + const extensionName = basename(args.path); + const manifest = { + name: extensionName, + version: '1.0.0', + }; + await writeFile( + join(args.path, 'gemini-extension.json'), + JSON.stringify(manifest, null, 2), + ); + console.log(`Successfully created new extension at ${args.path}.`); + } console.log( `You can install this using "gemini extensions link ${args.path}" to test it out.`, ); @@ -67,7 +85,7 @@ async function getBoilerplateChoices() { } export const newCommand: CommandModule = { - command: 'new