Skip to content

Commit 707c77b

Browse files
jakemac53chrstnb
authored andcommitted
Add support for auto-updating git extensions (#8511)
1 parent c7f3186 commit 707c77b

File tree

20 files changed

+1311
-526
lines changed

20 files changed

+1311
-526
lines changed

packages/cli/src/commands/extensions/install.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { describe, it, expect, type MockInstance } from 'vitest';
7+
import { describe, it, expect, vi, type MockInstance } from 'vitest';
88
import { handleInstall, installCommand } from './install.js';
99
import yargs from 'yargs';
1010

@@ -32,6 +32,15 @@ describe('extensions install command', () => {
3232
validationParser.parse('install some-url --path /some/path'),
3333
).toThrow('Arguments source and path are mutually exclusive');
3434
});
35+
36+
it('should fail if both auto update and local path are provided', () => {
37+
const validationParser = yargs([]).command(installCommand).fail(false);
38+
expect(() =>
39+
validationParser.parse(
40+
'install some-url --path /some/path --auto-update',
41+
),
42+
).toThrow('Arguments path and auto-update are mutually exclusive');
43+
});
3544
});
3645

3746
describe('handleInstall', () => {

packages/cli/src/commands/extensions/install.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface InstallArgs {
1414
source?: string;
1515
path?: string;
1616
ref?: string;
17+
autoUpdate?: boolean;
1718
}
1819

1920
export async function handleInstall(args: InstallArgs) {
@@ -32,6 +33,7 @@ export async function handleInstall(args: InstallArgs) {
3233
source,
3334
type: 'git',
3435
ref: args.ref,
36+
autoUpdate: args.autoUpdate,
3537
};
3638
} else {
3739
throw new Error(`The source "${source}" is not a valid URL format.`);
@@ -40,6 +42,7 @@ export async function handleInstall(args: InstallArgs) {
4042
installMetadata = {
4143
source: args.path,
4244
type: 'local',
45+
autoUpdate: args.autoUpdate,
4346
};
4447
} else {
4548
// This should not be reached due to the yargs check.
@@ -71,8 +74,13 @@ export const installCommand: CommandModule = {
7174
describe: 'The git ref to install from.',
7275
type: 'string',
7376
})
77+
.option('auto-update', {
78+
describe: 'Enable auto-update for this extension.',
79+
type: 'boolean',
80+
})
7481
.conflicts('source', 'path')
7582
.conflicts('path', 'ref')
83+
.conflicts('path', 'auto-update')
7684
.check((argv) => {
7785
if (!argv.source && !argv.path) {
7886
throw new Error('Either source or --path must be provided.');
@@ -84,6 +92,7 @@ export const installCommand: CommandModule = {
8492
source: argv['source'] as string | undefined,
8593
path: argv['path'] as string | undefined,
8694
ref: argv['ref'] as string | undefined,
95+
autoUpdate: argv['auto-update'] as boolean | undefined,
8796
});
8897
},
8998
};

packages/cli/src/commands/extensions/update.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@
66

77
import type { CommandModule } from 'yargs';
88
import {
9-
updateExtensionByName,
10-
updateAllUpdatableExtensions,
11-
type ExtensionUpdateInfo,
129
loadExtensions,
1310
annotateActiveExtensions,
14-
checkForAllExtensionUpdates,
1511
} from '../../config/extension.js';
12+
import {
13+
updateAllUpdatableExtensions,
14+
type ExtensionUpdateInfo,
15+
checkForAllExtensionUpdates,
16+
updateExtension,
17+
} from '../../config/extensions/update.js';
18+
import { checkForExtensionUpdate } from '../../config/extensions/github.js';
1619
import { getErrorMessage } from '../../utils/errors.js';
20+
import { ExtensionUpdateState } from '../../ui/state/extensions.js';
1721

1822
interface UpdateArgs {
1923
name?: string;
@@ -37,7 +41,7 @@ export async function handleUpdate(args: UpdateArgs) {
3741
let updateInfos = await updateAllUpdatableExtensions(
3842
workingDir,
3943
extensions,
40-
await checkForAllExtensionUpdates(extensions, (_) => {}),
44+
await checkForAllExtensionUpdates(extensions, new Map(), (_) => {}),
4145
() => {},
4246
);
4347
updateInfos = updateInfos.filter(
@@ -54,13 +58,34 @@ export async function handleUpdate(args: UpdateArgs) {
5458
}
5559
if (args.name)
5660
try {
61+
const extension = extensions.find(
62+
(extension) => extension.name === args.name,
63+
);
64+
if (!extension) {
65+
console.log(`Extension "${args.name}" not found.`);
66+
return;
67+
}
68+
let updateState: ExtensionUpdateState | undefined;
69+
if (!extension.installMetadata) {
70+
console.log(
71+
`Unable to install extension "${args.name}" due to missing install metadata`,
72+
);
73+
return;
74+
}
75+
await checkForExtensionUpdate(extension, (newState) => {
76+
updateState = newState;
77+
});
78+
if (updateState !== ExtensionUpdateState.UPDATE_AVAILABLE) {
79+
console.log(`Extension "${args.name}" is already up to date.`);
80+
return;
81+
}
5782
// TODO(chrstnb): we should list extensions if the requested extension is not installed.
58-
const updatedExtensionInfo = await updateExtensionByName(
59-
args.name,
83+
const updatedExtensionInfo = (await updateExtension(
84+
extension,
6085
workingDir,
61-
extensions,
86+
updateState,
6287
() => {},
63-
);
88+
))!;
6489
if (
6590
updatedExtensionInfo.originalVersion !==
6691
updatedExtensionInfo.updatedVersion
@@ -69,7 +94,7 @@ export async function handleUpdate(args: UpdateArgs) {
6994
`Extension "${args.name}" successfully updated: ${updatedExtensionInfo.originalVersion}${updatedExtensionInfo.updatedVersion}.`,
7095
);
7196
} else {
72-
console.log(`Extension "${args.name}" already up to date.`);
97+
console.log(`Extension "${args.name}" is already up to date.`);
7398
}
7499
} catch (error) {
75100
console.error(getErrorMessage(error));

0 commit comments

Comments
 (0)