Skip to content

Commit 7011ada

Browse files
authoredJun 16, 2024
feat(command)!: add support for node and bun to upgrade command (#705)
1 parent 318e2af commit 7011ada

24 files changed

+945
-183
lines changed
 

‎command/upgrade/get_runtime.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Runtime } from "./runtime.ts";
2+
3+
/** Names of supported runtimes. */
4+
export type RuntimeName = "deno" | "node" | "bun";
5+
6+
/** Result of getRuntime(). */
7+
export interface GetRuntimeResult {
8+
runtimeName: RuntimeName;
9+
runtime: Runtime;
10+
}
11+
12+
/** Get runtime handler for current runtime. */
13+
export async function getRuntime(): Promise<GetRuntimeResult> {
14+
// deno-lint-ignore no-explicit-any
15+
const { Deno, process } = globalThis as any;
16+
17+
if (Deno?.version?.deno) {
18+
const { DenoRuntime } = await import("./runtime/deno_runtime.ts");
19+
return { runtimeName: "deno", runtime: new DenoRuntime() };
20+
} else if (process?.versions?.bun) {
21+
const { BunRuntime } = await import("./runtime/bun_runtime.ts");
22+
return { runtimeName: "bun", runtime: new BunRuntime() };
23+
} else if (process?.versions?.node) {
24+
const { NodeRuntime } = await import("./runtime/node_runtime.ts");
25+
return { runtimeName: "node", runtime: new NodeRuntime() };
26+
} else {
27+
throw new Error("Unsupported runtime.");
28+
}
29+
}

‎command/upgrade/logger.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Spinner } from "./spinner.ts";
2+
3+
export interface Logger {
4+
log(...data: Array<unknown>): void;
5+
info(...data: Array<unknown>): void;
6+
warn(...data: Array<unknown>): void;
7+
error(...data: Array<unknown>): void;
8+
}
9+
10+
export interface LoggerOptions {
11+
spinner?: Spinner;
12+
verbose?: boolean;
13+
}
14+
15+
export function createLogger({ spinner, verbose }: LoggerOptions = {}): Logger {
16+
function write(
17+
type: "log" | "info" | "warn" | "error",
18+
...args: Array<unknown>
19+
): void {
20+
spinner?.stop();
21+
console[type](...args);
22+
spinner?.start();
23+
}
24+
25+
return {
26+
log: (...args: Array<unknown>): void => {
27+
verbose && write("log", ...args);
28+
},
29+
info: (...args: Array<unknown>): void => write("info", ...args),
30+
warn: (...args: Array<unknown>): void => write("warn", ...args),
31+
error: (...args: Array<unknown>): void => write("error", ...args),
32+
};
33+
}

