第一次

This commit is contained in:
2025-07-20 07:00:47 +00:00
parent 6f1c19241b
commit 107620d04f
26 changed files with 3206 additions and 3203 deletions

View File

@@ -1,15 +1,15 @@
{ {
"plugins": [ "plugins": [
"@babel/plugin-proposal-class-properties" "@babel/plugin-proposal-class-properties"
], ],
"presets": [ "presets": [
[ [
"@babel/preset-env", "@babel/preset-env",
{ {
"targets": { "targets": {
"node": "12" "node": "12"
} }
} }
] ]
] ]
} }

View File

@@ -1,25 +1,25 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the // For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{ {
"name": "Node.js & TypeScript", "name": "Node.js & TypeScript",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm", "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bookworm",
"features": { "features": {
"ghcr.io/devcontainers/features/git:1": {} "ghcr.io/devcontainers/features/git:1": {}
}, },
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
// "features": {}, // "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [], // "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install" "postCreateCommand": "npm install"
// Configure tool-specific properties. // Configure tool-specific properties.
// "customizations": {}, // "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root" // "remoteUser": "root"
} }

View File

@@ -1,3 +1,3 @@
!.eslintrc.js !.eslintrc.js
/node_modules/** /node_modules/**
/lib/** /lib/**

View File

@@ -1,30 +1,33 @@
{ {
"root": true, "root": true,
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"ecmaVersion": 6, "ecmaVersion": 6,
"sourceType": "module" "sourceType": "module"
}, },
"plugins": [ "plugins": [
"@typescript-eslint" "@typescript-eslint"
], ],
"rules": { "rules": {
"@typescript-eslint/naming-convention": [ "@typescript-eslint/naming-convention": [
"warn", "warn",
{ {
"selector": "import", "selector": "import",
"format": [ "camelCase", "PascalCase" ] "format": [
} "camelCase",
], "PascalCase"
"@typescript-eslint/semi": "warn", ]
"curly": "warn", }
"eqeqeq": "warn", ],
"no-throw-literal": "warn", "@typescript-eslint/semi": "off", //关闭分号检查
"semi": "off" "curly": "warn",
}, "eqeqeq": "warn",
"ignorePatterns": [ "no-throw-literal": "warn",
"out", "semi": "off"
"dist", },
"**/*.d.ts" "ignorePatterns": [
] "out",
"dist",
"**/*.d.ts"
]
} }

16
.gitignore vendored
View File

@@ -1,9 +1,9 @@
/node_modules/ /node_modules/
/dist /dist
*.vsix *.vsix
yarn.lock yarn.lock
package-lock.json package-lock.json
*.dict *.dict
.vscode/settings.json .vscode/settings.json
*.DS_Store *.DS_Store
/tmp /tmp

52
.vscode/launch.json vendored
View File

@@ -1,26 +1,26 @@
// A launch configuration that launches the extension inside a new window // A launch configuration that launches the extension inside a new window
{ {
"version": "0.1.0", "version": "0.1.0",
"configurations": [ "configurations": [
{ {
"name": "Launch Extension", "name": "Launch Extension",
"type": "extensionHost", "type": "extensionHost",
"request": "launch", "request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceRoot}"], "args": ["--extensionDevelopmentPath=${workspaceRoot}"],
"sourceMaps": true, "sourceMaps": true,
"preLaunchTask": "npm: build", "preLaunchTask": "npm: build",
"outFiles": ["${workspaceFolder}/dist/**/*.js"], "outFiles": ["${workspaceFolder}/dist/**/*.js"],
}, },
{ {
"name": "Launch Tests", "name": "Launch Tests",
"type": "extensionHost", "type": "extensionHost",
"request": "launch", "request": "launch",
"runtimeExecutable": "${execPath}", "runtimeExecutable": "${execPath}",
"args": [ "args": [
"--extensionDevelopmentPath=${workspaceRoot}", "--extensionDevelopmentPath=${workspaceRoot}",
"--extensionTestsPath=${workspaceRoot}/test" "--extensionTestsPath=${workspaceRoot}/test"
], ],
"preLaunchTask": "npm: build", "preLaunchTask": "npm: build",
} }
] ]
} }

66
.vscode/tasks.json vendored
View File

@@ -1,34 +1,34 @@
{ {
"version": "2.0.0", "version": "2.0.0",
"tasks": [ "tasks": [
{ {
"type": "npm", "type": "npm",
"script": "build", "script": "build",
"group": "build", "group": "build",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: build", "label": "npm: build",
"detail": "webpack --mode production" "detail": "webpack --mode production"
}, },
{ {
"type": "npm", "type": "npm",
"script": "package", "script": "package",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: package", "label": "npm: package",
"detail": "webpack --mode production && vsce package" "detail": "webpack --mode production && vsce package"
}, },
{ {
"type": "npm", "type": "npm",
"script": "watch", "script": "watch",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: watch", "label": "npm: watch",
"detail": "webpack --mode production --watch" "detail": "webpack --mode production --watch"
}, },
{ {
"type": "npm", "type": "npm",
"script": "format", "script": "format",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: format", "label": "npm: format",
"detail": "eslint src --ext ts" "detail": "eslint src --ext ts"
}, },
] ]
} }

View File

