第一次

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": [
"@babel/plugin-proposal-class-properties"
],
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "12"
}
}
]
]
{
"plugins": [
"@babel/plugin-proposal-class-properties"
],
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "12"
}
}
]
]
}

View File

@@ -1,25 +1,25 @@
// 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
{
"name": "Node.js & TypeScript",
// 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",
"features": {
"ghcr.io/devcontainers/features/git:1": {}
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
// 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
{
"name": "Node.js & TypeScript",
// 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",
"features": {
"ghcr.io/devcontainers/features/git:1": {}
},
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

View File

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

View File

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

16
.gitignore vendored
View File

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

52
.vscode/launch.json vendored
View File

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

66
.vscode/tasks.json vendored
View File

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

View File

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

1322
LICENSE

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,36 @@
# DevStar
#### Description
Super IDE Client for VS Code
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
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)
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)
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/)
# DevStar
#### Description
Super IDE Client for VS Code
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
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)
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)
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/)

166
README.md
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,214 +1,214 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import DevstarAPIHandler from './devstar-api';
import { showErrorNotification } from './utils';
const {
generateKeyPairSync,
createHash
} = require('node:crypto');
const sshpk = require('sshpk');
export default class User {
private context: vscode.ExtensionContext;
private username: string | undefined;
private userToken: string | undefined;
private usernameKey: string = 'devstarUsername'
private userTokenKey: string = 'devstarUserToken'
private localUserPrivateKeyPath: string = ''
private devstarHostname: string;
constructor(context: vscode.ExtensionContext) {
this.context = context;
this.username = this.context.globalState.get(this.usernameKey);
this.userToken = this.context.globalState.get(this.userTokenKey);
// 提取devstar domain的主域名用于本地ssh key的命名
let devstarDomainFromConfig: string | undefined;
let devstarDomainURL: string;
devstarDomainFromConfig = vscode.workspace.getConfiguration('devstar').get('devstarDomain')
// 如果没有配置devstar domain则默认domain为https://devstar.cn
devstarDomainURL = (devstarDomainFromConfig === undefined || devstarDomainFromConfig === "") ? 'https://devstar.cn' : devstarDomainFromConfig;
let parsedUrl = new URL(devstarDomainURL);
this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname并用下划线替换.
}
public async login(token: string, username: string) {
const devstarAPIHandler = new DevstarAPIHandler()
try {
const res = await devstarAPIHandler.verifyToken(token, username)
if (!res) {
throw new Error('Token verification failed')
}
// token与用户名验证通过
// 插件登录存储token与用户名
this.setUserTokenToLocal(token)
this.setUsernameToLocal(username)
// 检查本地是否有用户所属公钥,没有则创建
if (!this.existUserPublicKey()) {
await this.createUserSSHKey()
// 上传公钥
const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this)
if (uploadResult !== 'ok') {
throw new Error('Upload user public key failed')
}
}
vscode.window.showInformationMessage(vscode.l10n.t('User login successfully!'))
return 'ok'
} catch (error) {
console.error(error)
await showErrorNotification('用户登录失败!')
return 'login failed'
}
}
public logout() {
this.setUserTokenToLocal("")
this.setUsernameToLocal("")
vscode.window.showInformationMessage(vscode.l10n.t("User has logged out!"))
}
public isLogged() {
var existUsername = false;
var existUserToken = false;
if (this.username != undefined && this.username != '') {
existUsername = true;
}
if (this.userToken != undefined && this.userToken != '') {
existUserToken = true;
}
if (existUsername && existUserToken) {
return true;
} else {
return false;
}
}
public getUsernameFromLocal(): string | undefined {
return this.username;
}
public getUserTokenFromLocal(): string | undefined {
return this.userToken;
}
public setUsernameToLocal(username: string) {
this.context.globalState.update(this.usernameKey, username);
this.username = username;
if (username !== "") {
console.log('Username has been stored.')
} else {
console.log('Username has been cleaned up.')
}
}
public setUserTokenToLocal(userToken: string) {
this.context.globalState.update(this.userTokenKey, userToken)
this.userToken = userToken
if (userToken !== "") {
console.log('Token has been stored.');
} else {
console.log('Token has been cleaned up.')
}
}
public updateLocalUserPrivateKeyPath(localUserPrivateKeyPath: string) {
this.context.globalState.update('localUserPrivateKeyPath', localUserPrivateKeyPath)
}
public getLocalUserPrivateKeyPath(): undefined | string {
return this.context.globalState.get('localUserPrivateKeyPath')
}
public getUserPrivateKeyPath(): string {
if (!this.isLogged) {
return '';
}
return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}`)
}
public getUserPublicKeyPath(): string {
if (!this.isLogged) {
return '';
}
return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}.pub`)
}
public existUserPublicKey(): boolean {
const userPublicKeyPath = this.getUserPublicKeyPath();
return fs.existsSync(userPublicKeyPath)
}
public existUserPrivateKey(): boolean {
const userPrivateKeyPath = this.getUserPrivateKeyPath();
return fs.existsSync(userPrivateKeyPath)
}
public getUserPublicKey(): string {
const userPublicKeyPath = this.getUserPublicKeyPath();
const userPublicKey = fs.readFileSync(userPublicKeyPath, 'utf-8');
// remove `\r` `\n`
const trimmedDefaultPublicKey = userPublicKey.replace(/[\r\n]/g, "");
return trimmedDefaultPublicKey;
}
public getUserPrivateKey(): string {
const userPrivateKey = this.getUserPrivateKeyPath();
return fs.readFileSync(userPrivateKey, 'utf-8');
}
public async createUserSSHKey() {
if (this.existUserPublicKey() && this.existUserPrivateKey()) {
// if both public and private key exists, stop
return;
}
const {
publicKey,
privateKey,
} = await generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
});
const publicKeyFingerprint = sshpk.parseKey(publicKey, 'pem').toString('ssh');
const publicKeyStr = publicKeyFingerprint; // public key is public key fingerprint
const privateKeyStr = privateKey;
try {
// 确保公/私钥目录存在
const publicKeyDir = path.dirname(this.getUserPublicKeyPath())
if (!fs.existsSync(publicKeyDir)) {
console.log(`Directory ${publicKeyDir} does not exist, creating it...`);
// 公钥与私钥的目录一样,所以只用创建一次
fs.mkdirSync(publicKeyDir, {recursive: true})
}
fs.writeFileSync(this.getUserPublicKeyPath(), publicKeyStr);
fs.writeFileSync(this.getUserPrivateKeyPath(), privateKeyStr);
// limit the permission of private key to prevent that the private key not works
fs.chmodSync(this.getUserPrivateKeyPath(), 0o600)
console.log(`User's ssh key has been created.`)
// 更新local user private key path
this.updateLocalUserPrivateKeyPath(this.getUserPrivateKeyPath())
console.log(`Update local user private key path.`)
} catch (error) {
console.error(`Failed to write public/private key into the user(${this.username}) ssh public/key file: `, error);
}
}
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
import DevstarAPIHandler from './devstar-api';
import { showErrorNotification } from './utils';
const {
generateKeyPairSync,
createHash
} = require('node:crypto');
const sshpk = require('sshpk');
export default class User {
private context: vscode.ExtensionContext;
private username: string | undefined;
private userToken: string | undefined;
private usernameKey: string = 'devstarUsername'
private userTokenKey: string = 'devstarUserToken'
private localUserPrivateKeyPath: string = ''
private devstarHostname: string;
constructor(context: vscode.ExtensionContext) {
this.context = context;
this.username = this.context.globalState.get(this.usernameKey);
this.userToken = this.context.globalState.get(this.userTokenKey);
// 提取devstar domain的主域名用于本地ssh key的命名
let devstarDomainFromConfig: string | undefined;
let devstarDomainURL: string;
devstarDomainFromConfig = vscode.workspace.getConfiguration('devstar').get('devstarDomain')
// 如果没有配置devstar domain则默认domain为https://devstar.cn
devstarDomainURL = (devstarDomainFromConfig === undefined || devstarDomainFromConfig === "") ? 'https://devstar.cn' : devstarDomainFromConfig;
let parsedUrl = new URL(devstarDomainURL);
this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname并用下划线替换.
}
public async login(token: string, username: string) {
const devstarAPIHandler = new DevstarAPIHandler()
try {
const res = await devstarAPIHandler.verifyToken(token, username)
if (!res) {
throw new Error('Token verification failed')
}
// token与用户名验证通过
// 插件登录存储token与用户名
this.setUserTokenToLocal(token)
this.setUsernameToLocal(username)
// 检查本地是否有用户所属公钥,没有则创建
if (!this.existUserPublicKey()) {
await this.createUserSSHKey()
// 上传公钥
const uploadResult = await devstarAPIHandler.uploadUserPublicKey(this)
if (uploadResult !== 'ok') {
throw new Error('Upload user public key failed')
}
}
vscode.window.showInformationMessage(vscode.l10n.t('User login successfully!'))
return 'ok'
} catch (error) {
console.error(error)
await showErrorNotification('用户登录失败!')
return 'login failed'
}
}
public logout() {
this.setUserTokenToLocal("")
this.setUsernameToLocal("")
vscode.window.showInformationMessage(vscode.l10n.t("User has logged out!"))
}
public isLogged() {
var existUsername = false;
var existUserToken = false;
if (this.username != undefined && this.username != '') {
existUsername = true;
}
if (this.userToken != undefined && this.userToken != '') {
existUserToken = true;
}
if (existUsername && existUserToken) {
return true;
} else {
return false;
}
}
public getUsernameFromLocal(): string | undefined {
return this.username;
}
public getUserTokenFromLocal(): string | undefined {
return this.userToken;
}
public setUsernameToLocal(username: string) {
this.context.globalState.update(this.usernameKey, username);
this.username = username;
if (username !== "") {
console.log('Username has been stored.')
} else {
console.log('Username has been cleaned up.')
}
}
public setUserTokenToLocal(userToken: string) {
this.context.globalState.update(this.userTokenKey, userToken)
this.userToken = userToken
if (userToken !== "") {
console.log('Token has been stored.');
} else {
console.log('Token has been cleaned up.')
}
}
public updateLocalUserPrivateKeyPath(localUserPrivateKeyPath: string) {
this.context.globalState.update('localUserPrivateKeyPath', localUserPrivateKeyPath)
}
public getLocalUserPrivateKeyPath(): undefined | string {
return this.context.globalState.get('localUserPrivateKeyPath')
}
public getUserPrivateKeyPath(): string {
if (!this.isLogged) {
return '';
}
return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}`)
}
public getUserPublicKeyPath(): string {
if (!this.isLogged) {
return '';
}
return path.join(os.homedir(), '.ssh', `id_rsa_${this.username}_${this.devstarHostname}.pub`)
}
public existUserPublicKey(): boolean {
const userPublicKeyPath = this.getUserPublicKeyPath();
return fs.existsSync(userPublicKeyPath)
}
public existUserPrivateKey(): boolean {
const userPrivateKeyPath = this.getUserPrivateKeyPath();
return fs.existsSync(userPrivateKeyPath)
}
public getUserPublicKey(): string {
const userPublicKeyPath = this.getUserPublicKeyPath();
const userPublicKey = fs.readFileSync(userPublicKeyPath, 'utf-8');
// remove `\r` `\n`
const trimmedDefaultPublicKey = userPublicKey.replace(/[\r\n]/g, "");
return trimmedDefaultPublicKey;
}
public getUserPrivateKey(): string {
const userPrivateKey = this.getUserPrivateKeyPath();
return fs.readFileSync(userPrivateKey, 'utf-8');
}
public async createUserSSHKey() {
if (this.existUserPublicKey() && this.existUserPrivateKey()) {
// if both public and private key exists, stop
return;
}
const {
publicKey,
privateKey,
} = await generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
});
const publicKeyFingerprint = sshpk.parseKey(publicKey, 'pem').toString('ssh');
const publicKeyStr = publicKeyFingerprint; // public key is public key fingerprint
const privateKeyStr = privateKey;
try {
// 确保公/私钥目录存在
const publicKeyDir = path.dirname(this.getUserPublicKeyPath())
if (!fs.existsSync(publicKeyDir)) {
console.log(`Directory ${publicKeyDir} does not exist, creating it...`);
// 公钥与私钥的目录一样,所以只用创建一次
fs.mkdirSync(publicKeyDir, {recursive: true})
}
fs.writeFileSync(this.getUserPublicKeyPath(), publicKeyStr);
fs.writeFileSync(this.getUserPrivateKeyPath(), privateKeyStr);
// limit the permission of private key to prevent that the private key not works
fs.chmodSync(this.getUserPrivateKeyPath(), 0o600)
console.log(`User's ssh key has been created.`)
// 更新local user private key path
this.updateLocalUserPrivateKeyPath(this.getUserPrivateKeyPath())
console.log(`Update local user private key path.`)
} catch (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 https from 'https';
import * as vscode from 'vscode';
import * as os from 'os';
import { exec } from 'child_process';
import * as path from 'path';
const {
generateKeyPairSync,
} = require('node:crypto')
const axios = require('axios');
const cheerio = require('cheerio');
export function isWindows(): boolean {
return os.platform() === 'win32';
}
export function isLinux(): boolean {
return os.platform() === 'linux';
}
export function isMacOS(): boolean {
return os.platform() === 'darwin';
}
export function fetch(url: string): Promise<string> {
// determine the library to use (based on the url protocol)
const lib = url.startsWith('https://') ? https : http;
return new Promise((resolve, reject) => {
lib.get(url, (response) => {
// make sure the status code is 200
if (response.statusCode !== 200) {
reject(new Error(`Failed to load page, status code: ${response.statusCode}`));
return;
}
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
resolve(data);
});
}).on('error', (err) => {
reject(err);
});
});
}
export const Sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
export async function getVsCodeCommitId(): Promise<string> {
if (isLinux() || isMacOS()) {
return new Promise<string>((resolve) => {
exec('code --version', (error, stdout, stderr) => {
if (error) {
console.error('Error occurred:' + error.message);
resolve("");
return;
}
if (stderr) {
console.error('Error output:' + stderr);
resolve("");
return;
}
const lines = stdout.trim().split('\n');
if (lines.length > 1) {
const commitId = lines[1]; // 第二行是 commit ID
resolve(commitId);
} else {
console.error('Unexpected output format:' + stdout);
resolve("");
return;
}
});
})
} else {
// 获取vscode version
const version = vscode.version
// 根据version提取相应的release页面中的commitid
try {
const { data } = await axios.get(`https://github.com/microsoft/vscode/releases/tag/${version}`);
// Load the HTML into cheerio
const $ = cheerio.load(data);
// Find the <a> tag with the 'data-hovercard-type="commit"' attribute
const commitLink = $('a[data-hovercard-type="commit"]');
// Extract the href attribute and commit hash
const href = commitLink.attr('href'); // href example: /microsoft/vscode/commit/fabdb6a30b49f79a7aba0f2ad9df9b399473380f
const commitHash = href.split('/').pop();
return commitHash
} catch (error) {
console.error('Failed to get commit id: ' + error)
return ""
}
}
}
export function devstarDomain(): string | undefined {
// 从用户配置中读取
return vscode.workspace.getConfiguration('devstar').get('devstarDomain')
}
export async function showErrorNotification(message: string): Promise<void> {
const selection = await vscode.window.showErrorMessage(message, 'Open Console', 'Report a problem',);
if (selection === 'Open Console') {
vscode.commands.executeCommand('workbench.action.toggleDevTools');
} else if (selection === 'Report a problem') {
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/mengning/DevStar/issues/new'));
}
}
export async function powershellVersion(): Promise<string> {
return new Promise((resolve, reject) => {
exec('powershell -Command "$PSVersionTable.PSVersion.ToString()"', (error, stdout, stderr) => {
if (error) {
reject(`Error executing PowerShell command: ${error.message}`);
return;
}
if (stderr) {
reject(`PowerShell command returned an error: ${stderr}`);
return;
}
resolve(stdout.trim());
});
});
import * as http from 'http';
import * as https from 'https';
import * as vscode from 'vscode';
import * as os from 'os';
import { exec } from 'child_process';
import * as path from 'path';
const {
generateKeyPairSync,
} = require('node:crypto')
const axios = require('axios');
const cheerio = require('cheerio');
export function isWindows(): boolean {
return os.platform() === 'win32';
}
export function isLinux(): boolean {
return os.platform() === 'linux';
}
export function isMacOS(): boolean {
return os.platform() === 'darwin';
}
export function fetch(url: string): Promise<string> {
// determine the library to use (based on the url protocol)
const lib = url.startsWith('https://') ? https : http;
return new Promise((resolve, reject) => {
lib.get(url, (response) => {
// make sure the status code is 200
if (response.statusCode !== 200) {
reject(new Error(`Failed to load page, status code: ${response.statusCode}`));
return;
}
let data = '';
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
resolve(data);
});
}).on('error', (err) => {
reject(err);
});
});
}
export const Sleep = (ms: number) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
export async function getVsCodeCommitId(): Promise<string> {
if (isLinux() || isMacOS()) {
return new Promise<string>((resolve) => {
exec('code --version', (error, stdout, stderr) => {
if (error) {
console.error('Error occurred:' + error.message);
resolve("");
return;
}
if (stderr) {
console.error('Error output:' + stderr);
resolve("");
return;
}
const lines = stdout.trim().split('\n');
if (lines.length > 1) {
const commitId = lines[1]; // 第二行是 commit ID
resolve(commitId);
} else {
console.error('Unexpected output format:' + stdout);
resolve("");
return;
}
});
})
} else {
// 获取vscode version
const version = vscode.version
// 根据version提取相应的release页面中的commitid
try {
const { data } = await axios.get(`https://github.com/microsoft/vscode/releases/tag/${version}`);
// Load the HTML into cheerio
const $ = cheerio.load(data);
// Find the <a> tag with the 'data-hovercard-type="commit"' attribute
const commitLink = $('a[data-hovercard-type="commit"]');
// Extract the href attribute and commit hash
const href = commitLink.attr('href'); // href example: /microsoft/vscode/commit/fabdb6a30b49f79a7aba0f2ad9df9b399473380f
const commitHash = href.split('/').pop();
return commitHash
} catch (error) {
console.error('Failed to get commit id: ' + error)
return ""
}
}
}
export function devstarDomain(): string | undefined {
// 从用户配置中读取
return vscode.workspace.getConfiguration('devstar').get('devstarDomain')
}
export async function showErrorNotification(message: string): Promise<void> {
const selection = await vscode.window.showErrorMessage(message, 'Open Console', 'Report a problem',);
if (selection === 'Open Console') {
vscode.commands.executeCommand('workbench.action.toggleDevTools');
} else if (selection === 'Report a problem') {
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://github.com/mengning/DevStar/issues/new'));
}
}
export async function powershellVersion(): Promise<string> {
return new Promise((resolve, reject) => {
exec('powershell -Command "$PSVersionTable.PSVersion.ToString()"', (error, stdout, stderr) => {
if (error) {
reject(`Error executing PowerShell command: ${error.message}`);
return;
}
if (stderr) {
reject(`PowerShell command returned an error: ${stderr}`);
return;
}
resolve(stdout.trim());
});
});
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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