Skip to content

Commit a16662a

Browse files
committed
Finish update command
1 parent 85c8d85 commit a16662a

File tree

6 files changed

+86
-71
lines changed

6 files changed

+86
-71
lines changed
Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,64 @@
1-
import semverGt from "semver/functions/gt"
21
import vscode from "vscode"
3-
import { fetchPackage, parsePackage } from "./utils/packages"
2+
import { fetchPackage } from "./utils/packages"
3+
import { parseDependency } from "./utils/parseDependency"
4+
import { DIAGNOSTIC_CODE } from "./utils/vars"
45

56
export class PackageJsonCodeActionProvider
67
implements vscode.CodeActionProvider {
7-
public async provideCodeActions(
8-
document: vscode.TextDocument,
9-
range: vscode.Range | vscode.Selection,
10-
context: vscode.CodeActionContext
11-
): Promise<(vscode.Command | vscode.CodeAction)[]> {
12-
const line = document.lineAt(range.start.line)
13-
const localInfo = parsePackage(line.text)
14-
const registryInfo = localInfo.name
15-
? await fetchPackage(localInfo.name)
16-
: undefined
17-
18-
if (localInfo?.version && registryInfo?.version) {
19-
const outdated = semverGt(registryInfo.version, localInfo.version)
20-
console.log(localInfo.name, outdated)
21-
// return vscode.commands.executeCommand("workbench.action")
8+
provideCodeActions(
9+
doc: vscode.TextDocument,
10+
range: vscode.Range,
11+
ctx: vscode.CodeActionContext
12+
): Promise<vscode.CodeAction[]> {
13+
// TODO: Add a single command to update all the packages
14+
if (!range.isSingleLine) {
15+
return Promise.resolve([])
2216
}
2317

24-
// const actions = context.diagnostics.map((error) => {
25-
// return {
26-
// diagnostics: [error],
27-
// edit: {
28-
// edits: [
29-
// {
30-
// edits: [
31-
// {
32-
// range: error,
33-
// text: "This text replaces the text with the error",
34-
// },
35-
// ],
36-
// resource: document.uri,
37-
// },
38-
// ],
39-
// },
40-
// isPreferred: true,
41-
// kind: "quickfix",
42-
// title: `Example quick fix`,
43-
// }
44-
// })
45-
46-
// return {
47-
// actions,
48-
// dispose() {},
49-
// }
18+
// For each diagnostic entry that has the matching `code`,
19+
// create a code action command.
20+
const promises = ctx.diagnostics
21+
.filter((diagnostic) => diagnostic.code === DIAGNOSTIC_CODE)
22+
.map((diagnostic) => this.createCommandCodeAction(doc, diagnostic))
23+
24+
return Promise.all(promises)
25+
}
26+
27+
private async createCommandCodeAction(
28+
doc: vscode.TextDocument,
29+
diagnostic: vscode.Diagnostic
30+
) {
31+
const action = new vscode.CodeAction(
32+
"Update package to latest version",
33+
vscode.CodeActionKind.QuickFix
34+
)
35+
36+
action.edit = await this.createEdit(doc, diagnostic.range)
37+
action.diagnostics = [diagnostic]
38+
action.isPreferred = true
39+
40+
return action
41+
}
42+
43+
private async createEdit(doc: vscode.TextDocument, range: vscode.Range) {
44+
const edit = new vscode.WorkspaceEdit()
45+
46+
// Get the latest version from the registry
47+
const line = doc.lineAt(range.start.line)
48+
const { name, version } = parseDependency(line.text)
49+
50+
// This check shouldn't be necessary, but the types are overly strong
51+
// so this is an extra safety check
52+
if (name && version) {
53+
// Keep the existing version prefix if it exists.
54+
const prefix = ["~", "^"].includes(version[0]) ? version[0] : ""
55+
const info = await fetchPackage(name)
56+
57+
if (info) {
58+
edit.replace(doc.uri, range, prefix + info.version)
59+
}
60+
}
61+
62+
return edit
5063
}
5164
}

src/diagnostics/getPackageRanges.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import vscode from "vscode"
2+
import { parseDependency } from "../utils/parseDependency"
23

34
const isDependencyBlock = (line: vscode.TextLine) =>
45
line.text.includes('"dependencies"') ||
56
line.text.includes('"devDependencies"')
67

7-
const getPackageName = (text: string) => {
8-
return text.match(/"(.+)":/)?.[1]
9-
}
10-
118
export function getPackageRanges(doc: vscode.TextDocument) {
129
let inDependencyBlock = false
1310
const ranges: Record<string, vscode.Range> = {}
@@ -20,16 +17,16 @@ export function getPackageRanges(doc: vscode.TextDocument) {
2017
}
2118

2219
if (inDependencyBlock) {
23-
const packageName = getPackageName(line.text)
20+
const { name, version } = parseDependency(line.text)
2421

25-
if (packageName) {
26-
const startIndex = line.text.indexOf(packageName)
22+
if (name && version) {
23+
const startIndex = line.text.indexOf(version)
2724

28-
ranges[packageName] = new vscode.Range(
25+
ranges[name] = new vscode.Range(
2926
line.lineNumber,
3027
startIndex,
3128
line.lineNumber,
32-
startIndex + packageName.length
29+
startIndex + version.length
3330
)
3431
}
3532
}

src/extension.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { PackageJsonCodeActionProvider } from "./PackageJsonCodeActionProvider"
33
import { findOutdatedPackages } from "./diagnostics/findOutdatedPackages"
44
import { getPackageRanges } from "./diagnostics/getPackageRanges"
55
import { subscribeToDocument } from "./diagnostics/subscribeToDocument"
6+
import { updatePackage } from "./packages/updatePackage"
7+
import { DIAGNOSTIC_CODE } from "./utils/vars"
68

79
let diagnosticCollection: vscode.DiagnosticCollection
810

@@ -15,11 +17,13 @@ export function activate(ctx: vscode.ExtensionContext): void {
1517
const ranges = getPackageRanges(doc)
1618

1719
const diagnostics = results.map((result) => {
18-
return new vscode.Diagnostic(
20+
const diagnostic = new vscode.Diagnostic(
1921
ranges[result.name],
2022
`Newer version of ${result.name} is available (${result.latestVersion}).`,
2123
vscode.DiagnosticSeverity.Information
2224
)
25+
diagnostic.code = DIAGNOSTIC_CODE
26+
return diagnostic
2327
})
2428

2529
// Clear any old diagnostics before creating the new diagnostics
@@ -34,7 +38,16 @@ export function activate(ctx: vscode.ExtensionContext): void {
3438
pattern: "**/package.json",
3539
scheme: "file",
3640
},
37-
new PackageJsonCodeActionProvider()
41+
new PackageJsonCodeActionProvider(),
42+
{
43+
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix],
44+
}
3845
)
3946
)
47+
48+
ctx.subscriptions.push(
49+
vscode.commands.registerCommand(COMMANDS.update, (name: string) => {
50+
updatePackage(name)
51+
})
52+
)
4053
}

src/utils/packages.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,9 @@
11
import cp from "child_process"
2-
import semverCoerce from "semver/functions/coerce"
32
import { PackageCache } from "../PackageCache"
43
import { PackageInfo } from "../models"
54

6-
const regex = /"(.+)":\s*"(.+)"/
75
const cache = new PackageCache()
86

9-
export function parsePackage(text: string) {
10-
const matches = text.match(regex)
11-
12-
if (matches) {
13-
const [_, name, version] = matches
14-
15-
return {
16-
name,
17-
version: semverCoerce(version)?.version,
18-
}
19-
}
20-
21-
return {}
22-
}
23-
247
function npmView(name: string): Promise<PackageInfo | undefined> {
258
const command = `npm view --json ${name} dist-tags.latest version`
269

src/utils/parseDependency.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function parseDependency(text: string) {
2+
const matches = text.match(/"(.+)":\s*"(.+)"/)
3+
4+
return {
5+
name: matches?.[1],
6+
version: matches?.[2],
7+
}
8+
}

src/utils/vars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const DIAGNOSTIC_CODE = "npm-outdated"

0 commit comments

Comments
 (0)