Update gemini extensions new (#10629)

This commit is contained in:
christine betts
2025-10-07 09:10:21 -04:00
committed by GitHub
parent 5a0b21b1d1
commit 69f93f852e
3 changed files with 43 additions and 19 deletions

View File

@@ -1,5 +1,4 @@
{
"name": "context-example",
"version": "1.0.0",
"contextFileName": "GEMINI.md"
"version": "1.0.0"
}

View File

@@ -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 () => {

View File

@@ -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 <path> <template>',
command: 'new <path> [template]',
describe: 'Create a new extension from a boilerplate example.',
builder: async (yargs) => {
const choices = await getBoilerplateChoices();
@@ -85,7 +103,7 @@ export const newCommand: CommandModule = {
handler: async (args) => {
await handleNew({
path: args['path'] as string,
template: args['template'] as string,
template: args['template'] as string | undefined,
});
},
};