From 657c39b71cf6d97d7f23f77e69bccc98037bde63 Mon Sep 17 00:00:00 2001 From: Anner Visser Date: Sun, 15 Oct 2023 18:53:23 +0200 Subject: [PATCH] feat(help): Skip version checks if it will cause permission prompts When a user hasn't granted net permissions to the default upgrade provider, they would be prompted to allow it when running the `help`/`--help` command. This commit adds an extra method to `Provider` where a provider should check any permissions it needs to return available versions have been pre-granted. If the necessary permissions have NOT been granted, the help command will skip checking for newer versions. The upgrade command is not affected. BREAKING CHANGE: Custom `Provider` implementations will have to implement `hasRequiredPermissions()` --- command/upgrade/_check_version.ts | 9 ++++++++- command/upgrade/provider.ts | 2 ++ command/upgrade/provider/deno_land.ts | 12 +++++++++++- command/upgrade/provider/github.ts | 9 +++++++++ command/upgrade/provider/jsr.ts | 9 +++++++++ command/upgrade/provider/nest_land.ts | 12 +++++++++++- command/upgrade/provider/npm.ts | 9 +++++++++ command/upgrade/upgrade_command.ts | 4 ++++ 8 files changed, 63 insertions(+), 3 deletions(-) diff --git a/command/upgrade/_check_version.ts b/command/upgrade/_check_version.ts index 4d4934d0..8103f3b2 100644 --- a/command/upgrade/_check_version.ts +++ b/command/upgrade/_check_version.ts @@ -10,6 +10,11 @@ export async function checkVersion(cmd: Command): Promise { if (!isUpgradeCommand(upgradeCommand)) { return; } + + if (!await upgradeCommand.hasRequiredPermissions()) { + // If not all required permissions were pre-granted, skip the version check to prevent prompting user + return; + } const latestVersion = await upgradeCommand.getLatestVersion(); const currentVersion = mainCommand.getVersion(); @@ -23,9 +28,11 @@ export async function checkVersion(cmd: Command): Promise { } function isUpgradeCommand(command: unknown): command is UpgradeCommandImpl { - return command instanceof Command && "getLatestVersion" in command; + return command instanceof Command && "getLatestVersion" in command && + "hasRequiredPermissions" in command; } interface UpgradeCommandImpl { + hasRequiredPermissions(): Promise; getLatestVersion(): Promise; } diff --git a/command/upgrade/provider.ts b/command/upgrade/provider.ts index 13d9c107..19ad073a 100644 --- a/command/upgrade/provider.ts +++ b/command/upgrade/provider.ts @@ -61,6 +61,8 @@ export abstract class Provider { this.logger = logger; } + abstract hasRequiredPermissions(): Promise; + abstract getVersions(name: string): Promise; abstract getRepositoryUrl(name: string, version?: string): string; diff --git a/command/upgrade/provider/deno_land.ts b/command/upgrade/provider/deno_land.ts index e0251bee..a75bc9cf 100644 --- a/command/upgrade/provider/deno_land.ts +++ b/command/upgrade/provider/deno_land.ts @@ -8,6 +8,7 @@ export class DenoLandProvider extends Provider { name = "deno.land"; private readonly repositoryUrl = "https://deno.land/x/"; private readonly registryUrl = "https://deno.land/x/"; + private readonly apiUrl = "https://cdn.deno.land/"; private readonly moduleName?: string; constructor({ name, main, logger }: DenoLandProviderOptions = {}) { @@ -15,11 +16,20 @@ export class DenoLandProvider extends Provider { this.moduleName = name; } + async hasRequiredPermissions(): Promise { + const apiUrl = new URL(this.apiUrl); + const permissionStatus = await Deno.permissions.query({ + name: "net", + host: apiUrl.host, + }); + return permissionStatus.state === "granted"; + } + async getVersions( name: string, ): Promise { const response = await fetch( - `https://cdn.deno.land/${this.moduleName ?? name}/meta/versions.json`, + `${this.apiUrl}${this.moduleName ?? name}/meta/versions.json`, ); if (!response.ok) { throw new Error( diff --git a/command/upgrade/provider/github.ts b/command/upgrade/provider/github.ts index 39c8f158..9f635bb6 100644 --- a/command/upgrade/provider/github.ts +++ b/command/upgrade/provider/github.ts @@ -30,6 +30,15 @@ export class GithubProvider extends Provider { this.githubToken = token; } + async hasRequiredPermissions(): Promise { + const apiUrl = new URL(this.apiUrl); + const permissionStatus = await Deno.permissions.query({ + name: "net", + host: apiUrl.host, + }); + return permissionStatus.state === "granted"; + } + async getVersions( _name: string, ): Promise { diff --git a/command/upgrade/provider/jsr.ts b/command/upgrade/provider/jsr.ts index cbbbb276..0f6abc9f 100644 --- a/command/upgrade/provider/jsr.ts +++ b/command/upgrade/provider/jsr.ts @@ -40,6 +40,15 @@ export class JsrProvider extends Provider { : options.name; } + async hasRequiredPermissions(): Promise { + const apiUrl = new URL(this.repositoryUrl); + const permissionStatus = await Deno.permissions.query({ + name: "net", + host: apiUrl.host, + }); + return permissionStatus.state === "granted"; + } + async getVersions( name: string, ): Promise { diff --git a/command/upgrade/provider/nest_land.ts b/command/upgrade/provider/nest_land.ts index 2ee3531b..8861ae23 100644 --- a/command/upgrade/provider/nest_land.ts +++ b/command/upgrade/provider/nest_land.ts @@ -8,6 +8,7 @@ export class NestLandProvider extends Provider { name = "nest.land"; private readonly repositoryUrl = "https://nest.land/package/"; private readonly registryUrl = "https://x.nest.land/"; + private readonly apiUrl = "https://nest.land/api/"; private readonly moduleName?: string; constructor({ name, main, logger }: NestLandProviderOptions = {}) { @@ -15,10 +16,19 @@ export class NestLandProvider extends Provider { this.moduleName = name; } + async hasRequiredPermissions(): Promise { + const apiUrl = new URL(this.apiUrl); + const permissionStatus = await Deno.permissions.query({ + name: "net", + host: apiUrl.host, + }); + return permissionStatus.state === "granted"; + } + async getVersions( name: string, ): Promise { - const response = await fetch(`https://nest.land/api/package-client`, { + const response = await fetch(`${this.apiUrl}package-client`, { method: "post", body: JSON.stringify({ data: { name: this.moduleName ?? name } }), headers: { "Content-Type": "application/json" }, diff --git a/command/upgrade/provider/npm.ts b/command/upgrade/provider/npm.ts index c9f66c8f..e320507e 100644 --- a/command/upgrade/provider/npm.ts +++ b/command/upgrade/provider/npm.ts @@ -31,6 +31,15 @@ export class NpmProvider extends Provider { } } + async hasRequiredPermissions(): Promise { + const apiUrl = new URL(this.apiUrl); + const permissionStatus = await Deno.permissions.query({ + name: "net", + host: apiUrl.host, + }); + return permissionStatus.state === "granted"; + } + async getVersions( name: string, ): Promise { diff --git a/command/upgrade/upgrade_command.ts b/command/upgrade/upgrade_command.ts index 8e542463..efd56bd1 100644 --- a/command/upgrade/upgrade_command.ts +++ b/command/upgrade/upgrade_command.ts @@ -169,6 +169,10 @@ export class UpgradeCommand extends Command { return versions; } + public async hasRequiredPermissions(): Promise { + return await this.getProvider().hasRequiredPermissions(); + } + public async getLatestVersion(): Promise { const { latest } = await this.getVersions(); return latest;