@@ -1,8 +1,8 @@
.vscode/** .vscode/**
.vscode-test/** .vscode-test/**
src/** src/**
test/** test/**
.babelrc .babelrc
.eslintignore .eslintignore
.eslintrc.js .eslintrc.js
.gitignore .gitignore

1322
LICENSE

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,36 @@
# DevStar # DevStar
#### Description #### Description
Super IDE Client for VS Code Super IDE Client for VS Code
#### Software Architecture #### Software Architecture
Software architecture description Software architecture description
#### Installation #### Installation
1. xxxx 1. xxxx
2. xxxx 2. xxxx
3. xxxx 3. xxxx
#### Instructions #### Instructions
1. xxxx 1. xxxx
2. xxxx 2. xxxx
3. xxxx 3. xxxx
#### Contribution #### Contribution
1. Fork the repository 1. Fork the repository
2. Create Feat_xxx branch 2. Create Feat_xxx branch
3. Commit your code 3. Commit your code
4. Create Pull Request 4. Create Pull Request
#### Gitee Feature #### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md 1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com) 2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) 3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp) 4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) 5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) 6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

166
README.md
View File

@@ -1,84 +1,84 @@
# DevStar # DevStar
## User Quick Start ## User Quick Start
进入home页面home页面的功能都需要登录后才能使用。 进入home页面home页面的功能都需要登录后才能使用。
登录后,登录状态会长久保存,直到主动退出登录或者卸载插件。 登录后,登录状态会长久保存,直到主动退出登录或者卸载插件。
### 可供配置的字段 ### 可供配置的字段
- Devstar Domain注意点`https://devstar.cn``http://localhost:3000`的协议头`https``http`不能写错或写反,否则会出现页面无反应的异常。) - Devstar Domain注意点`https://devstar.cn``http://localhost:3000`的协议头`https``http`不能写错或写反,否则会出现页面无反应的异常。)
注意: 注意:
1配置修改后重启vscode才能生效 1配置修改后重启vscode才能生效
### 创建新仓库/创建新项目 ### 创建新仓库/创建新项目
目前可供选择的字段 目前可供选择的字段
- name* 必填 - name* 必填
- default_branch - default_branch
- description - description
- gitignores - gitignores
- issue_labels - issue_labels
- license - license
- object_format_name - object_format_name
- private - private
- readme - readme
- template - template
- trust_model - trust_model
### 打开项目 ### 打开项目
打开项目是指在vscode上打开远程容器中创建好的项目。选择项目名称右侧对应的Open project即可打开项目。 打开项目是指在vscode上打开远程容器中创建好的项目。选择项目名称右侧对应的Open project即可打开项目。
### 编译/调试 ### 编译/调试
容器环境提供了开发环境,安装好编译与调试所需要的工具链。 容器环境提供了开发环境,安装好编译与调试所需要的工具链。
## Developer Quick Start ## Developer Quick Start
### 准备开发环境 ### 准备开发环境
#### Windows/MacOS/Linux #### Windows/MacOS/Linux
需要预置开发环境Nodejs、Typescript开发环境 需要预置开发环境Nodejs、Typescript开发环境
1. git clone项目到本地然后通过VSCode打开项目。 1. git clone项目到本地然后通过VSCode打开项目。
2. 下载项目所需依赖,执行命令:`npm install` 2. 下载项目所需依赖,执行命令:`npm install`
#### DevContainer环境 #### DevContainer环境
1. git clone项目到本地然后通过VSCode打开项目 1. git clone项目到本地然后通过VSCode打开项目
2. 项目提供了devcontainer的开发配置通过vscode打开本项目后会提示“Folder contains a Dev Container configuration file. Reopen folder to develop in a container ([learn more](https://aka.ms/vscode-remote/docker)).”此时点击“Reopen in Container”自动进入vscode的devcontainer环境中。 2. 项目提供了devcontainer的开发配置通过vscode打开本项目后会提示“Folder contains a Dev Container configuration file. Reopen folder to develop in a container ([learn more](https://aka.ms/vscode-remote/docker)).”此时点击“Reopen in Container”自动进入vscode的devcontainer环境中。
3. 本项目的devcontainer预置了Node.js & Typescript的开发环境。在vscode的命令行工具执行`npm install`命令,下载项目所需依赖。 3. 本项目的devcontainer预置了Node.js & Typescript的开发环境。在vscode的命令行工具执行`npm install`命令,下载项目所需依赖。
### 编译插件 ### 编译插件
1. 项目目录`.vscode/launch.json`中提供了编译与启动插件的配置。 1. 项目目录`.vscode/launch.json`中提供了编译与启动插件的配置。
2.`F5`即可编译和测试插件。 2.`F5`即可编译和测试插件。
### 打包&发布插件 ### 打包&发布插件
1. 打包和发布插件均需要用到`@vscode/vsce`包,项目依赖中已包含。 1. 打包和发布插件均需要用到`@vscode/vsce`包,项目依赖中已包含。
2. 打包插件 2. 打包插件
1. 方法一通过Ctrl+Shift+P启动Command Palette选择Tasks: Run Task然后选择最下方的Show All Tasks...最后选择npm: vscode:package。 1. 方法一通过Ctrl+Shift+P启动Command Palette选择Tasks: Run Task然后选择最下方的Show All Tasks...最后选择npm: vscode:package。
2. 方法二打开package.json文件找到`scripts`字段,它上方有`Debug`按钮点击之后会让你选择要执行的task选择vscode:package。 2. 方法二打开package.json文件找到`scripts`字段,它上方有`Debug`按钮点击之后会让你选择要执行的task选择vscode:package。
3. 发布插件 3. 发布插件
1. 发布之前需要先完成打包。 1. 发布之前需要先完成打包。
2. 发布插件需要publisher的Personal Access Tokentoken项目负责人Token**有效时间**1年 2. 发布插件需要publisher的Personal Access Tokentoken项目负责人Token**有效时间**1年
3. 发布方法 3. 发布方法
1. 方法一步骤与打包插件的方法一基本一样除了最后一步选择npm: vscode:publish。接着会弹出要求填写Personal Access Token的prompt填上回车即可。 1. 方法一步骤与打包插件的方法一基本一样除了最后一步选择npm: vscode:publish。接着会弹出要求填写Personal Access Token的prompt填上回车即可。
2. 方法二步骤与打包插件的方法二基本一样除了最后一步选择npm: vscode:publish。接着会弹出要求填写Personal Access Token的prompt填上回车即可。 2. 方法二步骤与打包插件的方法二基本一样除了最后一步选择npm: vscode:publish。接着会弹出要求填写Personal Access Token的prompt填上回车即可。
### 特殊Git ### 特殊Git
由于先在主机上clone下来项目在devcontainer上再打开以后git认为所有的文件都更改了。需要在`.git/config`添加如下的配置: 由于先在主机上clone下来项目在devcontainer上再打开以后git认为所有的文件都更改了。需要在`.git/config`添加如下的配置:
```bash ```bash
[core] [core]
- filemode = false - filemode = false
- autocrlf = true - autocrlf = true
``` ```

View File

@@ -1,44 +1,44 @@
## 项目结构 ## 项目结构
`package.json` `package.json`
- 插件基本信息 - 插件基本信息
- 插件的命令、对外开放的接口 - 插件的命令、对外开放的接口
- 插件的activity bar、status bar - 插件的activity bar、status bar
- scripts - scripts
- 项目配置、依赖 - 项目配置、依赖
`tsconfig.json``webpack.config.js` `tsconfig.json``webpack.config.js`
- ts编译为js的配置 - ts编译为js的配置
assets目录 assets目录
- 静态资源 - 静态资源
源文件 源文件
- `main.ts`负责构建插件、构建插件过程的操作activate/deactivate插件 - `main.ts`负责构建插件、构建插件过程的操作activate/deactivate插件
- `home.ts`负责与home页面相关的操作如构建home页面webview、作为home页面与vscode交互的桥梁 - `home.ts`负责与home页面相关的操作如构建home页面webview、作为home页面与vscode交互的桥梁
- home页面 - embedded.html - home页面 - embedded.html
- views目录负责vscode中除home页面的其他界面设计 - views目录负责vscode中除home页面的其他界面设计
- `quick-access-tree.ts`负责左侧快速访问栏 - `quick-access-tree.ts`负责左侧快速访问栏
- `remote-container.ts`负责与remote container相关的操作如第一次连接容器、打开容器中的项目文件夹 - `remote-container.ts`负责与remote container相关的操作如第一次连接容器、打开容器中的项目文件夹
- `utils.ts`:一些通用函数 - `utils.ts`:一些通用函数
.devcontainer目录 .devcontainer目录
- devcontainer相关配置 - devcontainer相关配置
.vscode目录 .vscode目录
- `launch.json`(编译)启动插件配置 - `launch.json`(编译)启动插件配置
## 模块 ## 模块
Remote container Remote container
Home Home

View File

@@ -1,13 +1,13 @@
{ {
"Home": "主页", "Home": "主页",
"DevStar Home": "DevStar主页", "DevStar Home": "DevStar主页",
"Open": "打开主页", "Open": "打开主页",
"Miscellaneous": "杂项", "Miscellaneous": "杂项",
"Clean": "清理", "Clean": "清理",
"User login successfully!":"用户已登录!", "User login successfully!":"用户已登录!",
"User has logged out!":"用户已登出!", "User has logged out!":"用户已登出!",
"Installing vscode-server and devstar extension in container": "正在容器中安装vscode-server及devstar插件", "Installing vscode-server and devstar extension in container": "正在容器中安装vscode-server及devstar插件",
"Connected! Start installation": "连接成功,开始安装", "Connected! Start installation": "连接成功,开始安装",
"Installation completed!": "安装完成!", "Installation completed!": "安装完成!",
"Project has been open!": "该项目已经打开!" "Project has been open!": "该项目已经打开!"
} }

View File

@@ -1,139 +1,139 @@
{ {
"name": "devstar", "name": "devstar",
"displayName": "%displayName%", "displayName": "%displayName%",
"description": "%description%", "description": "%description%",
"version": "0.3.8", "version": "0.3.8",
"keywords": [], "keywords": [],
"publisher": "mengning", "publisher": "mengning",
"engines": { "engines": {
"vscode": "^1.75.0" "vscode": "^1.75.0"
}, },
"l10n": "./l10n", "l10n": "./l10n",
"license": "Apache-2.0", "license": "Apache-2.0",
"categories": [], "categories": [],
"main": "./dist/extension", "main": "./dist/extension",
"icon": "assets/images/devstar-logo.png", "icon": "assets/images/devstar-logo.png",
"bugs": { "bugs": {
"url": "https://github.com/mengning/DevStar/issues" "url": "https://github.com/mengning/DevStar/issues"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/mengning/DevStar.git" "url": "https://github.com/mengning/DevStar.git"
}, },
"activationEvents": [ "activationEvents": [
"onView:devstar.quickAccess", "onView:devstar.quickAccess",
"onCommand:devstar.showHome", "onCommand:devstar.showHome",
"onUri" "onUri"
], ],
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
"command": "devstar.showHome", "command": "devstar.showHome",
"title": "%devstar.showHome.title%", "title": "%devstar.showHome.title%",
"category": "DevStar" "category": "DevStar"
}, },
{ {
"command": "devstar.connectRemoteContainer", "command": "devstar.connectRemoteContainer",
"title": "%devstar.connectRemoteContainer.title%", "title": "%devstar.connectRemoteContainer.title%",
"category": "DevStar" "category": "DevStar"
} }
], ],
"uriHandlers": [ "uriHandlers": [
{ {
"protocol": "vscode", "protocol": "vscode",
"path": "/openProject" "path": "/openProject"
} }
], ],
"views": { "views": {
"devstarListView": [ "devstarListView": [
{ {
"id": "devstar.quickAccess", "id": "devstar.quickAccess",
"name": "%devstar.quickAccess.title%", "name": "%devstar.quickAccess.title%",
"type": "tree" "type": "tree"
} }
] ]
}, },
"viewsContainers": { "viewsContainers": {
"activitybar": [ "activitybar": [
{ {
"id": "devstarListView", "id": "devstarListView",
"title": "%devstar.devstar.title%", "title": "%devstar.devstar.title%",
"icon": "assets/icons/devstar-activity-icon.svg" "icon": "assets/icons/devstar-activity-icon.svg"
} }
] ]
}, },
"viewsWelcome": [ "viewsWelcome": [
{ {
"view": "devstar.quickAccess", "view": "devstar.quickAccess",
"contents": "%devstar.welcome.title%" "contents": "%devstar.welcome.title%"
} }
], ],
"menus": { "menus": {
"file/newFile": [ "file/newFile": [
{ {
"command": "devstar.showHome", "command": "devstar.showHome",
"group": "navigation" "group": "navigation"
} }
], ],
"touchBar": [] "touchBar": []
}, },
"configuration": { "configuration": {
"type": "object", "type": "object",
"title": "DevStar", "title": "DevStar",
"properties": { "properties": {
"devstar.disableDevStarHomeStartup": { "devstar.disableDevStarHomeStartup": {
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"description": "Disable showing DevStar Home at startup" "description": "Disable showing DevStar Home at startup"
}, },
"devstar.devstarDomain": { "devstar.devstarDomain": {
"type": "string", "type": "string",
"default": "https://devstar.cn/", "default": "https://devstar.cn/",
"description": "DevStar Domain URL" "description": "DevStar Domain URL"
} }
} }
} }
}, },
"scripts": { "scripts": {
"build": "webpack --mode production", "build": "webpack --mode production",
"package": "webpack --mode production && vsce package", "package": "webpack --mode production && vsce package",
"publish": "vsce publish", "publish": "vsce publish",
"watch": "webpack --mode production --watch", "watch": "webpack --mode production --watch",
"format": "eslint src --ext ts" "format": "eslint src --ext ts"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.7.9",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"fs-plus": "~3.1.1", "fs-plus": "~3.1.1",
"node-ssh": "^13.2.0", "node-ssh": "^13.2.0",
"semver": "^7.7.2", "semver": "^7.7.2",
"sshpk": "^1.18.0" "sshpk": "^1.18.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "~7.21.3", "@babel/core": "~7.21.3",
"@babel/eslint-parser": "~7.21.3", "@babel/eslint-parser": "~7.21.3",
"@babel/plugin-proposal-class-properties": "~7.18.6", "@babel/plugin-proposal-class-properties": "~7.18.6",
"@babel/plugin-proposal-object-rest-spread": "~7.18.9", "@babel/plugin-proposal-object-rest-spread": "~7.18.9",
"@babel/preset-env": "~7.20.2", "@babel/preset-env": "~7.20.2",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.18.6",
"@types/mocha": "^10.0.6", "@types/mocha": "^10.0.6",
"@types/node": "18.x", "@types/node": "18.x",
"@types/vscode": "~1.75.0", "@types/vscode": "~1.75.0",
"@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.11.0", "@typescript-eslint/parser": "^7.11.0",
"@vscode/test-cli": "^0.0.9", "@vscode/test-cli": "^0.0.9",
"@vscode/test-electron": "^2.4.0", "@vscode/test-electron": "^2.4.0",
"@vscode/vsce": "^2.29.0", "@vscode/vsce": "^2.29.0",
"babel-loader": "~9.1.2", "babel-loader": "~9.1.2",
"eslint-import-resolver-webpack": "~0.13.2", "eslint-import-resolver-webpack": "~0.13.2",
"eslint-plugin-import": "~2.27.5", "eslint-plugin-import": "~2.27.5",
"prettier": "~2.8.4", "prettier": "~2.8.4",
"ts-loader": "^9.5.1", "ts-loader": "^9.5.1",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"webpack": "~5.76.2", "webpack": "~5.76.2",
"webpack-cli": "~5.0.1" "webpack-cli": "~5.0.1"
}, },
"extensionDependencies": [ "extensionDependencies": [
"ms-vscode.cpptools" "ms-vscode.cpptools"
] ]
} }

View File

@@ -1,9 +1,9 @@
{ {
"displayName": "DevStar", "displayName": "DevStar",
"description": "DevStar Client", "description": "DevStar Client",
"devstar.showHome.title": "DevStar Home", "devstar.showHome.title": "DevStar Home",
"devstar.connectRemoteContainer.title": "Connect to a Remote Container", "devstar.connectRemoteContainer.title": "Connect to a Remote Container",
"devstar.quickAccess.title": "Quick Access", "devstar.quickAccess.title": "Quick Access",
"devstar.devstar.title":"DevStar", "devstar.devstar.title":"DevStar",
"devstar.welcome.title":"welcome DevStar..." "devstar.welcome.title":"welcome DevStar..."
} }

View File

@@ -1,9 +1,9 @@
{ {
"displayName": "DevStar", "displayName": "DevStar",
"description": "DevStar客户端", "description": "DevStar客户端",
"devstar.showHome.title": "显示主页", "devstar.showHome.title": "显示主页",
"devstar.connectRemoteContainer.title": "连接容器", "devstar.connectRemoteContainer.title": "连接容器",
"devstar.quickAccess.title": "快速访问", "devstar.quickAccess.title": "快速访问",
"devstar.devstar.title":"DevStar", "devstar.devstar.title":"DevStar",
"devstar.welcome.title":"欢迎使用DevStar..." "devstar.welcome.title":"欢迎使用DevStar..."
} }

View File

@@ -1,99 +1,99 @@
import * as os from 'os'; import * as os from 'os';
import User from "./user"; import User from "./user";
import * as utils from './utils'; import * as utils from './utils';
export default class DevstarAPIHandler { export default class DevstarAPIHandler {
private devstarDomain: string; private devstarDomain: string;
constructor() { constructor() {
// 获取domain // 获取domain
const devstarDomainFromUserConfig = utils.devstarDomain() const devstarDomainFromUserConfig = utils.devstarDomain()
if (undefined == devstarDomainFromUserConfig || "" == devstarDomainFromUserConfig) { if (undefined == devstarDomainFromUserConfig || "" == devstarDomainFromUserConfig) {
this.devstarDomain = "https://devstar.cn"; this.devstarDomain = "https://devstar.cn";
} else { } else {
this.devstarDomain = devstarDomainFromUserConfig.endsWith('/') ? devstarDomainFromUserConfig.slice(0, -1) : devstarDomainFromUserConfig; this.devstarDomain = devstarDomainFromUserConfig.endsWith('/') ? devstarDomainFromUserConfig.slice(0, -1) : devstarDomainFromUserConfig;
} }
} }
public async verifyToken(token: string, username: string): Promise<boolean> { public async verifyToken(token: string, username: string): Promise<boolean> {
try { try {
const response = await fetch(this.devstarDomain + `/api/devcontainer/user`, { const response = await fetch(this.devstarDomain + `/api/devcontainer/user`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'token ' + token 'Authorization': 'token ' + token
} }
}); });
// 处理非200响应状态码 // 处理非200响应状态码
if (!response.ok) { if (!response.ok) {
const text = await response.text(); // 先读取文本防止json解析失败 const text = await response.text(); // 先读取文本防止json解析失败
if (response.status == 401) { if (response.status == 401) {
throw new Error('Token错误') throw new Error('Token错误')
} else { } else {
throw new Error(`HTTP Error: ${response.status} - ${text}`); throw new Error(`HTTP Error: ${response.status} - ${text}`);
} }
} }
const responseData = await response.json(); const responseData = await response.json();
const data = responseData.data const data = responseData.data
if (data.username == undefined || data.username == "") { if (data.username == undefined || data.username == "") {
throw new Error('Token对应用户不存在') throw new Error('Token对应用户不存在')
} else { } else {
// 验证用户名匹配 // 验证用户名匹配
if (data.username !== username) { if (data.username !== username) {
throw new Error('Token与用户名不符'); throw new Error('Token与用户名不符');
} }
} }
return true; return true;
} catch (error) { } catch (error) {
console.error(error) console.error(error)
return false return false
} }
} }
// 上传公钥 // 上传公钥
public async uploadUserPublicKey(user: User): Promise<string> { public async uploadUserPublicKey(user: User): Promise<string> {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
// 获取机器名称 // 获取机器名称
const machineName = os.hostname(); const machineName = os.hostname();
// 组成公钥名称 // 组成公钥名称
const timestamp = Date.now(); const timestamp = Date.now();
const keyTitle = `${user.getUsernameFromLocal()}-${machineName}-${timestamp}` const keyTitle = `${user.getUsernameFromLocal()}-${machineName}-${timestamp}`
const postData = { const postData = {
"key": user.getUserPublicKey(), "key": user.getUserPublicKey(),
"title": keyTitle "title": keyTitle
} }
const uploadUrl = this.devstarDomain + `/api/v1/user/keys` const uploadUrl = this.devstarDomain + `/api/v1/user/keys`
// 上传公钥 // 上传公钥
fetch(uploadUrl, { fetch(uploadUrl, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': 'token ' + user.getUserTokenFromLocal() 'Authorization': 'token ' + user.getUserTokenFromLocal()
}, },
body: JSON.stringify(postData) body: JSON.stringify(postData)
}) })
.then(response => { .then(response => {
response.json().then(data => { response.json().then(data => {
if (response.ok) { if (response.ok) {
console.log("Successfully upload new created public key.\n", data) console.log("Successfully upload new created public key.\n", data)
resolve("ok") resolve("ok")
} else { } else {
throw new Error(`Failed to upload new created public key!\nError: ${data.message}`) throw new Error(`Failed to upload new created public key!\nError: ${data.message}`)
} }
}) })
}) })
.catch(error => { .catch(error => {
console.error(error); console.error(error);
}); });
}) })
} }
} }

View File

@@ -1,246 +1,246 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as os from 'os'; import * as os from 'os';
import RemoteContainer from './remote-container'; import RemoteContainer from './remote-container';
import User from './user'; import User from './user';
import * as utils from './utils' import * as utils from './utils'
export default class DSHome { export default class DSHome {
private context: vscode.ExtensionContext; private context: vscode.ExtensionContext;
private remoteContainer: RemoteContainer; private remoteContainer: RemoteContainer;
private user: User; private user: User;
private devstarHomePageUrl: string; private devstarHomePageUrl: string;
private devstarDomain: string | undefined private devstarDomain: string | undefined
constructor(context: vscode.ExtensionContext, user: User) { constructor(context: vscode.ExtensionContext, user: User) {
this.context = context; this.context = context;
this.user = user; this.user = user;
this.remoteContainer = new RemoteContainer(user); this.remoteContainer = new RemoteContainer(user);
this.devstarDomain = utils.devstarDomain() this.devstarDomain = utils.devstarDomain()
if (undefined == this.devstarDomain || "" == this.devstarDomain) { if (undefined == this.devstarDomain || "" == this.devstarDomain) {
this.devstarHomePageUrl = "https://devstar.cn/devstar-home" this.devstarHomePageUrl = "https://devstar.cn/devstar-home"
} else { } else {
this.devstarHomePageUrl = this.devstarDomain.endsWith('/') ? this.devstarDomain + "devstar-home" : this.devstarDomain + "/devstar-home" this.devstarHomePageUrl = this.devstarDomain.endsWith('/') ? this.devstarDomain + "devstar-home" : this.devstarDomain + "/devstar-home"
} }
} }
async toggle(devstarHomePageUrl: string = this.devstarHomePageUrl) { async toggle(devstarHomePageUrl: string = this.devstarHomePageUrl) {
const panel = vscode.window.createWebviewPanel( const panel = vscode.window.createWebviewPanel(
'homeWebview', 'homeWebview',
vscode.l10n.t('Home'), vscode.l10n.t('Home'),
vscode.ViewColumn.One, vscode.ViewColumn.One,
{ {
enableScripts: true, enableScripts: true,
retainContextWhenHidden: true, retainContextWhenHidden: true,
} }
); );
panel.webview.html = await this.getWebviewContent(devstarHomePageUrl); panel.webview.html = await this.getWebviewContent(devstarHomePageUrl);
panel.webview.onDidReceiveMessage( panel.webview.onDidReceiveMessage(
async (message) => { async (message) => {
const data = message.data const data = message.data
const need_return = message.need_return const need_return = message.need_return
if (need_return) { if (need_return) {
// ================= need return ==================== // ================= need return ====================
switch (message.command) { switch (message.command) {
// ----------------- frequent ----------------------- // ----------------- frequent -----------------------
case 'getHomeConfig': case 'getHomeConfig':
const config = { const config = {
language: vscode.env.language language: vscode.env.language
} }
panel.webview.postMessage({command: 'getHomeConfig', data: {homeConfig: config}}) panel.webview.postMessage({command: 'getHomeConfig', data: {homeConfig: config}})
break; break;
case 'getUserToken': case 'getUserToken':
const userToken = this.user.getUserTokenFromLocal() const userToken = this.user.getUserTokenFromLocal()
if (userToken === undefined) { if (userToken === undefined) {
panel.webview.postMessage({ command: 'getUserToken', data: { userToken: '' } }) panel.webview.postMessage({ command: 'getUserToken', data: { userToken: '' } })
break; break;
} else { } else {
panel.webview.postMessage({ command: 'getUserToken', data: { userToken: userToken } }) panel.webview.postMessage({ command: 'getUserToken', data: { userToken: userToken } })
break; break;
} }
case 'getUsername': case 'getUsername':
const username = this.user.getUsernameFromLocal() const username = this.user.getUsernameFromLocal()
if (username === undefined) { if (username === undefined) {
panel.webview.postMessage({ command: 'getUsername', data: { username: '' } }) panel.webview.postMessage({ command: 'getUsername', data: { username: '' } })
break; break;
} else { } else {
panel.webview.postMessage({ command: 'getUsername', data: { username: username } }) panel.webview.postMessage({ command: 'getUsername', data: { username: username } })
break; break;
} }
case 'firstOpenRemoteFolder': case 'firstOpenRemoteFolder':
// data.host - project name // data.host - project name
await this.remoteContainer.firstOpenProject(data.host, data.hostname, data.port, data.username, data.path, this.context) await this.remoteContainer.firstOpenProject(data.host, data.hostname, data.port, data.username, data.path, this.context)
break; break;
case 'openRemoteFolder': case 'openRemoteFolder':
this.remoteContainer.openRemoteFolder(data.host, data.port, data.username, data.path); this.remoteContainer.openRemoteFolder(data.host, data.port, data.username, data.path);
break; break;
case 'getDevstarDomain': case 'getDevstarDomain':
panel.webview.postMessage({ command: 'getDevstarDomain', data: { devstarDomain: this.devstarDomain } }) panel.webview.postMessage({ command: 'getDevstarDomain', data: { devstarDomain: this.devstarDomain } })
break; break;
// ----------------- not frequent ----------------------- // ----------------- not frequent -----------------------
case 'setUserToken': case 'setUserToken':
this.user.setUserTokenToLocal(data.userToken) this.user.setUserTokenToLocal(data.userToken)
if (data.userToken === this.user.getUserTokenFromLocal()) { if (data.userToken === this.user.getUserTokenFromLocal()) {
panel.webview.postMessage({ command: 'setUserToken', data: { ok: true } }) panel.webview.postMessage({ command: 'setUserToken', data: { ok: true } })
break; break;
} else { } else {
panel.webview.postMessage({ command: 'setUserToken', data: { ok: false } }) panel.webview.postMessage({ command: 'setUserToken', data: { ok: false } })
break; break;
} }
case 'setUsername': case 'setUsername':
this.user.setUsernameToLocal(data.username); this.user.setUsernameToLocal(data.username);
if (data.username === this.user.getUsernameFromLocal()) { if (data.username === this.user.getUsernameFromLocal()) {
panel.webview.postMessage({ command: 'setUsername', data: { ok: true } }); panel.webview.postMessage({ command: 'setUsername', data: { ok: true } });
break; break;
} else { } else {
panel.webview.postMessage({ command: 'setUsername', data: { ok: false } }); panel.webview.postMessage({ command: 'setUsername', data: { ok: false } });
break; break;
} }
case 'getUserPublicKey': case 'getUserPublicKey':
var userPublicKey = ''; var userPublicKey = '';
if (this.user.existUserPrivateKey()) { if (this.user.existUserPrivateKey()) {
userPublicKey = this.user.getUserPublicKey(); userPublicKey = this.user.getUserPublicKey();
panel.webview.postMessage({ command: 'getUserPublicKey', data: { userPublicKey: userPublicKey } }) panel.webview.postMessage({ command: 'getUserPublicKey', data: { userPublicKey: userPublicKey } })
break; break;
} else { } else {
panel.webview.postMessage({ command: 'getUserPublicKey', data: { userPublicKey: userPublicKey } }) panel.webview.postMessage({ command: 'getUserPublicKey', data: { userPublicKey: userPublicKey } })
break; break;
} }
case 'createUserPublicKey': case 'createUserPublicKey':
await this.user.createUserSSHKey(); await this.user.createUserSSHKey();
if (this.user.existUserPublicKey()) { if (this.user.existUserPublicKey()) {
panel.webview.postMessage({ command: 'createUserPublicKey', data: { ok: true } }) panel.webview.postMessage({ command: 'createUserPublicKey', data: { ok: true } })
break; break;
} else { } else {
panel.webview.postMessage({ command: 'createUserPublicKey', data: { ok: false } }) panel.webview.postMessage({ command: 'createUserPublicKey', data: { ok: false } })
break; break;
} }
case 'getMachineName': case 'getMachineName':
const machineName = os.hostname(); const machineName = os.hostname();
panel.webview.postMessage({ command: 'getMachineName', data: { machineName: machineName } }) panel.webview.postMessage({ command: 'getMachineName', data: { machineName: machineName } })
} }
} else { } else {
// ================= don't need return ============== // ================= don't need return ==============
// frequent // frequent
switch (message.command) { switch (message.command) {
// ----------------- frequent ----------------------- // ----------------- frequent -----------------------
case 'showInformationNotification': case 'showInformationNotification':
vscode.window.showInformationMessage(data.message); vscode.window.showInformationMessage(data.message);
break; break;
case 'showWarningNotification': case 'showWarningNotification':
vscode.window.showWarningMessage(data.message) vscode.window.showWarningMessage(data.message)
break; break;
case 'showErrorNotification': case 'showErrorNotification':
await utils.showErrorNotification(data.message) await utils.showErrorNotification(data.message)
break; break;
} }
} }
}, },
undefined, undefined,
this.context.subscriptions this.context.subscriptions
); );
this.context.subscriptions.push(panel) this.context.subscriptions.push(panel)
} }
async getWebviewContent(devstarHomePageUrl: string): Promise<string> { async getWebviewContent(devstarHomePageUrl: string): Promise<string> {
return ` return `
<?php <?php
header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method"); header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE"); header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
header("Allow: GET, POST, OPTIONS, PUT, DELETE"); header("Allow: GET, POST, OPTIONS, PUT, DELETE");
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DevStar Home</title> <title>DevStar Home</title>
</head> </head>
<body> <body>
<iframe id="embedded-devstar" src="${devstarHomePageUrl}" sandbox="allow-popups allow-same-origin allow-scripts allow-forms allow-top-navigation-by-user-activation" width="100%" height="100%" frameborder="0" <iframe id="embedded-devstar" src="${devstarHomePageUrl}" sandbox="allow-popups allow-same-origin allow-scripts allow-forms allow-top-navigation-by-user-activation" width="100%" height="100%" frameborder="0"
style="border: 0; left: 0; right: 0; bottom: 0; top: 0; position:absolute;"> style="border: 0; left: 0; right: 0; bottom: 0; top: 0; position:absolute;">
</iframe> </iframe>
<script> <script>
const vscode = acquireVsCodeApi(); const vscode = acquireVsCodeApi();
function firstOpenRemoteFolder() { function firstOpenRemoteFolder() {
vscode.postMessage({ command: 'firstOpenRemoteFolder', host: host, username: username, password: password, port: port, path: path }); vscode.postMessage({ command: 'firstOpenRemoteFolder', host: host, username: username, password: password, port: port, path: path });
} }
function openRemoteFolder() { function openRemoteFolder() {
vscode.postMessage({ command: 'openRemoteFolder', host: host, path: path }); vscode.postMessage({ command: 'openRemoteFolder', host: host, path: path });
} }
function firstOpenRemoteFolderWithData(host, username, password, port, path) { function firstOpenRemoteFolderWithData(host, username, password, port, path) {
vscode.postMessage({ command: 'firstOpenRemoteFolder', host: host, username: username, password: password, port: port, path: path }); vscode.postMessage({ command: 'firstOpenRemoteFolder', host: host, username: username, password: password, port: port, path: path });
} }
function openRemoteFolderWithData(host, path) { function openRemoteFolderWithData(host, path) {
vscode.postMessage({ command: 'openRemoteFolder', host: host, path: path }); vscode.postMessage({ command: 'openRemoteFolder', host: host, path: path });
} }
async function communicateVSCode(command, data) { async function communicateVSCode(command, data) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// request to vscode // request to vscode
vscode.postMessage({ command: command, need_return: true, data: data }) vscode.postMessage({ command: command, need_return: true, data: data })
function handleResponse(event) { function handleResponse(event) {
const jsonData = event.data; const jsonData = event.data;
if (jsonData.command === command) { if (jsonData.command === command) {
// console.log("communicateVSCode", jsonData.data) // console.log("communicateVSCode", jsonData.data)
// return vscode response // return vscode response
window.removeEventListener('message', handleResponse) // 清理监听器 window.removeEventListener('message', handleResponse) // 清理监听器
resolve(jsonData.data) resolve(jsonData.data)
} }
} }
window.addEventListener('message', handleResponse) window.addEventListener('message', handleResponse)
setTimeout(() => { setTimeout(() => {
window.removeEventListener('message', handleResponse) window.removeEventListener('message', handleResponse)
reject('timeout') reject('timeout')
}, 5000); // 5秒超时 }, 5000); // 5秒超时
}) })
} }
// 监听子页面的消息 // 监听子页面的消息
window.addEventListener('message', async (event) => { window.addEventListener('message', async (event) => {
// 出于安全考虑,检查 event.origin 是否是你预期的源 // 出于安全考虑,检查 event.origin 是否是你预期的源
// if (event.origin !== "http://expected-origin.com") { // if (event.origin !== "http://expected-origin.com") {
// return; // return;
// } // }
try { try {
const jsonData = event.data; const jsonData = event.data;
if (jsonData.target === 'vscode') { if (jsonData.target === 'vscode') {
const actionFromHome = jsonData.action const actionFromHome = jsonData.action
const dataFromHome = jsonData.data const dataFromHome = jsonData.data
const dataFromVSCodeResponse = await communicateVSCode(actionFromHome, dataFromHome) const dataFromVSCodeResponse = await communicateVSCode(actionFromHome, dataFromHome)
var iframe = document.getElementById('embedded-devstar'); var iframe = document.getElementById('embedded-devstar');
if (iframe && iframe.contentWindow) { if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage({ action: actionFromHome, data: dataFromVSCodeResponse }, '*') iframe.contentWindow.postMessage({ action: actionFromHome, data: dataFromVSCodeResponse }, '*')
} }
} else if (jsonData.target === 'vscode_no_return') { } else if (jsonData.target === 'vscode_no_return') {
vscode.postMessage({ command: jsonData.action, need_return: false, data: jsonData.data }) vscode.postMessage({ command: jsonData.action, need_return: false, data: jsonData.data })
} }
} catch (error) { } catch (error) {
console.error('Error parsing message:', error); console.error('Error parsing message:', error);
} }
}); });
</script> </script>
</body> </body>
</html>` </html>`
} }
} }

View File

@@ -1,155 +1,155 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as fs from 'fs'; import * as fs from 'fs';
import QuickAccessTreeProvider from './views/quick-access-tree'; import QuickAccessTreeProvider from './views/quick-access-tree';
import DSHome from './home'; import DSHome from './home';
import RemoteContainer, { openProjectWithoutLogging } from './remote-container'; import RemoteContainer, { openProjectWithoutLogging } from './remote-container';
import User from './user'; import User from './user';
import DevstarAPIHandler from './devstar-api'; import DevstarAPIHandler from './devstar-api';
import * as os from 'os'; import * as os from 'os';
import * as utils from './utils'; import * as utils from './utils';
export class DevStarExtension { export class DevStarExtension {
user: User; user: User;
remoteContainer: RemoteContainer; remoteContainer: RemoteContainer;
dsHome: DSHome; dsHome: DSHome;
constructor(private context: vscode.ExtensionContext) { constructor(private context: vscode.ExtensionContext) {
this.user = new User(context); this.user = new User(context);
// 只保持一个User实例 // 只保持一个User实例
this.remoteContainer = new RemoteContainer(this.user); this.remoteContainer = new RemoteContainer(this.user);
this.dsHome = new DSHome(context, this.user); this.dsHome = new DSHome(context, this.user);
// 确定local系统是否为win如果是保存powershell版本 // 确定local系统是否为win如果是保存powershell版本
if (vscode.env.remoteName === undefined) { if (vscode.env.remoteName === undefined) {
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
utils.powershellVersion() utils.powershellVersion()
.then(powershellVersion => { .then(powershellVersion => {
this.context.globalState.update('powershellVersion', powershellVersion) this.context.globalState.update('powershellVersion', powershellVersion)
}) })
} }
} }
// support for open with vscode in web // support for open with vscode in web
const handler = vscode.window.registerUriHandler({ const handler = vscode.window.registerUriHandler({
handleUri: async (uri: vscode.Uri) => { handleUri: async (uri: vscode.Uri) => {
const devstarAPIHandler = new DevstarAPIHandler() const devstarAPIHandler = new DevstarAPIHandler()
if (uri.path === '/openProject') { if (uri.path === '/openProject') {
const params = new URLSearchParams(uri.query); const params = new URLSearchParams(uri.query);
const host = params.get('host'); const host = params.get('host');
const hostname = params.get('hostname'); const hostname = params.get('hostname');
const port = params.get('port'); const port = params.get('port');
const username = params.get('username'); const username = params.get('username');
const path = params.get('path'); const path = params.get('path');
const access_token = params.get('access_token'); const access_token = params.get('access_token');
const devstar_username = params.get('devstar_username'); const devstar_username = params.get('devstar_username');
if (host && hostname && port && username && path) { if (host && hostname && port && username && path) {
const container_host = host; const container_host = host;
const container_hostname = hostname const container_hostname = hostname
const container_port = parseInt(port, 10); const container_port = parseInt(port, 10);
const container_username = username; const container_username = username;
const project_path = decodeURIComponent(path); const project_path = decodeURIComponent(path);
if (access_token && devstar_username) { if (access_token && devstar_username) {
if (!this.user.isLogged()) { if (!this.user.isLogged()) {
// 如果没有用户登录,则直接登录; // 如果没有用户登录,则直接登录;
const res = await this.user.login(access_token, devstar_username) const res = await this.user.login(access_token, devstar_username)
if (res === 'ok') { if (res === 'ok') {
await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context) await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context)
} }
} else if (devstar_username === this.user.getUsernameFromLocal()) { } else if (devstar_username === this.user.getUsernameFromLocal()) {
// 如果同用户已经登录,则忽略,直接打开项目 // 如果同用户已经登录,则忽略,直接打开项目
await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context) await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context)
} else { } else {
// 如果不是同用户,可以选择切换用户,或者不切换登录用户,直接打开容器 // 如果不是同用户,可以选择切换用户,或者不切换登录用户,直接打开容器
const selection = await vscode.window.showWarningMessage(`已登录用户:${this.user.getUsernameFromLocal()},是否切换用户?`, const selection = await vscode.window.showWarningMessage(`已登录用户:${this.user.getUsernameFromLocal()},是否切换用户?`,
'Yes', 'No',); 'Yes', 'No',);
if (selection === 'Yes') { if (selection === 'Yes') {
// 如果没有用户登录,则直接登录; // 如果没有用户登录,则直接登录;
const res = await this.user.login(access_token, devstar_username) const res = await this.user.login(access_token, devstar_username)
if (res === 'ok') { if (res === 'ok') {
await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context) await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context)
} }
} else if (selection === 'No') { } else if (selection === 'No') {
await openProjectWithoutLogging(container_host, container_port, container_username, project_path); await openProjectWithoutLogging(container_host, container_port, container_username, project_path);
} }
} }
} else { } else {
await openProjectWithoutLogging(container_host, container_port, container_username, project_path); await openProjectWithoutLogging(container_host, container_port, container_username, project_path);
} }
} else { } else {
vscode.window.showErrorMessage('Missing required parameters.'); vscode.window.showErrorMessage('Missing required parameters.');
} }
} else if (uri.path === "/openProjectSkippingLoginCheck") { } else if (uri.path === "/openProjectSkippingLoginCheck") {
// 仅有已登录、不用改变登录状态时,用此流程 // 仅有已登录、不用改变登录状态时,用此流程
const params = new URLSearchParams(uri.query); const params = new URLSearchParams(uri.query);
const host = params.get('host'); const host = params.get('host');
const hostname = params.get('hostname'); const hostname = params.get('hostname');
const port = params.get('port'); const port = params.get('port');
const username = params.get('username'); const username = params.get('username');
const path = params.get('path'); const path = params.get('path');
if (host && hostname && port && username && path) { if (host && hostname && port && username && path) {
const container_host = host; const container_host = host;
const container_hostname = hostname const container_hostname = hostname
const container_port = parseInt(port, 10); const container_port = parseInt(port, 10);
const container_username = username; const container_username = username;
const project_path = decodeURIComponent(path); const project_path = decodeURIComponent(path);
await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context) await this.remoteContainer.firstOpenProject(container_host, container_hostname, container_port, container_username, project_path, this.context)
} }
} }
} }
}); });
context.subscriptions.push(handler); context.subscriptions.push(handler);
context.subscriptions.push( context.subscriptions.push(
vscode.window.registerTreeDataProvider( vscode.window.registerTreeDataProvider(
'devstar.quickAccess', 'devstar.quickAccess',
new QuickAccessTreeProvider() new QuickAccessTreeProvider()
) )
); );
this.registerGlobalCommands(context); this.registerGlobalCommands(context);
this.startDevStarHome(); this.startDevStarHome();
} }
async startDevStarHome() { async startDevStarHome() {
vscode.commands.executeCommand('devstar.showHome'); vscode.commands.executeCommand('devstar.showHome');
} }
registerGlobalCommands(context: vscode.ExtensionContext) { registerGlobalCommands(context: vscode.ExtensionContext) {
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('devstar.showHome', (url: string) => vscode.commands.registerCommand('devstar.showHome', (url: string) =>
this.dsHome.toggle(url) this.dsHome.toggle(url)
), ),
vscode.commands.registerCommand('devstar.clean', () => { vscode.commands.registerCommand('devstar.clean', () => {
// 先清除ssh key // 先清除ssh key
if (fs.existsSync(this.user.getUserPrivateKeyPath())) { if (fs.existsSync(this.user.getUserPrivateKeyPath())) {
fs.unlinkSync(this.user.getUserPrivateKeyPath()) fs.unlinkSync(this.user.getUserPrivateKeyPath())
} }
if (fs.existsSync(this.user.getUserPublicKeyPath())) { if (fs.existsSync(this.user.getUserPublicKeyPath())) {
fs.unlinkSync(this.user.getUserPublicKeyPath()) fs.unlinkSync(this.user.getUserPublicKeyPath())
} }
console.log("User's ssh key has been deleted!") console.log("User's ssh key has been deleted!")
// 更新local user private key path // 更新local user private key path
this.user.updateLocalUserPrivateKeyPath("") this.user.updateLocalUserPrivateKeyPath("")
// 退出登录 // 退出登录
this.user.logout() this.user.logout()
}) })
); );
} }
} }
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
return new DevStarExtension(context); return new DevStarExtension(context);
} }
export function deactivate() { export function deactivate() {
} }

View File

@@ -1,204 +1,204 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as rd from 'readline' import * as rd from 'readline'
const { NodeSSH } = require('node-ssh') const { NodeSSH } = require('node-ssh')
import * as utils from './utils'; import * as utils from './utils';
import User from './user'; import User from './user';
import DevstarAPIHandler from './devstar-api'; import DevstarAPIHandler from './devstar-api';
export default class RemoteContainer { export default class RemoteContainer {
private user: User; private user: User;
constructor(user: User) { constructor(user: User) {
this.user = user this.user = user
} }
/** /**
* 第一次打开远程项目 * 第一次打开远程项目
* *
* 远程环境先创建local窗口在通过命令行调用url打开目前仅支持vscode协议 * 远程环境先创建local窗口在通过命令行调用url打开目前仅支持vscode协议
* @param host 项目名称 * @param host 项目名称
* @param hostname ip * @param hostname ip
* @param port * @param port
* @param username * @param username
* @param path * @param path
* @param context 用于支持远程项目环境 * @param context 用于支持远程项目环境
*/ */
async firstOpenProject(host: string, hostname: string, port: number, username: string, path: string, context: vscode.ExtensionContext) { async firstOpenProject(host: string, hostname: string, port: number, username: string, path: string, context: vscode.ExtensionContext) {
if (vscode.env.remoteName) { if (vscode.env.remoteName) {
// 远程环境 // 远程环境
vscode.commands.executeCommand('workbench.action.terminal.newLocal').then(() => { vscode.commands.executeCommand('workbench.action.terminal.newLocal').then(() => {
const terminal = vscode.window.terminals[vscode.window.terminals.length - 1]; const terminal = vscode.window.terminals[vscode.window.terminals.length - 1];
if (terminal) { if (terminal) {
// vscode协议 // vscode协议
// 根据系统+命令行版本确定命令 // 根据系统+命令行版本确定命令
const semver = require('semver') const semver = require('semver')
const powershellVersion = context.globalState.get('powershellVersion') const powershellVersion = context.globalState.get('powershellVersion')
const powershell_semver_compatible_version = semver.coerce(powershellVersion) const powershell_semver_compatible_version = semver.coerce(powershellVersion)
if (powershellVersion === undefined) if (powershellVersion === undefined)
terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`) terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`)
else if (semver.satisfies(powershell_semver_compatible_version, ">=5.1.26100")) { else if (semver.satisfies(powershell_semver_compatible_version, ">=5.1.26100")) {
// win & powershell >= 5.1.26100.0 // win & powershell >= 5.1.26100.0
terminal.sendText(`code --new-window ; code --% --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`) terminal.sendText(`code --new-window ; code --% --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`)
} else { } else {
// win & powershell < 5.1.26100.0 // win & powershell < 5.1.26100.0
terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`) terminal.sendText(`code --new-window && code --open-url "vscode://mengning.devstar/openProjectSkippingLoginCheck?host=${host}&hostname=${hostname}&port=${port}&username=${username}&path=${path}"`)
} }
} }
}) })
} else { } else {
await this.firstConnect(host, hostname, username, port) await this.firstConnect(host, hostname, username, port)
.then((res) => { .then((res) => {
if (res === 'success') { if (res === 'success') {
// only success then open folder // only success then open folder
this.openRemoteFolder(host, port, username, path); this.openRemoteFolder(host, port, username, path);
} }
}) })
} }
} }
/** /**
* local environment第一次连接其他项目 * local environment第一次连接其他项目
* @param host 项目名称 * @param host 项目名称
* @param hostname ip * @param hostname ip
* @param username * @param username
* @param port * @param port
* @returns 成功返回success * @returns 成功返回success
*/ */
// connect with key // connect with key
async firstConnect(host: string, hostname: string, username: string, port: number): Promise<string> { async firstConnect(host: string, hostname: string, username: string, port: number): Promise<string> {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const ssh = new NodeSSH(); const ssh = new NodeSSH();
vscode.window.withProgress({ vscode.window.withProgress({
location: vscode.ProgressLocation.Notification, location: vscode.ProgressLocation.Notification,
title: vscode.l10n.t("Installing vscode-server and devstar extension in container"), title: vscode.l10n.t("Installing vscode-server and devstar extension in container"),
cancellable: false cancellable: false
}, async (progress) => { }, async (progress) => {
try { try {
// 检查公私钥是否存在,如果不存在,需要创建 // 检查公私钥是否存在,如果不存在,需要创建
if (!this.user.existUserPrivateKey() || !this.user.existUserPublicKey()) { if (!this.user.existUserPrivateKey() || !this.user.existUserPublicKey()) {
await this.user.createUserSSHKey() await this.user.createUserSSHKey()
// 上传公钥 // 上传公钥
const devstarAPIHandler = new DevstarAPIHandler() const devstarAPIHandler = new DevstarAPIHandler()
const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this.user) const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this.user)
if (uploadResult !== "ok") { if (uploadResult !== "ok") {
throw new Error('Upload public key failed.') throw new Error('Upload public key failed.')
} }
} }
} catch (error) { } catch (error) {
console.error("Failed to first connect container: ", error) console.error("Failed to first connect container: ", error)
} }
// 本地环境 // 本地环境
try { try {
// connect with key // connect with key
await ssh.connect({ await ssh.connect({
host: hostname, host: hostname,
username: username, username: username,
port: port, port: port,
privateKeyPath: this.user.getUserPrivateKeyPath() privateKeyPath: this.user.getUserPrivateKeyPath()
}); });
progress.report({ message: vscode.l10n.t("Connected! Start installation") }); progress.report({ message: vscode.l10n.t("Connected! Start installation") });
// install vscode-server and devstar extension // install vscode-server and devstar extension
const vscodeCommitId = await utils.getVsCodeCommitId() const vscodeCommitId = await utils.getVsCodeCommitId()
if ("" != vscodeCommitId) { if ("" != vscodeCommitId) {
const vscodeServerUrl = `https://vscode.download.prss.microsoft.com/dbazure/download/stable/${vscodeCommitId}/vscode-server-linux-x64.tar.gz` const vscodeServerUrl = `https://vscode.download.prss.microsoft.com/dbazure/download/stable/${vscodeCommitId}/vscode-server-linux-x64.tar.gz`
const installVscodeServerScript = ` const installVscodeServerScript = `
mkdir -p ~/.vscode-server/bin/${vscodeCommitId} && \\ mkdir -p ~/.vscode-server/bin/${vscodeCommitId} && \\
if [ "$(ls -A ~/.vscode-server/bin/${vscodeCommitId})" ]; then if [ "$(ls -A ~/.vscode-server/bin/${vscodeCommitId})" ]; then
~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar
else else
wget ${vscodeServerUrl} -O vscode-server-linux-x64.tar.gz && \\ wget ${vscodeServerUrl} -O vscode-server-linux-x64.tar.gz && \\
mv vscode-server-linux-x64.tar.gz ~/.vscode-server/bin/${vscodeCommitId} && \\ mv vscode-server-linux-x64.tar.gz ~/.vscode-server/bin/${vscodeCommitId} && \\
cd ~/.vscode-server/bin/${vscodeCommitId} && \\ cd ~/.vscode-server/bin/${vscodeCommitId} && \\
tar -xvzf vscode-server-linux-x64.tar.gz --strip-components 1 && \\ tar -xvzf vscode-server-linux-x64.tar.gz --strip-components 1 && \\
rm vscode-server-linux-x64.tar.gz && \\ rm vscode-server-linux-x64.tar.gz && \\
~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar ~/.vscode-server/bin/${vscodeCommitId}/bin/code-server --install-extension mengning.devstar
fi fi
`; `;
await ssh.execCommand(installVscodeServerScript); await ssh.execCommand(installVscodeServerScript);
console.log("vscode-server and extension installed"); console.log("vscode-server and extension installed");
vscode.window.showInformationMessage(vscode.l10n.t('Installation completed!')); vscode.window.showInformationMessage(vscode.l10n.t('Installation completed!'));
} }
await ssh.dispose(); await ssh.dispose();
// only connect successfully then save the host info // only connect successfully then save the host info
await this.storeProjectSSHInfo(host, hostname, port, username) await this.storeProjectSSHInfo(host, hostname, port, username)
resolve('success') resolve('success')
} catch (error) { } catch (error) {
console.error('Failed to install vscode-server and extension: ', error); console.error('Failed to install vscode-server and extension: ', error);
await ssh.dispose(); await ssh.dispose();
} }
}); });
}); });
} }
/** /**
* 本地环境保存项目的ssh连接信息 * 本地环境保存项目的ssh连接信息
* @param host * @param host
* @param hostname * @param hostname
* @param port * @param port
* @param username * @param username
*/ */
async storeProjectSSHInfo(host: string, hostname: string, port: number, username: string): Promise<void> { async storeProjectSSHInfo(host: string, hostname: string, port: number, username: string): Promise<void> {
const sshConfigPath = path.join(os.homedir(), '.ssh', 'config'); const sshConfigPath = path.join(os.homedir(), '.ssh', 'config');
// check if the host and related info exist in local ssh config file before saving // check if the host and related info exist in local ssh config file before saving
var canAppendSSHConfig = true var canAppendSSHConfig = true
if (fs.existsSync(sshConfigPath)) { if (fs.existsSync(sshConfigPath)) {
var reader = rd.createInterface(fs.createReadStream(sshConfigPath)) var reader = rd.createInterface(fs.createReadStream(sshConfigPath))
for await (const line of reader) { for await (const line of reader) {
if (line.includes(`Host ${host}`)) { if (line.includes(`Host ${host}`)) {
// the container ssh info exists // the container ssh info exists
canAppendSSHConfig = false canAppendSSHConfig = false
break; break;
} }
} }
} }
if (canAppendSSHConfig) { if (canAppendSSHConfig) {
// save the host to the local ssh config file // save the host to the local ssh config file
const privateKeyPath = this.user.getUserPrivateKeyPath(); const privateKeyPath = this.user.getUserPrivateKeyPath();
const newSShConfigContent = const newSShConfigContent =
`\nHost ${host}\n HostName ${hostname}\n Port ${port}\n User ${username}\n PreferredAuthentications publickey\n IdentityFile ${privateKeyPath}\n `; `\nHost ${host}\n HostName ${hostname}\n Port ${port}\n User ${username}\n PreferredAuthentications publickey\n IdentityFile ${privateKeyPath}\n `;
fs.writeFileSync(sshConfigPath, newSShConfigContent, { encoding: 'utf8', flag: 'a' }); fs.writeFileSync(sshConfigPath, newSShConfigContent, { encoding: 'utf8', flag: 'a' });
console.log('Host registered in local ssh config'); console.log('Host registered in local ssh config');
} }
} }
/** /**
* local env * local env
* 仅支持已经成功连接并在ssh config file中存储ssh信息的项目连接。 * 仅支持已经成功连接并在ssh config file中存储ssh信息的项目连接。
* *
* @host 表示project name * @host 表示project name
*/ */
openRemoteFolder(host: string, port: number, username: string, path: string): void { openRemoteFolder(host: string, port: number, username: string, path: string): void {
let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`);
terminal.show(true); terminal.show(true);
// 在原窗口打开 // 在原窗口打开
terminal.sendText(`code --remote ssh-remote+${username}@${host}:${port} ${path} --reuse-window`); terminal.sendText(`code --remote ssh-remote+${username}@${host}:${port} ${path} --reuse-window`);
} }
} }
/** /**
* 打开项目(无须插件登录) * 打开项目(无须插件登录)
* @param hostname 表示ip * @param hostname 表示ip
* @param port * @param port
* @param username * @param username
* @param path * @param path
*/ */
export async function openProjectWithoutLogging(hostname: string, port: number, username: string, path: string): Promise<void> { export async function openProjectWithoutLogging(hostname: string, port: number, username: string, path: string): Promise<void> {
const command = `code --remote ssh-remote+${username}@${hostname}:${port} ${path} --reuse-window` const command = `code --remote ssh-remote+${username}@${hostname}:${port} ${path} --reuse-window`
let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`); let terminal = vscode.window.activeTerminal || vscode.window.createTerminal(`Ext Terminal`);
terminal.show(true); terminal.show(true);
terminal.sendText(command); terminal.sendText(command);
} }

