Skip to content

Commit e7ffbe4

Browse files
authored
feat(vscode): ensure dart_frog_cli is installed when used (#726)
1 parent a6ba9f4 commit e7ffbe4

File tree

8 files changed

+212
-11
lines changed

8 files changed

+212
-11
lines changed

extensions/vscode/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ Dart Frog can be installed from the [VS Code Marketplace](https://marketplace.vi
1111

1212
## Commands
1313

14-
| Command | Description |
15-
| ----------------------- | -------------------------- |
16-
| `Route: New Route` | Generates a new route |
17-
| `Route: New Middleware` | Generates a new middleware |
14+
| Command | Description |
15+
| --------------------------- | -------------------------- |
16+
| `Dart Frog: Install CLI` | Installs Dart Frog CLI |
17+
| `Dart Frog: New Route` | Generates a new route |
18+
| `Dart Frog: New Middleware` | Generates a new middleware |
1819

1920
You can activate the commands by launching the command palette (View -> Command Palette) and entering the command name or you can right click on the directory or file in which you'd like to create the route and select the command from the context menu.
2021

extensions/vscode/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
"main": "./out/extension.js",
3232
"contributes": {
3333
"commands": [
34+
{
35+
"command": "extension.install-cli",
36+
"title": "Dart Frog: Install CLI"
37+
},
3438
{
3539
"command": "extension.new-route",
3640
"title": "Dart Frog: New Route"
@@ -55,6 +59,9 @@
5559
]
5660
}
5761
},
62+
"extensionDependencies": [
63+
"Dart-Code.dart-code"
64+
],
5865
"scripts": {
5966
"vscode:prepublish": "npm run compile",
6067
"compile": "tsc -p ./",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from "./install-cli";
12
export * from "./new-route";
23
export * from "./new-middleware";
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const cp = require("child_process");
2+
3+
import { window, ProgressOptions } from "vscode";
4+
5+
/**
6+
* Installs Dart Frog CLI in the user's system if not already installed.
7+
*/
8+
export const installCLI = async (): Promise<void> => {
9+
if (!hasDartFrogCliInstalled()) {
10+
const options: ProgressOptions = {
11+
location: 15,
12+
title: "Installing Dart Frog CLI...",
13+
};
14+
window.withProgress(options, installDartFrogCliVersion);
15+
}
16+
};
17+
18+
/**
19+
* Whether the user has Dart Frog CLI installed in their system.
20+
*
21+
* @returns {boolean} True if the user has Dart Frog CLI installed in their
22+
* system, false otherwise.
23+
*/
24+
function hasDartFrogCliInstalled(): boolean {
25+
try {
26+
cp.execSync(`dart_frog --version`);
27+
return true;
28+
} catch (error) {
29+
return false;
30+
}
31+
}
32+
33+
/**
34+
* Installs Dart Frog CLI from pub.dev.
35+
*
36+
* @returns {Promise<void>} A promise that resolves when the installation is
37+
* complete.
38+
*/
39+
async function installDartFrogCliVersion(): Promise<void> {
40+
await cp.exec(
41+
`dart pub global activate dart_frog_cli`,
42+
function (error: Error, stdout: String, stderr: String) {
43+
if (error) {
44+
window.showErrorMessage(error.message);
45+
}
46+
}
47+
);
48+
}

extensions/vscode/src/extension.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as vscode from "vscode";
2-
import { newRoute, newMiddleware } from "./commands";
2+
import { installCLI, newRoute, newMiddleware } from "./commands";
33

44
/**
55
* This method is called when the extension is activated.
@@ -12,11 +12,12 @@ import { newRoute, newMiddleware } from "./commands";
1212
export function activate(
1313
context: vscode.ExtensionContext
1414
): vscode.ExtensionContext {
15+
installCLI();
16+
1517
context.subscriptions.push(
18+
vscode.commands.registerCommand("extension.install-cli", installCLI),
1619
vscode.commands.registerCommand("extension.new-route", newRoute),
1720
vscode.commands.registerCommand("extension.new-middleware", newMiddleware)
1821
);
1922
return context;
2023
}
21-
22-
export function deactivate() {}

extensions/vscode/src/test/runTest.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,37 @@
77
* @see https://github.com/prettier/prettier-vscode/blob/main/src/test/runTests.ts
88
*/
99

10+
import * as cp from "child_process";
1011
import * as path from "path";
11-
import { runTests } from "@vscode/test-electron";
12+
import {
13+
runTests,
14+
downloadAndUnzipVSCode,
15+
resolveCliArgsFromVSCodeExecutablePath,
16+
} from "@vscode/test-electron";
1217

1318
async function main() {
1419
try {
1520
const extensionDevelopmentPath = path.resolve(__dirname, "../../");
1621
const extensionTestsPath = path.resolve(__dirname, "./suite/index");
22+
const vscodeExecutablePath = await downloadAndUnzipVSCode();
23+
const [cliPath, ...args] =
24+
resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);
25+
26+
// Install Dart Code extension
27+
cp.spawnSync(
28+
cliPath,
29+
[...args, "--install-extension", "Dart-Code.dart-code"],
30+
{
31+
encoding: "utf-8",
32+
stdio: "inherit",
33+
}
34+
);
1735

1836
// Download VS Code, unzip it and run the integration test
1937
await runTests({
38+
vscodeExecutablePath,
2039
extensionDevelopmentPath,
2140
extensionTestsPath,
22-
launchArgs: ["--disable-extensions"],
2341
});
2442
} catch (err) {
2543
console.error("❌ Failed to run tests");
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const sinon = require("sinon");
2+
var proxyquire = require("proxyquire");
3+
4+
import { afterEach, beforeEach } from "mocha";
5+
import * as assert from "assert";
6+
7+
suite("install-cli command", () => {
8+
const updateCommand = `dart pub global activate dart_frog_cli`;
9+
10+
let vscodeStub: any;
11+
let childProcessStub: any;
12+
let command: any;
13+
14+
beforeEach(() => {
15+
vscodeStub = {
16+
window: {
17+
showErrorMessage: sinon.stub(),
18+
withProgress: sinon.stub(),
19+
},
20+
};
21+
childProcessStub = {
22+
exec: sinon.stub(),
23+
execSync: sinon.stub(),
24+
};
25+
26+
command = proxyquire("../../../commands/install-cli", {
27+
vscode: vscodeStub,
28+
// eslint-disable-next-line @typescript-eslint/naming-convention
29+
child_process: childProcessStub,
30+
});
31+
});
32+
33+
afterEach(() => {
34+
sinon.restore();
35+
});
36+
37+
test("does not install if Dart Frog CLI is already installed", async () => {
38+
childProcessStub.execSync.withArgs("dart_frog --version").returns("0.0.0");
39+
40+
await command.installCLI();
41+
42+
const wantedCalls = childProcessStub.exec
43+
.getCalls()
44+
.filter((call: any) => call.args[0] === updateCommand);
45+
assert.equal(wantedCalls.length, 0);
46+
});
47+
48+
suite("installs Dart Frog CLI", () => {
49+
beforeEach(() => {
50+
childProcessStub.execSync
51+
.withArgs("dart_frog --version")
52+
.throws("Command failed");
53+
});
54+
55+
test("when not already installed", async () => {
56+
await command.installCLI();
57+
58+
let progressFunction = vscodeStub.window.withProgress.getCall(0).args[1];
59+
await progressFunction();
60+
61+
sinon.assert.calledWith(childProcessStub.exec, updateCommand);
62+
});
63+
64+
test("shows progress", async () => {
65+
await command.installCLI();
66+
67+
const progressOptions = vscodeStub.window.withProgress.getCall(0).args[0];
68+
69+
assert.strictEqual(progressOptions.title, "Installing Dart Frog CLI...");
70+
assert.strictEqual(progressOptions.location, 15);
71+
});
72+
73+
test("shows error message on failure", async () => {
74+
const error = new Error("Command failed");
75+
childProcessStub.exec.withArgs(updateCommand).yields(error);
76+
77+
await command.installCLI();
78+
79+
const progressFunction =
80+
vscodeStub.window.withProgress.getCall(0).args[1];
81+
await progressFunction();
82+
83+
sinon.assert.calledWith(
84+
vscodeStub.window.showErrorMessage,
85+
error.message
86+
);
87+
});
88+
});
89+
});

extensions/vscode/src/test/suite/extension.test.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var proxyquire = require("proxyquire");
33

44
import * as assert from "assert";
55
import * as vscode from "vscode";
6-
import { newMiddleware, newRoute } from "../../commands";
6+
import { installCLI, newMiddleware, newRoute } from "../../commands";
77
import { afterEach, beforeEach } from "mocha";
88

99
suite("activate", () => {
@@ -26,14 +26,31 @@ suite("activate", () => {
2626
registerCommand: sinon.stub(),
2727
},
2828
};
29-
extension = proxyquire("../../extension", { vscode: vscodeStub });
29+
const childProcessStub = {
30+
execSync: sinon.stub(),
31+
};
32+
extension = proxyquire("../../extension", {
33+
vscode: vscodeStub,
34+
// eslint-disable-next-line @typescript-eslint/naming-convention
35+
child_process: childProcessStub,
36+
});
3037
context = { subscriptions: [] };
3138
});
3239

3340
afterEach(() => {
3441
sinon.restore();
3542
});
3643

44+
test("install-cli", async () => {
45+
extension.activate(context);
46+
47+
sinon.assert.calledWith(
48+
vscodeStub.commands.registerCommand,
49+
"extension.install-cli",
50+
installCLI
51+
);
52+
});
53+
3754
test("new-route", async () => {
3855
extension.activate(context);
3956

@@ -54,4 +71,23 @@ suite("activate", () => {
5471
);
5572
});
5673
});
74+
75+
test("calls installCLI", async () => {
76+
const vscodeStub = {
77+
commands: {
78+
registerCommand: sinon.stub(),
79+
},
80+
};
81+
const installCLIStub = sinon.stub();
82+
const extension = proxyquire("../../extension", {
83+
vscode: vscodeStub,
84+
// eslint-disable-next-line @typescript-eslint/naming-convention
85+
"./commands": { installCLI: installCLIStub },
86+
});
87+
const context = { subscriptions: [] };
88+
89+
extension.activate(context);
90+
91+
sinon.assert.calledOnce(installCLIStub);
92+
});
5793
});

0 commit comments

Comments
 (0)