init for deploy files

This commit is contained in:
熊潇 2024-10-10 00:43:50 +08:00
parent b2acf93806
commit ef21829ffe
23 changed files with 1519 additions and 6 deletions

38
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Publish to npm
on:
push:
tags:
- 'v*.*.*' # 当推送带有版本号的 tag 时触发,例如 v1.0.0
workflow_dispatch: # 添加手动触发器
jobs:
publish:
runs-on: ubuntu-latest
steps:
# Step 1: Clone current Git repository
- name: Checkout this repository
uses: actions/checkout@v3
# Step 3: Setup Node.js and install dependencies
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20.6'
registry-url: 'https://registry.npmjs.org/'
cache: 'npm' # 启用 npm 缓存,提高安装速度
- name: Configure npm authentication
run: npm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}
- name: Install dependencies
run: npm install
- name: Build project
run: npm run build
# Step 6: 发布到 npm
- name: Publish package
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Step 7: 发布成功后,更新版本标签
# - name: Create Git tag
# run: |
# TAG="v$(node -p -e "require('./package.json').version")"
# git tag $TAG
# git push origin $TAG

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
node_modules node_modules
.DS_Store .DS_Store
dist

1
.npmrc Normal file
View File

@ -0,0 +1 @@
@abearxiong:registry=https://npm.pkg.github.com

2
bin/envision.js Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env node
import '../dist/app.js';

3
docs/readme.md Normal file
View File

@ -0,0 +1,3 @@
# envision-cli
## 上传文件

View File

@ -7,16 +7,41 @@
"bin": { "bin": {
"envision": "./bin/envision" "envision": "./bin/envision"
}, },
"files": [
"dist",
"bin"
],
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "dev": "tsx src/index.ts ",
"build": "rimraf dist && rollup -c",
"b": "./bin/envision.js"
}, },
"keywords": ["kevisual","cli"], "keywords": [
"kevisual",
"cli"
],
"author": "abearxiong", "author": "abearxiong",
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^28.0.0",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/plugin-typescript": "^12.1.0",
"@types/node": "^22.5.5", "@types/node": "^22.5.5",
"commander": "^12.1.0" "commander": "^12.1.0",
"rollup": "^4.24.0",
"tslib": "^2.7.0",
"typescript": "^5.6.3",
"@kevisual/query": "0.0.7-alpha.1",
"chalk": "^5.3.0",
"form-data": "^4.0.0",
"glob": "^11.0.0",
"inquirer": "^12.0.0"
}, },
"dependencies": { "dependencies": {},
"chalk": "^5.3.0" "resolutions": {
"picomatch": "^4"
},
"engines": {
"node": ">=18.0.0"
} }
} }

1016
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
readme.md Normal file
View File

@ -0,0 +1,4 @@
# Cli For Envision
## upload files

27
rollup.config.js Normal file
View File

@ -0,0 +1,27 @@
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
/**
* @type {import('rollup').RollupOptions}
*/
export default {
input: 'src/index.ts', // TypeScript 入口文件
output: {
file: 'dist/app.js', // 输出文件
format: 'es', // 输出格式设置为 ES 模块
},
plugins: [
resolve(), // 使用 @rollup/plugin-node-resolve 解析 node_modules 中的模块
commonjs(),
json(),
typescript({
allowImportingTsExtensions: true,
noEmit: true,
// 不生成声明文件
declaration: false,
}), // 使用 @rollup/plugin-typescript 处理 TypeScript 文件
],
};

16
src/app.ts Normal file
View File

@ -0,0 +1,16 @@
import { program , Command} from 'commander';
import fs from 'fs';
// 将多个子命令加入主程序中
program.name('app').description('A CLI tool with envison').version('0.0.1');
const ls =new Command('ls')
.description('List files in the current directory')
.action(() => {
console.log('List files');
console.log(fs.readdirSync(process.cwd()));
});
program.addCommand(ls);
export const app = program;
export { program, Command };

118
src/command/deploy.ts Normal file
View File