View File

@@ -1,214 +1,214 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as fs from 'fs'; import * as fs from 'fs';
import DevstarAPIHandler from './devstar-api'; import DevstarAPIHandler from './devstar-api';
import { showErrorNotification } from './utils'; import { showErrorNotification } from './utils';
const { const {
generateKeyPairSync, generateKeyPairSync,
createHash createHash
} = require('node:crypto'); } = require('node:crypto');
const sshpk = require('sshpk'); const sshpk = require('sshpk');
export default class User { export default class User {
private context: vscode.ExtensionContext; private context: vscode.ExtensionContext;
private username: string | undefined; private username: string | undefined;
private userToken: string | undefined; private userToken: string | undefined;
private usernameKey: string = 'devstarUsername' private usernameKey: string = 'devstarUsername'
private userTokenKey: string = 'devstarUserToken' private userTokenKey: string = 'devstarUserToken'
private localUserPrivateKeyPath: string = '' private localUserPrivateKeyPath: string = ''
private devstarHostname: string; private devstarHostname: string;
constructor(context: vscode.ExtensionContext) { constructor(context: vscode.ExtensionContext) {
this.context = context; this.context = context;
this.username = this.context.globalState.get(this.usernameKey); this.username = this.context.globalState.get(this.usernameKey);
this.userToken = this.context.globalState.get(this.userTokenKey); this.userToken = this.context.globalState.get(this.userTokenKey);
// 提取devstar domain的主域名用于本地ssh key的命名 // 提取devstar domain的主域名用于本地ssh key的命名
let devstarDomainFromConfig: string | undefined; let devstarDomainFromConfig: string | undefined;
let devstarDomainURL: string; let devstarDomainURL: string;
devstarDomainFromConfig = vscode.workspace.getConfiguration('devstar').get('devstarDomain') devstarDomainFromConfig = vscode.workspace.getConfiguration('devstar').get('devstarDomain')
// 如果没有配置devstar domain则默认domain为https://devstar.cn // 如果没有配置devstar domain则默认domain为https://devstar.cn
devstarDomainURL = (devstarDomainFromConfig === undefined || devstarDomainFromConfig === "") ? 'https://devstar.cn' : devstarDomainFromConfig; devstarDomainURL = (devstarDomainFromConfig === undefined || devstarDomainFromConfig === "") ? 'https://devstar.cn' : devstarDomainFromConfig;
let parsedUrl = new URL(devstarDomainURL); let parsedUrl = new URL(devstarDomainURL);
this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname并用下划线替换. this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname并用下划线替换.
} }
public async login(token: string, username: string) { public async login(token: string, username: string) {
const devstarAPIHandler = new DevstarAPIHandler() const devstarAPIHandler = new DevstarAPIHandler()
try { try {
const res = await devstarAPIHandler.verifyToken(token, username) const res = await devstarAPIHandler.verifyToken(token, username)
if (!res) { if (!res) {
throw new Error('Token verification failed') throw new Error('Token verification failed')
} }
// token与用户名验证通过 // token与用户名验证通过
// 插件登录存储token与用户名 // 插件登录存储token与用户名
this.setUserTokenToLocal(token) this.setUserTokenToLocal(token)
this.setUsernameToLocal(username) this.setUsernameToLocal(username)
// 检查本地是否有用户所属公钥,没有则创建 // 检查本地是否有用户所属公钥,没有则创建
if (!this.existUserPublicKey()) { if (!this.existUserPublicKey()) {
await this.createUserSSHKey() await this.createUserSSHKey()
// 上传公钥 // 上传公钥
const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this) const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this)
if (uploadResult !== 'ok') { if (uploadResult !== 'ok') {
throw new Error('Upload user public key failed') throw new Error('Upload user public key failed')
} }
} }
vscode.window.showInformationMessage(vscode.l10n.t('User login successfully!')) vscode.window.showInformationMessage(vscode.l10n.t('User login successfully!'))
return 'ok' return 'ok'
} catch (error) { } catch (error) {
console.error(error) console.error(error)
await showErrorNotification('用户登录失败!') await showErrorNotification('用户登录失败!')
return 'login failed' return 'login failed'
} }
} }
public logout() { public logout() {
this.setUserTokenToLocal("") this.setUserTokenToLocal("")
this.setUsernameToLocal("") this.setUsernameToLocal("")
vscode.window.showInformationMessage(vscode.l10n.t("User has logged out!")) vscode.window.showInformationMessage(vscode.l10n.t("User has logged out!"))
} }
public isLogged() { public isLogged() {
var existUsername = false; var existUsername = false;
var existUserToken = false; var existUserToken = false;
if (this.username != undefined && this.username != '') { if (this.username != undefined && this.username != '') {
existUsername = true; existUsername = true;
} }
if (this.userToken != undefined && this.userToken != '') { if (this.userToken != undefined && this.userToken != '') {
existUserToken = true; existUserToken = true;
} }
if (existUsername && existUserToken) { if (existUsername && existUserToken) {
return true; return true;
} else { } else {
return false; return false;
} }
} }
public getUsernameFromLocal(): string | undefined { public getUsernameFromLocal(): string | undefined {
return this.username; return this.username;
} }
public getUserTokenFromLocal(): string | undefined { public getUserTokenFromLocal(): string | undefined {
return this.userToken; return this.userToken;
} }
public setUsernameToLocal(username: string) { public setUsernameToLocal(username: string) {
this.context.globalState.update(this.usernameKey, username); this.context.globalState.update(this.usernameKey, username);
this.username = username; this.username = username;
if (username !== "") { if (username !== "") {
console.log('Username has been stored.') console.log('Username has been stored.')
} else { } else {
console.log('Username has been cleaned up.') console.log('Username has been cleaned up.')
} }
} }
public setUserTokenToLocal(userToken: string) { public setUserTokenToLocal(userToken: string) {
this.context.globalState.update(this.userTokenKey, userToken) this.context.globalState.update(this.userTokenKey, userToken)
this.userToken = userToken this.userToken = userToken
if (userToken !== "") { if (userToken !== "") {
console.log('Token has been stored.'); console.log('Token has been stored.');
} else { } else {
console.log('Token has been cleaned up.') console.log('Token has been cleaned up.')
} }
} }
public updateLocalUserPrivateKeyPath(localUserPrivateKeyPath: string) { public updateLocalUserPrivateKeyPath(localUserPrivateKeyPath: string) {
this.context.globalState.update('localUserPrivateKeyPath', localUserPrivateKeyPath) this.context.globalState.update('localUserPrivateKeyPath', localUserPrivateKeyPath)
} }
public getLocalUserPrivateKeyPath(): undefined | string { public getLocalUserPrivateKeyPath(): undefined | string {
return this.context.globalState.get('localUserPrivateKeyPath') return this.context.globalState.get('localUserPrivateKeyPath')
} }
public getUserPrivateKeyPath(): string { public getUserPrivateKeyPath(): string {
if (!this.isLogged) { if (!this.isLogged) {
return ''; return '';
} }
return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}`) return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}`)
} }
public getUserPublicKeyPath(): string { public getUserPublicKeyPath(): string {
if (!this.isLogged) { if (!this.isLogged) {
return ''; return '';
} }
return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}.pub`) return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}.pub`)
} }
public existUserPublicKey(): boolean { public existUserPublicKey(): boolean {
const userPublicKeyPath = this.getUserPublicKeyPath(); const userPublicKeyPath = this.getUserPublicKeyPath();
return fs.existsSync(userPublicKeyPath) return fs.existsSync(userPublicKeyPath)
} }
public existUserPrivateKey(): boolean { public existUserPrivateKey(): boolean {
const userPrivateKeyPath = this.getUserPrivateKeyPath(); const userPrivateKeyPath = this.getUserPrivateKeyPath();
return fs.existsSync(userPrivateKeyPath) return fs.existsSync(userPrivateKeyPath)
} }
public getUserPublicKey(): string { public getUserPublicKey(): string {
const userPublicKeyPath = this.getUserPublicKeyPath(); const userPublicKeyPath = this.getUserPublicKeyPath();
const userPublicKey = fs.readFileSync(userPublicKeyPath, 'utf-8'); const userPublicKey = fs.readFileSync(userPublicKeyPath, 'utf-8');
// remove `\r` `\n` // remove `\r` `\n`
const trimmedDefaultPublicKey = userPublicKey.replace(/[\r\n]/g, ""); const trimmedDefaultPublicKey = userPublicKey.replace(/[\r\n]/g, "");
return trimmedDefaultPublicKey; return trimmedDefaultPublicKey;
} }
public getUserPrivateKey(): string { public getUserPrivateKey(): string {
const userPrivateKey = this.getUserPrivateKeyPath(); const userPrivateKey = this.getUserPrivateKeyPath();
return fs.readFileSync(userPrivateKey, 'utf-8'); return fs.readFileSync(userPrivateKey, 'utf-8');
} }
public async createUserSSHKey() { public async createUserSSHKey() {
if (this.existUserPublicKey() && this.existUserPrivateKey()) { if (this.existUserPublicKey() && this.existUserPrivateKey()) {
// if both public and private key exists, stop // if both public and private key exists, stop
return; return;
} }
const { const {
publicKey, publicKey,
privateKey, privateKey,
} = await generateKeyPairSync('rsa', { } = await generateKeyPairSync('rsa', {
modulusLength: 4096, modulusLength: 4096,
publicKeyEncoding: { publicKeyEncoding: {
type: 'pkcs1', type: 'pkcs1',
format: 'pem', format: 'pem',
}, },
privateKeyEncoding: { privateKeyEncoding: {
type: 'pkcs1', type: 'pkcs1',
format: 'pem', format: 'pem',
}, },
}); });
const publicKeyFingerprint = sshpk.parseKey(publicKey, 'pem').toString('ssh'); const publicKeyFingerprint = sshpk.parseKey(publicKey, 'pem').toString('ssh');
const publicKeyStr = publicKeyFingerprint; // public key is public key fingerprint const publicKeyStr = publicKeyFingerprint; // public key is public key fingerprint
const privateKeyStr = privateKey; const privateKeyStr = privateKey;
try { try {
// 确保公/私钥目录存在 // 确保公/私钥目录存在
const publicKeyDir = path.dirname(this.getUserPublicKeyPath()) const publicKeyDir = path.dirname(this.getUserPublicKeyPath())
if (!fs.existsSync(publicKeyDir)) { if (!fs.existsSync(publicKeyDir)) {
console.log(`Directory ${publicKeyDir} does not exist, creating it...`); console.log(`Directory ${publicKeyDir} does not exist, creating it...`);
// 公钥与私钥的目录一样,所以只用创建一次 // 公钥与私钥的目录一样,所以只用创建一次
fs.mkdirSync(publicKeyDir, {recursive: true}) fs.mkdirSync(publicKeyDir, {recursive: true})
} }
fs.writeFileSync(this.getUserPublicKeyPath(), publicKeyStr); fs.writeFileSync(this.getUserPublicKeyPath(), publicKeyStr);
fs.writeFileSync(this.getUserPrivateKeyPath(), privateKeyStr); fs.writeFileSync(this.getUserPrivateKeyPath(), privateKeyStr);
// limit the permission of private key to prevent that the private key not works // limit the permission of private key to prevent that the private key not works
fs.chmodSync(this.getUserPrivateKeyPath(), 0o600) fs.chmodSync(this.getUserPrivateKeyPath(), 0o600)
console.log(`User's ssh key has been created.`) console.log(`User's ssh key has been created.`)
// 更新local user private key path // 更新local user private key path
this.updateLocalUserPrivateKeyPath(this.getUserPrivateKeyPath()) this.updateLocalUserPrivateKeyPath(this.getUserPrivateKeyPath())
console.log(`Update local user private key path.`) console.log(`Update local user private key path.`)
} catch (error) { } catch (error) {
console.error(`Failed to write public/private key into the user(${this.username}) ssh public/key file: `, error); console.error(`Failed to write public/private key into the user(${this.username}) ssh public/key file: `, error);
} }
} }
} }

