Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4f4970a
add an auto-update setting
jakemac53 Sep 8, 2025
f4e59f0
add tests, fix bug revealed by tests
jakemac53 Sep 8, 2025
a62e265
change default in schema to false, and requires restart to true
jakemac53 Sep 12, 2025
3dee48b
Add useExtensionUpdates, use it to log messages.
jakemac53 Sep 15, 2025
6534307
add missing return
jakemac53 Sep 15, 2025
43b3b81
add tests for useExtensionUpdates
jakemac53 Sep 16, 2025
7238216
Merge branch 'main' into do-auto-update
jakemac53 Sep 16, 2025
4fa72b5
run preflight
jakemac53 Sep 16, 2025
f0eb076
move hook out to ui/hooks/useExtensionUpdates.ts
jakemac53 Sep 16, 2025
7346fa5
fix a bunch of tests
jakemac53 Sep 16, 2025
49500ff
move update logic to config/extensions/update.ts
jakemac53 Sep 16, 2025
6a979de
move update tests next to the implementation
jakemac53 Sep 16, 2025
12983cb
move auto update config to extension install metadata
jakemac53 Sep 16, 2025
8c85a83
fix some more errors
jakemac53 Sep 16, 2025
eb96131
Merge branch 'main' into do-auto-update
jakemac53 Sep 16, 2025
48505f2
Merge branch 'main' into do-auto-update
jakemac53 Sep 16, 2025
98ddffa
Merge branch 'main' into do-auto-update
jakemac53 Sep 17, 2025
c1c838a
fix up merge errors
jakemac53 Sep 17, 2025
fc5f8a3
Merge branch 'main' into do-auto-update
jakemac53 Sep 17, 2025
c2968e8
add back FocusContext import
jakemac53 Sep 17, 2025
f3d55f6
fix up extension tests
jakemac53 Sep 17, 2025
3468ac4
use useEffect, fix simpleGit usage
jakemac53 Sep 18, 2025
dcb9038
Merge branch 'main' into do-auto-update
jakemac53 Sep 18, 2025
5783757
go back to useMemo
jakemac53 Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion packages/cli/src/commands/extensions/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect, type MockInstance } from 'vitest';
import { describe, it, expect, vi, type MockInstance } from 'vitest';
import { handleInstall, installCommand } from './install.js';
import yargs from 'yargs';

Expand Down Expand Up @@ -32,6 +32,15 @@ describe('extensions install command', () => {
validationParser.parse('install some-url --path /some/path'),
).toThrow('Arguments source and path are mutually exclusive');
});

it('should fail if both auto update and local path are provided', () => {
const validationParser = yargs([]).command(installCommand).fail(false);
expect(() =>
validationParser.parse(
'install some-url --path /some/path --auto-update',
),
).toThrow('Arguments path and auto-update are mutually exclusive');
});
});

describe('handleInstall', () => {
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/commands/extensions/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface InstallArgs {
source?: string;
path?: string;
ref?: string;
autoUpdate?: boolean;
}

export async function handleInstall(args: InstallArgs) {
Expand All @@ -32,6 +33,7 @@ export async function handleInstall(args: InstallArgs) {
source,
type: 'git',
ref: args.ref,
autoUpdate: args.autoUpdate,
};
} else {
throw new Error(`The source "${source}" is not a valid URL format.`);
Expand All @@ -40,6 +42,7 @@ export async function handleInstall(args: InstallArgs) {
installMetadata = {
source: args.path,
type: 'local',
autoUpdate: args.autoUpdate,
};
} else {
// This should not be reached due to the yargs check.
Expand Down Expand Up @@ -71,8 +74,13 @@ export const installCommand: CommandModule = {
describe: 'The git ref to install from.',
type: 'string',
})
.option('auto-update', {
describe: 'Enable auto-update for this extension.',
type: 'boolean',
})
.conflicts('source', 'path')
.conflicts('path', 'ref')
.conflicts('path', 'auto-update')
.check((argv) => {
if (!argv.source && !argv.path) {
throw new Error('Either source or --path must be provided.');
Expand All @@ -84,6 +92,7 @@ export const installCommand: CommandModule = {
source: argv['source'] as string | undefined,
path: argv['path'] as string | undefined,
ref: argv['ref'] as string | undefined,
autoUpdate: argv['auto-update'] as boolean | undefined,
});
},
};
45 changes: 35 additions & 10 deletions packages/cli/src/commands/extensions/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@

import type { CommandModule } from 'yargs';
import {
updateExtensionByName,
updateAllUpdatableExtensions,
type ExtensionUpdateInfo,
loadExtensions,
annotateActiveExtensions,
checkForAllExtensionUpdates,
} from '../../config/extension.js';
import {
updateAllUpdatableExtensions,
type ExtensionUpdateInfo,
checkForAllExtensionUpdates,
updateExtension,
} from '../../config/extensions/update.js';
import { checkForExtensionUpdate } from '../../config/extensions/github.js';
import { getErrorMessage } from '../../utils/errors.js';
import { ExtensionUpdateState } from '../../ui/state/extensions.js';

interface UpdateArgs {
name?: string;
Expand All @@ -37,7 +41,7 @@ export async function handleUpdate(args: UpdateArgs) {
let updateInfos = await updateAllUpdatableExtensions(
workingDir,
extensions,
await checkForAllExtensionUpdates(extensions, (_) => {}),
await checkForAllExtensionUpdates(extensions, new Map(), (_) => {}),
() => {},
);
updateInfos = updateInfos.filter(
Expand All @@ -54,13 +58,34 @@ export async function handleUpdate(args: UpdateArgs) {
}
if (args.name)
try {
const extension = extensions.find(
(extension) => extension.name === args.name,
);
if (!extension) {
console.log(`Extension "${args.name}" not found.`);
return;
}
let updateState: ExtensionUpdateState | undefined;
if (!extension.installMetadata) {
console.log(
`Unable to install extension "${args.name}" due to missing install metadata`,
);
return;
}
await checkForExtensionUpdate(extension, (newState) => {
updateState = newState;
});
if (updateState !== ExtensionUpdateState.UPDATE_AVAILABLE) {
console.log(`Extension "${args.name}" is already up to date.`);
return;
}
// TODO(chrstnb): we should list extensions if the requested extension is not installed.
const updatedExtensionInfo = await updateExtensionByName(
args.name,
const updatedExtensionInfo = (await updateExtension(
extension,
workingDir,
extensions,
updateState,
() => {},
);
))!;
if (
updatedExtensionInfo.originalVersion !==
updatedExtensionInfo.updatedVersion
Expand All @@ -69,7 +94,7 @@ export async function handleUpdate(args: UpdateArgs) {
`Extension "${args.name}" successfully updated: ${updatedExtensionInfo.originalVersion} β†’ ${updatedExtensionInfo.updatedVersion}.`,
);
} else {
console.log(`Extension "${args.name}" already up to date.`);
console.log(`Extension "${args.name}" is already up to date.`);
}
} catch (error) {
console.error(getErrorMessage(error));
Expand Down
Loading
Loading