@ -0,0 +1,118 @@
import { app, Command } from '@/app.ts';
import { glob } from 'glob';
import path from 'path';
import fs from 'fs';
import FormData from 'form-data';
import { baseURL } from '@/module/query.ts';
import { getConfig } from '@/module/index.ts';
import inquirer from 'inquirer';
const command = new Command('deploy')
.description('deploy to server')
.argument('<filePath>', 'Path to the file to be uploaded') // 定义文件路径参数
.option('-v, --version <version>', 'verbose')
.option('-k, --key <key>', 'key')
.action(async (filePath, options) => {
try {
let { version, key } = options;
if (!version || !key) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'version',
message: 'Enter your version:',
when: () => !version,
},
{
type: 'input',
name: 'key',
message: 'Enter your key:',
when: () => !key,
},
]);
version = answers.version || version;
key = answers.key || key;
}
const pwd = process.cwd();
const directory = path.join(pwd, filePath);
const gPath = path.join(directory, '**/*');
const files = await glob(gPath, { cwd: pwd, ignore: ['node_modules/**/*'] });
const _relativeFiles = files.map((file) => file.replace(directory + '/', ''));
console.log('upload Files', _relativeFiles);
console.log('upload Files Key', key, version);
// 确认是否上传
const confirm = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Do you want to upload these files?',
},
]);
if (!confirm.confirm) {
return;
}
const res = await uploadFiles(_relativeFiles, directory, { key, version });
if (res?.code === 200) {
console.log('File uploaded successfully!');
res.data?.data?.files?.map?.((d) => {
console.log('uploaded file', d?.name, d?.path);
});
} else {
console.error('File upload failed', res?.message);
}
} catch (error) {
console.error('error', error);
}
});
const uploadFiles = async (files: string[], directory: string, { key, version }: { key: string; version: string }): Promise<any> => {
const config = await getConfig();
const form = new FormData();
for (const file of files) {
const filePath = path.join(directory, file);
form.append('file', fs.createReadStream(filePath), {
filename: file,
});
}
form.append('appKey', key);
form.append('version', version);
return new Promise((resolve) => {
const url = new URL(baseURL);
console.log('upload url', url.hostname, url.protocol, url.port);
form.submit(
{
path: '/api/app/upload',
host: url.hostname,
protocol: url.protocol as any,
port: url.port,
method: 'POST',
headers: {
Authorization: 'Bearer ' + config.token,
...form.getHeaders(),
},
},
(err, res) => {
if (err) {
console.error('Error uploading file:', err.message);
return;
}
// 处理服务器响应
let body = '';
res.on('data', (chunk) => {
body += chunk;
});
res.on('end', () => {
try {
const res = JSON.parse(body);
resolve(res);
} catch (e) {
resolve({ code: 500, message: body });
}
});
},
);
});
};
app.addCommand(command);

59
src/command/login.ts Normal file
View File

@ -0,0 +1,59 @@
import { app, program, Command } from '@/app.ts';
import { getConfig, writeConfig } from '@/module/get-config.ts';
import { queryLogin, queryMe } from '@/route/index.ts';
import inquirer from 'inquirer';
// 导入 login 命令
// 定义login命令支持 `-u` 和 `-p` 参数来输入用户名和密码
const loginCommand = new Command('login')
.description('Login to the application')
.option('-u, --username <username>', 'Specify username')
.option('-p, --password <password>', 'Specify password')
.action(async (options) => {
const config = getConfig();
let { username, password } = options;
// 如果没有传递参数,则通过交互式输入
if (!username || !password) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Enter your username:',
when: () => !username, // 当 username 为空时,提示用户输入
},
{
type: 'password',
name: 'password',
message: 'Enter your password:',
mask: '*', // 隐藏输入的字符
when: () => !password, // 当 password 为空时,提示用户输入
},
]);
username = answers.username || username;
password = answers.password || password;
}
if (config.token) {
const res = await queryMe();
if (res.code === 200) {
const data = res.data;
if (data.username === username) {
console.log('Already Login For', data.username);
return;
}
}
}
const res = await queryLogin(username, password);
if (res.code === 200) {
console.log('登录成功');
const { token } = res.data;
writeConfig({ ...config, token });
} else {
console.log('登录失败', res.message || '');
}
console.log('u', username, password);
});
app.addCommand(loginCommand);

11
src/command/logout.ts Normal file
View File

@ -0,0 +1,11 @@
import { app, Command } from '@/app.ts';
import { getConfig, writeConfig } from '@/module/index.ts';
const command = new Command('logout')
.description('')
.action(async () => {
const config = getConfig();
writeConfig({ ...config, token: '' });
});
app.addCommand(command);

37
src/command/ls-token.ts Normal file
View File