View File

@@ -1,135 +1,135 @@
import * as http from 'http'; import * as http from 'http';
import * as https from 'https'; import * as https from 'https';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as os from 'os'; import * as os from 'os';
import { exec } from 'child_process'; import { exec } from 'child_process';
import * as path from 'path'; import * as path from 'path';
const { const {
generateKeyPairSync, generateKeyPairSync,
} = require('node:crypto') } = require('node:crypto')
const axios = require('axios'); const axios = require('axios');
const cheerio = require('cheerio'); const cheerio = require('cheerio');
export function isWindows(): boolean { export function isWindows(): boolean {
return os.platform() === 'win32'; return os.platform() === 'win32';
} }
export function isLinux(): boolean { export function isLinux(): boolean {
return os.platform() === 'linux'; return os.platform() === 'linux';
} }
export function isMacOS(): boolean { export function isMacOS(): boolean {
return os.platform() === 'darwin'; return os.platform() === 'darwin';
} }
export function fetch(url: string): Promise<string> { export function fetch(url: string): Promise<string> {
// determine the library to use (based on the url protocol) // determine the library to use (based on the url protocol)
const lib = url.startsWith('https://') ? https : http; const lib = url.startsWith('https://') ? https : http;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lib.get(url, (response) => { lib.get(url, (response) => {
// make sure the status code is 200 // make sure the status code is 200
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
reject(new Error(`Failed to load page, status code: ${response.statusCode}`)); reject(new Error(`Failed to load page, status code: ${response.statusCode}`));
return; return;
} }
let data = ''; let data = '';
response.on('data', (chunk) => { response.on('data', (chunk) => {
data += chunk; data += chunk;
}); });
response.on('end', () => { response.on('end', () => {
resolve(data); resolve(data);
}); });
}).on('error', (err) => { }).on('error', (err) => {
reject(err); reject(err);
}); });
}); });
} }
export const Sleep = (ms: number) => { export const Sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms)) return new Promise(resolve => setTimeout(resolve, ms))
} }
export async function getVsCodeCommitId(): Promise<string> { export async function getVsCodeCommitId(): Promise<string> {
if (isLinux() || isMacOS()) { if (isLinux() || isMacOS()) {
return new Promise<string>((resolve) => { return new Promise<string>((resolve) => {
exec('code --version', (error, stdout, stderr) => { exec('code --version', (error, stdout, stderr) => {
if (error) { if (error) {
console.error('Error occurred:' + error.message); console.error('Error occurred:' + error.message);
resolve(""); resolve("");
return; return;
} }
if (stderr) { if (stderr) {
console.error('Error output:' + stderr); console.error('Error output:' + stderr);
resolve(""); resolve("");
return; return;
} }
const lines = stdout.trim().split('\n'); const lines = stdout.trim().split('\n');
if (lines.length > 1) { if (lines.length > 1) {
const commitId = lines[1]; // 第二行是 commit ID const commitId = lines[1]; // 第二行是 commit ID
resolve(commitId); resolve(commitId);
} else { } else {
console.error('Unexpected output format:' + stdout); console.error('Unexpected output format:' + stdout);
resolve(""); resolve("");
return; return;
} }
}); });
}) })
} else { } else {
// 获取vscode version // 获取vscode version
const version = vscode.version const version = vscode.version
// 根据version提取相应的release页面中的commitid // 根据version提取相应的release页面中的commitid
try { try {
const { data } = await axios.get(`https://github.com/microsoft/vscode/releases/tag/${version}`); const { data } = await axios.get(`https://github.com/microsoft/vscode/releases/tag/${version}`);
// Load the HTML into cheerio // Load the HTML into cheerio
const $ = cheerio.load(data); const $ = cheerio.load(data);
// Find the <a> tag with the 'data-hovercard-type="commit"' attribute // Find the <a> tag with the 'data-hovercard-type="commit"' attribute
const commitLink = $('a[data-hovercard-type="commit"]'); const commitLink = $('a[data-hovercard-type="commit"]');
// Extract the href attribute and commit hash // Extract the href attribute and commit hash
const href = commitLink.attr('href'); // href example: /microsoft/vscode/commit/fabdb6a30b49f79a7aba0f2ad9df9b399473380f const href = commitLink.attr('href'); // href example: /microsoft/vscode/commit/fabdb6a30b49f79a7aba0f2ad9df9b399473380f
const commitHash = href.split('/').pop(); const commitHash = href.split('/').pop();
return commitHash return commitHash
} catch (error) { } catch (error) {
console.error('Failed to get commit id: ' + error) console.error('Failed to get commit id: ' + error)
return "" return ""
} }
} }
} }
export function devstarDomain(): string | undefined { export function devstarDomain(): string | undefined {
// 从用户配置中读取 // 从用户配置中读取
return vscode.workspace.getConfiguration('devstar').get('devstarDomain') return vscode.workspace.getConfiguration('devstar').get('devstarDomain')
} }
export async function showErrorNotification(message: string): Promise<void> { export async function showErrorNotification(message: string): Promise<void> {
const selection = await vscode.window.showErrorMessage(message, 'Open Console', 'Report a problem',); const selection = await vscode.window.showErrorMessage(message, 'Open Console', 'Report a problem',);
if (selection === 'Open Console') { if (selection === 'Open Console') {
vscode.commands.executeCommand('workbench.action.toggleDevTools'); vscode.commands.executeCommand('workbench.action.toggleDevTools');
} else if (selection === 'Report a problem') { } else if (selection === 'Report a problem') {
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/mengning/DevStar/issues/new')); vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/mengning/DevStar/issues/new'));
} }
} }
export async function powershellVersion(): Promise<string> { export async function powershellVersion(): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec('powershell -Command "$PSVersionTable.PSVersion.ToString()"', (error, stdout, stderr) => { exec('powershell -Command "$PSVersionTable.PSVersion.ToString()"', (error, stdout, stderr) => {
if (error) { if (error) {
reject(`Error executing PowerShell command: ${error.message}`); reject(`Error executing PowerShell command: ${error.message}`);
return; return;
} }
if (stderr) { if (stderr) {
reject(`PowerShell command returned an error: ${stderr}`); reject(`PowerShell command returned an error: ${stderr}`);
return; return;
} }
resolve(stdout.trim()); resolve(stdout.trim());
}); });
}); });
} }