‎command/upgrade/mod.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { Provider, type UpgradeOptions, type Versions } from "./provider.ts";
1+
export { Provider, type Versions } from "./provider.ts";
22
export {
33
UpgradeCommand,
44
type UpgradeCommandOptions,

‎command/upgrade/provider.ts

+62-63
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,78 @@
11
import { bold, brightBlue, cyan, green, red, yellow } from "@std/fmt/colors";
22
import { ValidationError } from "../_errors.ts";
33
import { Table } from "@cliffy/table";
4+
import type { Logger } from "./logger.ts";
45

56
export interface Versions {
67
latest: string;
78
versions: Array<string>;
89
}
910

10-
export interface UpgradeOptions {
11+
/** Shared provider options. */
12+
export interface ProviderOptions {
13+
main?: string;
14+
logger?: Logger;
15+
}
16+
17+
/** Provider upgrade options. */
18+
export interface ProviderUpgradeOptions {
1119
name: string;
12-
from?: string;
1320
to: string;
14-
args?: Array<string>;
1521
main?: string;
16-
importMap?: string;
22+
args?: Array<string>;
23+
from?: string;
24+
force?: boolean;
25+
verbose?: boolean;
1726
}
1827

28+
/**
29+
* Upgrade provider.
30+
*
31+
* The upgrade provider is an api wrapper for a javascript registry which is
32+
* used by the upgrade command to upgrade the cli to a specific version.
33+
*
34+
* @example Upgrade provider example.
35+
*
36+
* ```
37+
* import { Command } from "@cliffy/command";
38+
* import { UpgradeCommand } from "@cliffy/command/upgrade";
39+
* import { DenoLandProvider } from "@cliffy/command/upgrade/provider/deno-land";
40+
* import { GithubProvider } from "@cliffy/command/upgrade/provider/github";
41+
* import { JsrProvider } from "@cliffy/command/upgrade/provider/jsr";
42+
* import { NestLandProvider } from "@cliffy/command/upgrade/provider/nest-land";
43+
* import { NpmProvider } from "@cliffy/command/upgrade/provider/npm";
44+
*
45+
* const upgradeCommand = new UpgradeCommand({
46+
* provider: [
47+
* new JsrProvider({ package: "@examples/package" }),
48+
* ],
49+
* });
50+
* ```
51+
*/
1952
export abstract class Provider {
2053
abstract readonly name: string;
54+
protected readonly main?: string;
2155
protected readonly maxListSize: number = 25;
56+
protected logger: Logger;
2257
private maxCols = 8;
2358

59+
protected constructor({ main, logger = console }: ProviderOptions = {}) {
60+
this.main = main;
61+
this.logger = logger;
62+
}
63+
2464
abstract getVersions(name: string): Promise<Versions>;
2565

26-
abstract getRepositoryUrl(name: string): string;
66+
abstract getRepositoryUrl(name: string, version?: string): string;
2767

2868
abstract getRegistryUrl(name: string, version: string): string;
2969

70+
upgrade?(options: ProviderUpgradeOptions): Promise<void>;
71+
72+
getSpecifier(name: string, version: string, defaultMain?: string): string {
73+
return `${this.getRegistryUrl(name, version)}${this.getMain(defaultMain)}`;
74+
}
75+
3076
async isOutdated(
3177
name: string,
3278
currentVersion: string,
@@ -57,7 +103,7 @@ export abstract class Provider {
57103

58104
// Check if requested version is already the latest available version.
59105
if (latest && latest === currentVersion && latest === targetVersion) {
60-
console.warn(
106+
this.logger.warn(
61107
yellow(
62108
`You're already using the latest available version ${currentVersion} of ${name}.`,
63109
),
@@ -67,7 +113,7 @@ export abstract class Provider {
67113

68114
// Check if requested version is already installed.
69115
if (targetVersion && currentVersion === targetVersion) {
70-
console.warn(
116+
this.logger.warn(
71117
yellow(`You're already using version ${currentVersion} of ${name}.`),
72118
);
73119
return false;
@@ -76,62 +122,6 @@ export abstract class Provider {
76122
return true;
77123
}
78124

79-
async upgrade(
80-
{ name, from, to, importMap, main, args = [] }: UpgradeOptions,
81-
): Promise<void> {
82-
if (to === "latest") {
83-
const { latest } = await this.getVersions(name);
84-
to = latest;
85-
}
86-
87-
const registryUrl = this.getRegistryUrl(name, to);
88-
const registry: string =
89-
registryUrl.startsWith("jsr:") || registryUrl.startsWith("npm:")
90-
? registryUrl
91-
: new URL(main || `${name}.ts`, registryUrl + "/").href;
92-
93-
const cmdArgs = ["install"];
94-
95-
if (importMap) {
96-
const importJson: string = new URL(importMap, registryUrl + "/").href;
97-
98-
cmdArgs.push("--import-map", importJson);
99-
}
100-
101-
if (args.length) {
102-
cmdArgs.push(...args, "--force", "--name", name, registry);
103-
} else {
104-
cmdArgs.push(
105-
"--no-check",
106-
"--quiet",
107-
"--force",
108-
"--name",
109-
name,
110-
registry,
111-
);
112-
}
113-
114-
const cmd = new Deno.Command(Deno.execPath(), {
115-
args: cmdArgs,
116-
stdout: "piped",
117-
stderr: "piped",
118-
});
119-
const { success, stderr } = await cmd.output();
120-
121-
if (!success) {
122-
await Deno.stderr.write(stderr);
123-
throw new Error(
124-
`Failed to upgrade ${name} from ${from} to version ${to}!`,
125-
);
126-
}
127-
128-
console.info(
129-
`Successfully upgraded ${name} from ${from} to version ${to}! (${
130-
this.getRegistryUrl(name, to)
131-
})`,
132-
);
133-
}
134-
135125
public async listVersions(
136126
name: string,
137127
currentVersion?: string,
@@ -177,4 +167,13 @@ export abstract class Provider {
177167
}
178168
}
179169
}
170+
171+
setLogger(logger: Logger): void {
172+
this.logger = logger;
173+
}
174+
175+
private getMain(defaultMain?: string): string {
176+
const main = this.main ?? defaultMain;
177+
return main ? `/${main}` : "";
178+
}
180179
}

‎command/upgrade/provider/deno_land.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Provider, Versions } from "../provider.ts";
1+
import { Provider, type ProviderOptions, type Versions } from "../provider.ts";
22

3-
export interface DenoLandProviderOptions {
3+
export interface DenoLandProviderOptions extends ProviderOptions {
44
name?: string;
55
}
66

@@ -10,8 +10,8 @@ export class DenoLandProvider extends Provider {
1010
private readonly registryUrl = "https://deno.land/x/";
1111
private readonly moduleName?: string;
1212

13-
constructor({ name }: DenoLandProviderOptions = {}) {
14-
super();
13+
constructor({ name, main, logger }: DenoLandProviderOptions = {}) {
14+
super({ main, logger });
1515
this.moduleName = name;
1616
}
1717

@@ -30,8 +30,11 @@ export class DenoLandProvider extends Provider {
3030
return await response.json();
3131
}
3232

33-
getRepositoryUrl(name: string): string {
34-
return new URL(`${this.moduleName ?? name}`, this.repositoryUrl).href;
33+
getRepositoryUrl(name: string, version?: string): string {
34+
return new URL(
35+
`${this.moduleName ?? name}${version ? `@${version}` : ""}`,
36+
this.repositoryUrl,
37+
).href;
3538
}
3639

3740
getRegistryUrl(name: string, version: string): string {

‎command/upgrade/provider/deno_land_test.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ import {
1313
} from "@c4spar/mock-command";
1414
import { DenoLandProvider } from "./deno_land.ts";
1515
import type { Versions } from "../provider.ts";
16+
import { upgrade } from "../upgrade.ts";
1617

1718
Deno.test("DenoLandProvider", async (ctx) => {
1819
mockGlobalFetch();
1920
mockGlobalCommand();
2021

21-
const provider = new DenoLandProvider();
22+
const provider = new DenoLandProvider({
23+
main: "foo.ts",
24+
});
2225

2326
await ctx.step({
2427
name: "should return registry url",
@@ -88,32 +91,40 @@ Deno.test("DenoLandProvider", async (ctx) => {
8891
await ctx.step({
8992
name: "should upgrade to latest version",
9093
async fn() {
91-
mockFetch("https://cdn.deno.land/foo/meta/versions.json", {
94+
const versionsResponse = {
9295
body: JSON.stringify({
9396
latest: "1.0.1",
9497
versions: ["1.0.1", "1.0.0"],
9598
}),
96-
});
99+
};
100+
mockFetch(
101+
"https://cdn.deno.land/foo/meta/versions.json",
102+
versionsResponse,
103+
);
104+
mockFetch(
105+
"https://cdn.deno.land/foo/meta/versions.json",
106+
versionsResponse,
107+
);
97108

98109
mockCommand({
99110
command: Deno.execPath(),
100111
args: [
101112
"install",
102-
"--no-check",
103-
"--quiet",
113+
"--name=foo",
114+
"--global",
104115
"--force",
105-
"--name",
106-
"foo",
116+
"--quiet",
107117
"https://deno.land/x/foo@1.0.1/foo.ts",
108118
],
109119
stdout: "piped",
110120
stderr: "piped",
111121
});
112122

113-
await provider.upgrade({
123+
await upgrade({
114124
name: "foo",
115-
from: "1.0.1",
125+
from: "1.0.0",
116126
to: "latest",
127+
provider,
117128
});
118129

119130
resetFetch();

‎command/upgrade/provider/github.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Provider, Versions } from "../provider.ts";
1+
import { Provider, type ProviderOptions, type Versions } from "../provider.ts";
22
import { bold, brightBlue } from "@std/fmt/colors";
33

4-
export interface GithubProviderOptions {
4+
export interface GithubProviderOptions extends ProviderOptions {
55
repository: string;
66
branches?: boolean;
77
token?: string;
@@ -21,8 +21,10 @@ export class GithubProvider extends Provider {
2121
private readonly listBranches?: boolean;
2222
private readonly githubToken?: string;
2323

24-
constructor({ repository, branches = true, token }: GithubProviderOptions) {
25-
super();
24+
constructor(
25+
{ repository, branches = true, token, main, logger }: GithubProviderOptions,
26+
) {
27+
super({ main, logger });
2628
this.repositoryName = repository;
2729
this.listBranches = branches;
2830
this.githubToken = token;
@@ -60,8 +62,11 @@ export class GithubProvider extends Provider {
6062
};
6163
}
6264

63-
getRepositoryUrl(_name: string): string {
64-
return new URL(this.repositoryName, this.repositoryUrl).href;
65+
getRepositoryUrl(_name: string, version?: string): string {
66+
return new URL(
67+
`${this.repositoryName}${version ? `/releases/tag/${version}` : ""}`,
68+
this.repositoryUrl,
69+
).href;
6570
}
6671

6772
getRegistryUrl(_name: string, version: string): string {

0 commit comments

Comments
 (0)
Failed to load comments.