@ -0,0 +1,37 @@
import { app, Command } from '@/app.ts';
import { getConfig, query, writeConfig } from '@/module/index.ts';
import inquirer from 'inquirer';
const token = new Command('token').description('show token').action(async () => {
const config = getConfig();
console.log('token', config.token);
});
app.addCommand(token);
const baseURL = new Command('baseURL').description('show baseURL').action(async () => {
const config = getConfig();
console.log('baseURL', config.baseURL);
});
app.addCommand(baseURL);
const setBaseURL = new Command('setBaseURL').description('set baseURL').action(async () => {
const config = getConfig();
const answers = await inquirer.prompt([
{
type: 'input',
name: 'baseURL',
message: `Enter your baseURL:(current: ${config.baseURL})`,
},
]);
const baseURL = answers.baseURL;
writeConfig({ ...config, baseURL });
});
app.addCommand(setBaseURL);
// const showQueryURL = new Command('showQueryURL').description('show query URL').action(async () => {
// console.log("url", query.url);
// });
// app.addCommand(showQueryURL);

19
src/command/me.ts Normal file
View File

@ -0,0 +1,19 @@
import { app, Command } from '@/app.ts';
import { getConfig, writeConfig } from '@/module/index.ts';
import {queryMe} from '../route/index.ts';
const command = new Command('me')
.description('')
.action(async () => {
const config = getConfig()
const res = await queryMe();
if(res.code===200) {
console.log('me', res.data)
} else {
console.log('not login')
writeConfig({ ...config, token: '' });
}
});
app.addCommand(command);

9
src/index.ts Normal file
View File

@ -0,0 +1,9 @@
import { app } from '@/app.ts';
import './command/login.ts';
import './command/logout.ts';
import './command/ls-token.ts';
import './command/me.ts';
import './command/deploy.ts';
app.parse(process.argv);

33
src/module/get-config.ts Normal file
View File

@ -0,0 +1,33 @@
import os from 'os';
import path from 'path';
import fs from 'fs';
const envisionPath = path.join(os.homedir(), '.config', 'envision');
const configPath = path.join(os.homedir(), '.config', 'envision', 'config.json');
export const checkFileExists = (filePath: string) => {
try {
fs.accessSync(filePath, fs.constants.F_OK);
return true;
} catch (error) {
return false;
}
};
export const getConfig = () => {
if (!checkFileExists(envisionPath)) {
fs.mkdirSync(envisionPath, { recursive: true });
}
if (checkFileExists(configPath)) {
const config = fs.readFileSync(configPath, 'utf-8');
try {
return JSON.parse(config);
} catch (e) {
return {};
}
}
return {};
};
export const writeConfig = (config: Record<string, any>) => {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
};

3
src/module/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './query.ts';
export * from './get-config.ts';

18
src/module/query.ts Normal file
View File

@ -0,0 +1,18 @@
import { Query } from '@kevisual/query';
import { getConfig } from './get-config.ts';
const config = getConfig();
export const baseURL = config?.baseURL || 'https://envision.xiongxiao.me';
export const query = new Query({
url: `${baseURL}/api/router`,
});
query.beforeRequest = async (config) => {
if (config.headers) {
const token = await getConfig()?.token;
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
}
return config;
};

1
src/route/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './user/login.ts';

19
src/route/user/login.ts Normal file
View File

@ -0,0 +1,19 @@
import { query } from '@/module/query.ts';
export const queryLogin = async (username: string, password: string) => {
return await query.post({
path: 'user',
key: 'login',
payload: {
username,
password,
},
});
};
export const queryMe = async () => {
return await query.post({
path: 'user',
key: 'me',
});
};

15
src/scripts/login.ts Normal file
View File

@ -0,0 +1,15 @@
import { query } from '@/module/query.ts';
export const login = async () => {
const res = await query.post({
path: 'user',
key: 'login',
payload: {
username: 'demo',
password: '123456',
},
});
console.log(res);
};
login();

37
tsconfig.json Normal file
View File

@ -0,0 +1,37 @@
{
"compilerOptions": {
"module": "nodenext",
"target": "esnext",
"noImplicitAny": false,
"outDir": "./dist",
"sourceMap": false,
"allowJs": true,
"newLine": "LF",
"baseUrl": "./",
"typeRoots": [
"node_modules/@types",
],
"declaration": true,
"noEmit": false,
"allowImportingTsExtensions": true,
"emitDeclarationOnly": true,
"moduleResolution": "NodeNext",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"paths": {
"@/*": [
"src/*"
],
}
},
"include": [
"typings.d.ts",
"src/**/*.ts",
"test/**/*.ts"
],
"exclude": [
"node_modules",
"dist",
],
}