View File

@@ -1,47 +1,47 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
class QuickItem extends vscode.TreeItem { class QuickItem extends vscode.TreeItem {
customChildren: QuickItem[] | undefined; customChildren: QuickItem[] | undefined;
constructor(label: string, command?: string, args?: any, collapsibleState?: vscode.TreeItemCollapsibleState, children?: QuickItem[]) { constructor(label: string, command?: string, args?: any, collapsibleState?: vscode.TreeItemCollapsibleState, children?: QuickItem[]) {
super(label, collapsibleState); super(label, collapsibleState);
if (command) { if (command) {
this.command = { this.command = {
title: label, title: label,
command, command,
arguments: args, arguments: args,
}; };
} }
this.customChildren = children; this.customChildren = children;
} }
} }
export default class QuickAccessTreeProvider { export default class QuickAccessTreeProvider {
getChildren(element: QuickItem) { getChildren(element: QuickItem) {
if (element && element.customChildren) { if (element && element.customChildren) {
return element.customChildren; return element.customChildren;
} }
return [ return [
new QuickItem( new QuickItem(
vscode.l10n.t('DevStar Home'), vscode.l10n.t('DevStar Home'),
undefined, undefined,
undefined, undefined,
vscode.TreeItemCollapsibleState.Expanded, vscode.TreeItemCollapsibleState.Expanded,
[new QuickItem(vscode.l10n.t('Open'), 'devstar.showHome')] [new QuickItem(vscode.l10n.t('Open'), 'devstar.showHome')]
), ),
new QuickItem( new QuickItem(
vscode.l10n.t('Miscellaneous'), vscode.l10n.t('Miscellaneous'),
undefined, undefined,
undefined, undefined,
vscode.TreeItemCollapsibleState.Expanded, vscode.TreeItemCollapsibleState.Expanded,
// [new QuickItem('Connect Remote Container', 'devstar.connectRemoteContainer')] // [new QuickItem('Connect Remote Container', 'devstar.connectRemoteContainer')]
[new QuickItem(vscode.l10n.t('Clean'), 'devstar.clean')] [new QuickItem(vscode.l10n.t('Clean'), 'devstar.clean')]
), ),
]; ];
} }
getTreeItem(element: QuickItem) { getTreeItem(element: QuickItem) {
return element; return element;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,24 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "CommonJS", "module": "CommonJS",
"target": "ES6", "target": "ES6",
"lib": [ "lib": [
"ES6", "ES6",
"DOM" "DOM"
], ],
"sourceMap": true, "sourceMap": true,
"rootDir": "src", "rootDir": "src",
"strict": true, /* enable all strict type-checking options */ "strict": true, /* enable all strict type-checking options */
/* Additional Checks */ /* Additional Checks */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
"noUnusedParameters": true, /* Report errors on unused parameters. */ "noUnusedParameters": true, /* Report errors on unused parameters. */
/* Emit */ /* Emit */
"outDir": "dist", /* Redirect output structure to the directory. */ "outDir": "dist", /* Redirect output structure to the directory. */
"removeComments": true, /* Do not emit comments to output. */ "removeComments": true, /* Do not emit comments to output. */
"noEmitOnError": true, /* Do not emit outputs if any errors were reported. */ "noEmitOnError": true, /* Do not emit outputs if any errors were reported. */
}, },
"include": [ "include": [
"**/*.ts" "**/*.ts"
], ],
} }

View File

@@ -1,32 +1,32 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const packageConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')); const packageConfig = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
const externals = Object.keys(packageConfig.dependencies); const externals = Object.keys(packageConfig.dependencies);
externals.push('vscode'); externals.push('vscode');
module.exports = { module.exports = {
mode: 'production', mode: 'production',
entry: __dirname + '/src/main.ts', entry: __dirname + '/src/main.ts',
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'extension.js', filename: 'extension.js',
libraryTarget: 'commonjs2' libraryTarget: 'commonjs2'
}, },
devtool: 'source-map', devtool: 'source-map',
target: 'node', target: 'node',
externals: externals, externals: externals,
module: { module: {
rules: [ rules: [
{ {
test: /\.ts$/, test: /\.ts$/,
loader: 'ts-loader', loader: 'ts-loader',
exclude: /node_modules/ exclude: /node_modules/
} }
] ]
}, },
resolve: { resolve: {
modules: [path.resolve('./node_modules'), path.resolve('./src')], modules: [path.resolve('./node_modules'), path.resolve('./src')],
extensions: ['.ts', '.js'] extensions: ['.ts', '.js']
} }
}; };