Compare commits
	
		
			7 Commits
		
	
	
		
			main
			...
			b713e4515a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					b713e4515a | ||
| 
						 | 
					5f38aafeed | ||
| 
						 | 
					9b56dba1e2 | ||
| 
						 | 
					dd106a1ecb | ||
| 
						 | 
					9141d67894 | ||
| 
						 | 
					e6c159520a | ||
| 
						 | 
					00fd73d41b | 
							
								
								
									
										28
									
								
								.babelrc
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								.babelrc
									
									
									
									
									
								
							@@ -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"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
!.eslintrc.js
 | 
			
		||||
/node_modules/**
 | 
			
		||||
/lib/**
 | 
			
		||||
!.eslintrc.js
 | 
			
		||||
/node_modules/**
 | 
			
		||||
/lib/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "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"
 | 
			
		||||
    ]
 | 
			
		||||
{
 | 
			
		||||
    "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"
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -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
									
									
								
							
							
						
						
									
										52
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -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
									
									
								
							
							
						
						
									
										66
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							@@ -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"
 | 
			
		||||
		},
 | 
			
		||||
	]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
.vscode/**
 | 
			
		||||
.vscode-test/**
 | 
			
		||||
src/**
 | 
			
		||||
test/**
 | 
			
		||||
.babelrc
 | 
			
		||||
.eslintignore
 | 
			
		||||
.eslintrc.js
 | 
			
		||||
.gitignore
 | 
			
		||||
.vscode/**
 | 
			
		||||
.vscode-test/**
 | 
			
		||||
src/**
 | 
			
		||||
test/**
 | 
			
		||||
.babelrc
 | 
			
		||||
.eslintignore
 | 
			
		||||
.eslintrc.js
 | 
			
		||||
.gitignore
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										72
									
								
								README.en.md
									
									
									
									
									
								
							
							
						
						
									
										72
									
								
								README.en.md
									
									
									
									
									
								
							@@ -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
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								README.md
									
									
									
									
									
								
							@@ -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 Token(token项目负责人)。(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 Token(token项目负责人)。(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
 | 
			
		||||
```
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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!": "该项目已经打开!"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										278
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										278
									
								
								package.json
									
									
									
									
									
								
							@@ -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"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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..."
 | 
			
		||||
}
 | 
			
		||||
@@ -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..."
 | 
			
		||||
}
 | 
			
		||||
@@ -1,99 +1,115 @@
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * domain使用用户配置
 | 
			
		||||
   */
 | 
			
		||||
  constructor();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * open with vscode链接传入devstarDomain
 | 
			
		||||
   * @param devstarDomainURL 
 | 
			
		||||
   */
 | 
			
		||||
  constructor(devstarDomainURL: string);
 | 
			
		||||
 | 
			
		||||
  constructor(devstarDomainURL?: string) {
 | 
			
		||||
    if (devstarDomainURL == undefined || devstarDomainURL == "") {
 | 
			
		||||
      // 获取domain
 | 
			
		||||
      const devstarDomainFromUserConfig = utils.devstarDomain()
 | 
			
		||||
      if (undefined == devstarDomainFromUserConfig || "" == devstarDomainFromUserConfig) {
 | 
			
		||||
        this.devstarDomain = "https://devstar.cn";
 | 
			
		||||
      } else {
 | 
			
		||||
        this.devstarDomain = devstarDomainFromUserConfig.endsWith('/') ? devstarDomainFromUserConfig.slice(0, -1) : devstarDomainFromUserConfig;
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // open with vscode传入
 | 
			
		||||
      this.devstarDomain = devstarDomainURL
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										530
									
								
								src/home.ts
									
									
									
									
									
								
							
							
						
						
									
										530
									
								
								src/home.ts
									
									
									
									
									
								
							@@ -1,246 +1,284 @@
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * 配置项提供devstarDomain 
 | 
			
		||||
   * @param context 
 | 
			
		||||
   * @param user 
 | 
			
		||||
   */
 | 
			
		||||
  constructor(context: vscode.ExtensionContext, user: User)
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * open with vscode链接提供devstarDomain
 | 
			
		||||
   * @param context 
 | 
			
		||||
   * @param user 
 | 
			
		||||
   * @param devstarDomain 
 | 
			
		||||
   */
 | 
			
		||||
  constructor(context: vscode.ExtensionContext, user: User, devstarDomain: string)
 | 
			
		||||
 | 
			
		||||
  constructor(context: vscode.ExtensionContext, user: User, devstarDomain?: string) {
 | 
			
		||||
    this.context = context;
 | 
			
		||||
    this.user = user;
 | 
			
		||||
    this.remoteContainer = new RemoteContainer(user);
 | 
			
		||||
 | 
			
		||||
    if (devstarDomain != undefined && devstarDomain != "") {
 | 
			
		||||
      this.devstarDomain = devstarDomain
 | 
			
		||||
    } else {
 | 
			
		||||
      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"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setUser(user: User) {
 | 
			
		||||
    this.user = user
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setRemoteContainer(remoteContainer: RemoteContainer) {
 | 
			
		||||
    this.remoteContainer = remoteContainer
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDevstarDomain(devstarDomain: string) {
 | 
			
		||||
    if (undefined == devstarDomain || "" == devstarDomain) {
 | 
			
		||||
      this.devstarDomain = devstarDomain
 | 
			
		||||
      this.devstarHomePageUrl = "https://devstar.cn/devstar-home"
 | 
			
		||||
    } else {
 | 
			
		||||
      this.devstarHomePageUrl = devstarDomain.endsWith('/') ? this.devstarDomain + "devstar-home" : 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>`
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										310
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										310
									
								
								src/main.ts
									
									
									
									
									
								
							@@ -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() {
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,204 +1,208 @@
 | 
			
		||||
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
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setUser(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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										462
									
								
								src/user.ts
									
									
									
									
									
								
							
							
						
						
									
										462
									
								
								src/user.ts
									
									
									
									
									
								
							@@ -1,214 +1,250 @@
 | 
			
		||||
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 devstarDomain: string;
 | 
			
		||||
  private devstarHostname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * devstarDomain由配置项提供
 | 
			
		||||
   * @param context 
 | 
			
		||||
   */
 | 
			
		||||
  constructor(context: vscode.ExtensionContext);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * devstarDomain由open with vscode链接提供
 | 
			
		||||
   * @param context 
 | 
			
		||||
   * @param devstarDomain 
 | 
			
		||||
   */
 | 
			
		||||
  constructor(context: vscode.ExtensionContext, devstarDomain: string);
 | 
			
		||||
 | 
			
		||||
  constructor(context: vscode.ExtensionContext, devstarDomain?: string) {
 | 
			
		||||
    this.context = context;
 | 
			
		||||
    this.username = this.context.globalState.get(this.usernameKey);
 | 
			
		||||
    this.userToken = this.context.globalState.get(this.userTokenKey);
 | 
			
		||||
 | 
			
		||||
    // 提取devstar domain的主域名,用于本地ssh key的命名
 | 
			
		||||
    if (devstarDomain != undefined && devstarDomain != "") {
 | 
			
		||||
      // open with vscode链接提供域名
 | 
			
		||||
      this.devstarDomain = devstarDomain
 | 
			
		||||
      let parsedUrl = new URL(devstarDomain);
 | 
			
		||||
      this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname,并用下划线替换.
 | 
			
		||||
    } else {
 | 
			
		||||
      // 用户配置项提供域名
 | 
			
		||||
      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;
 | 
			
		||||
      this.devstarDomain = devstarDomainURL
 | 
			
		||||
      let parsedUrl = new URL(devstarDomainURL);
 | 
			
		||||
      this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_'); //提取hostname,并用下划线替换.
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setDevstarDomainAndHostname(devstarDomain: string) {
 | 
			
		||||
    if (devstarDomain != "") {
 | 
			
		||||
      this.devstarDomain = devstarDomain
 | 
			
		||||
      const parsedUrl = new URL(devstarDomain)
 | 
			
		||||
      this.devstarHostname = parsedUrl.hostname.replace(/\./g, '_');
 | 
			
		||||
    } else {
 | 
			
		||||
      console.error(vscode.l10n.t("devstar domain is null"))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDevstarDomain(): string {
 | 
			
		||||
    return this.devstarDomain
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async login(token: string, username: string): Promise<string> {
 | 
			
		||||
    const devstarAPIHandler = new DevstarAPIHandler(this.devstarDomain)
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										268
									
								
								src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										268
									
								
								src/utils.ts
									
									
									
									
									
								
							@@ -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());
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1830
									
								
								test/home.html
									
									
									
									
									
								
							
							
						
						
									
										1830
									
								
								test/home.html
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -17,9 +17,6 @@ header("Allow: GET, POST, OPTIONS, PUT, DELETE");
 | 
			
		||||
  <iframe id="embedded-devstar" src="http://localhost:8080/test/home.html" width="100%" height="100%" frameborder="0"
 | 
			
		||||
    style="border: 0; left: 0; right: 0; bottom: 0; top: 0; position:absolute;">
 | 
			
		||||
  </iframe>
 | 
			
		||||
  <!-- <iframe id="embedded-devstar" src="http://localhost:8080/home.html" width="100%" height="100%" frameborder="0"
 | 
			
		||||
    style="border: 0; left: 0; right: 0; bottom: 0; top: 0; position:absolute;">
 | 
			
		||||
  </iframe> -->
 | 
			
		||||
 | 
			
		||||
  <!-- <iframe 
 | 
			
		||||
    id="embedded-devstar"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
	],
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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']
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user