diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 5ff8656..7939aa8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,3 +1,5 @@ +const os = require('os'); + module.exports = { root: true, parser: '@typescript-eslint/parser', @@ -21,6 +23,7 @@ module.exports = { 'no-sync': 'error', 'prefer-promise-reject-errors': 'off', 'n/prefer-promises/fs': 'error', + 'prettier/prettier': os.platform() === 'win32' ? 'off' : 'error', '@typescript-eslint/no-floating-promises': [ 'error', { diff --git a/package.json b/package.json index 99984fe..da3c0d0 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,10 @@ "title": "phpfmt: Format This File", "when": "!inOutput && editorFocus && editorLangId == php" }, + { + "command": "phpfmt.upgradeFmt", + "title": "phpfmt: Upgrade fmt.phar or fmt.stub.php" + }, { "command": "phpfmt.listTransformations", "title": "phpfmt: List Transformations" diff --git a/src/PHPFmtProvider.ts b/src/PHPFmtProvider.ts index cbaaac8..5b3315f 100644 --- a/src/PHPFmtProvider.ts +++ b/src/PHPFmtProvider.ts @@ -12,11 +12,14 @@ import { type QuickPickItem, type WorkspaceConfiguration } from 'vscode'; +import path from 'path'; +import fs from 'fs'; import pkg from 'pjson'; import type { PHPFmt } from './PHPFmt'; import type { Widget } from './Widget'; import type { Transformation } from './Transformation'; import { PHPFmtIgnoreError } from './PHPFmtError'; +import { downloadFile } from './utils'; export class PHPFmtProvider { private readonly documentSelector: DocumentSelector; @@ -53,6 +56,43 @@ export class PHPFmtProvider { }); } + public registerUpgradeFmtCommand(): Disposable { + return Commands.registerCommand('phpfmt.upgradeFmt', async () => { + try { + const destFile = this.phpfmt.getFmt().pharPath; + const bakFile = `${this.phpfmt.getFmt().pharPath}.bak`; + + const baseDir = path.resolve(destFile, '..'); + const installFile = path.join(baseDir, 'install.js'); + const installContent = String(await fs.promises.readFile(installFile)); + const regex = /(var|const|let)\s+url\s+=\s+'(https?:\/\/[^']+)'/s; + const match = installContent.match(regex); + if (match?.[2]) { + const url = match[2]; + + await fs.promises.copyFile(destFile, bakFile); + + try { + await downloadFile(url, destFile); + } catch (err) { + this.widget.logError('Download failed', err); + await fs.promises.copyFile(bakFile, destFile); + } + + await Window.showInformationMessage( + 'fmt.phar or fmt.stub.php upgraded successfully!' + ); + } else { + throw new Error('Failed to get url in modules'); + } + } catch (err) { + await Window.showErrorMessage( + 'fmt.phar or fmt.stub.php upgraded failed!' + ); + } + }); + } + private async getTransformationItems(): Promise { const transformationItems = await this.transformation.getTransformations(); const items = transformationItems.map(o => ({ diff --git a/src/extension.ts b/src/extension.ts index 5994303..5425b62 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -12,6 +12,7 @@ export function activate(context: ExtensionContext): void { context.subscriptions.push( provider.registerOnDidChangeConfiguration(), provider.registerFormatCommand(), + provider.registerUpgradeFmtCommand(), provider.registerListTransformationsCommand(), ...provider.registerToggleTransformationsCommand(), ...provider.registerToggleBooleanCommand(), diff --git a/src/utils.ts b/src/utils.ts index 8222f56..a861e35 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,47 @@ -import childProcess from 'child_process'; -import util from 'util'; +import childProcess, { type ExecOptions } from 'child_process'; +import fs, { type ObjectEncodingOptions } from 'fs'; +import https from 'https'; -export const exec = util.promisify(childProcess.exec); +export const exec = async ( + command: string, + options?: (ObjectEncodingOptions & ExecOptions) | undefined | null +): Promise<{ stdout: string; stderr: string }> => { + return await new Promise((resolve, reject) => { + const child = childProcess.exec( + command, + options, + (error, stdout, stderr) => { + if (error != null) { + reject(error); + } else { + resolve({ + stdout: String(stdout), + stderr: String(stderr) + }); + } + } + ); + + child.on('error', reject); + }); +}; + +export const downloadFile = async ( + url: string, + filePath: string +): Promise => { + await new Promise((resolve, reject) => { + https + .get(url, res => { + const dest = fs.createWriteStream(filePath, { + autoClose: true + }); + res.pipe(dest).on('finish', () => { + resolve(); + }); + }) + .on('error', err => { + reject(err); + }); + }); +};