From 9a14fc695539918915a0ba7975e07066b9e61028 Mon Sep 17 00:00:00 2001 From: dingzhenznen <1324652616@qq.com> Date: Mon, 4 Jul 2022 11:32:28 +0800 Subject: [PATCH] feat(cli): add oss/server command (#154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add login to index.ts * add remarks * add remote server * add remote server * add remote server * add response check * fix init function * fix init function * fix command * add server api * add command to index * add oss command Co-authored-by: 丁振振 --- .../cli/src/actions/{appAction.ts => app.ts} | 0 packages/cli/src/actions/function.ts | 151 +++++++++++++ packages/cli/src/actions/init.ts | 53 +++++ packages/cli/src/actions/oss.ts | 213 ++++++++++++++++++ .../src/actions/{userAction.ts => user.ts} | 0 packages/cli/src/api/functions.ts | 60 ++--- packages/cli/src/api/oss.ts | 21 ++ packages/cli/src/api/request.ts | 116 +++++----- packages/cli/src/api/user.ts | 2 +- packages/cli/src/functions.ts | 164 +++++++------- packages/cli/src/index.ts | 176 ++++++++------- packages/cli/src/oss.ts | 48 ++++ packages/cli/src/utils/tokens.ts | 6 +- packages/cli/src/utils/util.ts | 102 ++++++--- .../src/handler/function/index.ts | 16 +- 15 files changed, 834 insertions(+), 294 deletions(-) rename packages/cli/src/actions/{appAction.ts => app.ts} (100%) create mode 100644 packages/cli/src/actions/function.ts create mode 100644 packages/cli/src/actions/init.ts create mode 100644 packages/cli/src/actions/oss.ts rename packages/cli/src/actions/{userAction.ts => user.ts} (100%) create mode 100644 packages/cli/src/api/oss.ts create mode 100644 packages/cli/src/oss.ts diff --git a/packages/cli/src/actions/appAction.ts b/packages/cli/src/actions/app.ts similarity index 100% rename from packages/cli/src/actions/appAction.ts rename to packages/cli/src/actions/app.ts diff --git a/packages/cli/src/actions/function.ts b/packages/cli/src/actions/function.ts new file mode 100644 index 0000000000..e3863cd469 --- /dev/null +++ b/packages/cli/src/actions/function.ts @@ -0,0 +1,151 @@ + +import * as path from 'node:path' +import * as fs from 'node:fs' +import { compileTs2js } from '../utils/util-lang' + +import { FUNCTIONS_DIR ,FUNCTIONS_FILE} from '../utils/constants' +import { checkDir} from '../utils/util' + +import { debugFunction,getFunctionByName,pushFunction,createFunction} from '../api/functions' + + + +/** + * pull function + * @param {any} data + * @param {any} options + * @returns + */ + +export async function handlePullFunctionCommand(data:any,options:any) { + + // functions dir + const functionsDir = path.resolve(process.cwd(), FUNCTIONS_DIR) + + checkDir(functionsDir) + + data.forEach(element => { + + //fuction name + const funcName =element.name; + const funcNameDir = path.resolve(functionsDir, funcName) + + checkDir(funcNameDir) + + const funcFile= path.resolve(funcNameDir, FUNCTIONS_FILE) + try{ + // check if exist function file + fs.accessSync(funcFile) + const currentCode =fs.readFileSync(funcFile,'utf-8') + + if(currentCode){ + // forceOverwrite + if(options.forceOverwrite){ + fs.writeFileSync(funcFile, element.code) + } + }else{ + fs.writeFileSync(funcFile, element.code) + } + }catch(err){ + + fs.writeFileSync(funcFile, element.code) + + } + + console.log('pull success') + }) + + +} + +/** + * invoke function + * @param {string} appid + * @param {string} functionName + * @param {object} param + * @returns + */ + +export async function handleInvokeFunctionCommand(appid:string,functionName:string,param:object) { + + const functionsDir = path.resolve(process.cwd(), FUNCTIONS_DIR) + + // get local code + const functionNameDir = path.resolve(functionsDir, functionName) + const funcFile= path.resolve(functionNameDir, FUNCTIONS_FILE) + const code = fs.readFileSync(funcFile, 'utf8') + const obj = { + func:{ + appid: appid, + code: code, + name:functionName, + compiledCode: compileTs2js(code), + debugParams: JSON.stringify(param), + }, + param:param + } + + const res = await debugFunction(functionName,obj) + console.log(res) + +} + + +/** + * push fuction + * @param {string} appid + * @param {string} functionName + * @param {any} options + * @returns + */ + + +export async function handlePushFunctionCommand(appid:string,functionName:string,options:any) { + + const functionsDir = path.resolve(process.cwd(), FUNCTIONS_DIR) + + // get local code + const functionNameDir = path.resolve(functionsDir, functionName) + const funcFile= path.resolve(functionNameDir, FUNCTIONS_FILE) + const code = fs.readFileSync(funcFile, 'utf8') + + // get function + const record = await getFunctionByName(appid,functionName) + + //update function + if(record.data){ + if(record.data.code!==code){ + + if(options.forceOverwrite){ + const data = { + code:code, + debugParams:JSON.stringify({"code":"laf"}), + } + const res = await pushFunction(appid,functionName,data) + if(res.data){ + console.log("push success") + } + }else{ + + console.log("romote code is different with local") + } + }else{ + console.log("romote code is same with local") + } + + }else{ + // create function + const data = { + code:code, + name:functionName, + label:"test", + status:1 + } + + const res = await createFunction(appid,data) + if(res.data){ + console.log("push success") + } + } + +} \ No newline at end of file diff --git a/packages/cli/src/actions/init.ts b/packages/cli/src/actions/init.ts new file mode 100644 index 0000000000..9537fd366e --- /dev/null +++ b/packages/cli/src/actions/init.ts @@ -0,0 +1,53 @@ +import * as fs from 'node:fs' +import * as path from 'node:path' +import { LAF_FILE } from '../utils/constants' +import { checkDir } from '../utils/util' + +import * as AdmZip from 'adm-zip' +import { pipeline } from 'node:stream/promises' + + +/** + * init app + * @param {string} appName + * @param {string} appid + * @param {string} endPoint + * @param {string} ossEndpoint + * @returns + */ + +export async function handleInitAppCommand(appName:string,appid:string,endPoint:string,ossEndpoint) { + + const appPath = path.resolve(process.cwd(), appName) + checkDir(appPath) + const lafFile = path.resolve(appPath, LAF_FILE) + // write data + fs.writeFileSync(lafFile, JSON.stringify({ appid: appid, root: appPath ,endPoint,ossEndpoint})) + +} + + +/** + * sync app + * @param {string} appName + * @param {any} data + * @returns + */ + +export async function handleSyncAppCommand(appName:string,data:any) { + + const appPath = path.resolve(process.cwd(), appName) + + const appZip = appName + '.zip' + const appZipPath = path.resolve(process.cwd(), appZip) + const writer = fs.createWriteStream(appZipPath) + await pipeline(data.data,writer); + + // unzip + const file = new AdmZip(appZipPath) + file.extractAllTo(appPath) + + fs.unlinkSync(appZipPath) + console.log('success') + +} \ No newline at end of file diff --git a/packages/cli/src/actions/oss.ts b/packages/cli/src/actions/oss.ts new file mode 100644 index 0000000000..df59860024 --- /dev/null +++ b/packages/cli/src/actions/oss.ts @@ -0,0 +1,213 @@ +import * as fs from 'node:fs' +import * as path from 'node:path' +import { createHash } from 'node:crypto' +import * as mime from 'mime' +import { checkDir, getS3Client } from '../utils/util' +import axios from 'axios' +import { pipeline } from 'node:stream/promises' + +/** + * push files + * @param {object} credentials + * @param {object} options + * @returns + */ +export async function handlePushCommand(credentials:any,options:any) { + const s3 = getS3Client(options.endpoint,credentials) + const source = options.source + + // get bucket objects + const res = await s3.listObjectsV2({ Bucket: options.bucketName, Delimiter: '' }).promise() + + // console.log(res.Contents) + const bucketObjects = res.Contents || [] + + // get source files + const abs_source = path.resolve(source) + checkDir(abs_source,false) + const sourceFiles = readdirRecursive(source).map(file => { + return { + key: path.relative(abs_source, file), + abs_path: path.resolve(file), + } + }) + + // get updated files + let updatedFiles = sourceFiles + if(!options.force) { + updatedFiles = getUpdatedFiles(sourceFiles, bucketObjects) + } + + console.log(`${updatedFiles.length} files need to be updated`) + + for (const file of updatedFiles) { + console.log(`uploading ${file.key} with: ${mime.getType(file.key)}`) + if (!options.dryRun) { + await s3.putObject({ + Bucket: options.bucketName, + Key: file.key, + Body: fs.readFileSync(path.resolve(source, file.abs_path)), + ContentType: mime.getType(file.key) + }).promise() + + console.log(path.resolve(source, file.abs_path)) + } + } + + // get deleted files // force 才delete + const deletedFiles = getDeletedFiles(sourceFiles, bucketObjects) + if(deletedFiles?.length > 0) { + console.log(`${deletedFiles.length} files need to be deleted`) + for (const obj of deletedFiles) { + console.log(`deleting ${obj.Key}`) + if (!options.dryRun) { + await s3.deleteObject({ Bucket: options.bucketName, Key: obj.Key }).promise() + } + } + } +} + + +/** + * pull files + * @param {object} credentials + * @param {object} options + * @returns + */ + +export async function handlePullCommand(credentials:any,options:any) { + + const s3 = getS3Client(options.endpoint,credentials) + + const outPath = options.outPath + + // get bucket objects + const res = await s3.listObjectsV2({ Bucket: options.bucketName, Delimiter: '' }).promise() + const bucketObjects = res.Contents || [] + + // get local files + const abs_source = path.resolve(outPath) + checkDir(abs_source) + const localFiles = readdirRecursive(outPath) + .map(file => { + return { + key: path.relative(abs_source, file), + abs_path: path.resolve(file), + } + }) + + let downFiles = bucketObjects + + + if(!options.force) { + downFiles = getDownFiles(localFiles, bucketObjects) + } + + + downFiles.forEach( async item => { + + const fileurl = s3.getSignedUrl('getObject', { Bucket: options.bucketName, Key: item.Key }) + const index = item.Key.lastIndexOf("/") + + if(index >0 ){ + const newDir = item.Key.substring(0,index) + const newPath = path.resolve(abs_source,newDir) + checkDir(newPath) + } + + const data = await axios({url: fileurl,method: 'GET',responseType: 'stream'}) + + const filepath = path.resolve(abs_source,item.Key) + + const writer = fs.createWriteStream(filepath) + + await pipeline(data.data,writer) + }) + + // get deleted files // force 才delete + const deletedFiles = getLocalDeletedFiles(localFiles, bucketObjects) + if(deletedFiles?.length > 0) { + console.log(`${deletedFiles.length} files need to be deleted`) + for (const obj of deletedFiles) { + console.log(`deleting ${obj.key}`) + fs.unlinkSync(obj.abs_path) + } + } + +} + + + +// readdir recursive +function readdirRecursive(dir: string) { + const files = fs.readdirSync(dir) + const result = [] + for (const file of files) { + const filepath = path.join(dir, file) + const stats = fs.statSync(filepath) + if (stats.isDirectory()) { + result.push(...readdirRecursive(filepath)) + } else { + result.push(filepath) + } + } + return result +} + +// get deleted files which are not in source files +function getDeletedFiles(sourceFiles: { key: string, abs_path: string }[], bucketObjects: any[]) { + const deletedFiles = bucketObjects.filter(bucketObject => { + const key = bucketObject.Key + return !sourceFiles.find(sourceFile => sourceFile.key === key) + }) + return deletedFiles +} + + +// get deleted files which are not in bucketObjects +function getLocalDeletedFiles(sourceFiles:any,bucketObjects:any){ + + const deletedFiles = sourceFiles.filter(sourceFile => { + const key = sourceFile.key + return !bucketObjects.find(bucketObject => bucketObject.Key === key) + }) + return deletedFiles + +} + +// get updated files +function getUpdatedFiles(sourceFiles: { key: string, abs_path: string }[], bucketObjects: any[]) { + const updatedFiles = sourceFiles.filter(sourceFile => { + const bucketObject = bucketObjects.find(bucketObject => bucketObject.Key === sourceFile.key) + if (!bucketObject) { + return true + } + return !compareFileMd5(sourceFile.abs_path, bucketObject) + }) + + return updatedFiles +} + +// get down files +function getDownFiles(sourceFiles:any,bucketObjects:any){ + + const downFiles = bucketObjects.filter(bucketObject => { + const sourceFile = sourceFiles.find(sourceFile => bucketObject.Key === sourceFile.key) + if (!sourceFile) { + return true + } + return !compareFileMd5(sourceFile.abs_path, bucketObject) + }) + + return downFiles + +} + +// compare file md5 +function compareFileMd5(sourceFile: string, bucketObject: any) { + + const source_data = fs.readFileSync(sourceFile) + const source_file_md5 = createHash('md5').update(source_data).digest('hex') + const etag = bucketObject.ETag.replace(/\"/g, '') + return source_file_md5 === etag +} \ No newline at end of file diff --git a/packages/cli/src/actions/userAction.ts b/packages/cli/src/actions/user.ts similarity index 100% rename from packages/cli/src/actions/userAction.ts rename to packages/cli/src/actions/user.ts diff --git a/packages/cli/src/api/functions.ts b/packages/cli/src/api/functions.ts index 8c78055b37..53b23f1a77 100644 --- a/packages/cli/src/api/functions.ts +++ b/packages/cli/src/api/functions.ts @@ -11,13 +11,13 @@ import axios from 'axios' * @returns */ -export async function pullFunction(appid: string,functionName:string) { +export async function pullFunction(appid: string, functionName: string) { let url = '' - if(functionName){ + if (functionName) { url = `/sys-api/apps/${appid}/function?status=1&page=1&limit=100&keyword=${functionName}` - }else{ + } else { url = `/sys-api/apps/${appid}/function?status=1&page=1&limit=100` } @@ -32,35 +32,37 @@ export async function pullFunction(appid: string,functionName:string) { /** * 调试函数 - * @param {string} appid * @param {string} functionName * @param {Object} obj * @returns */ -export async function debugFunction(appid: string,functionName:string,obj:Object) { +export async function debugFunction(functionName: string, obj: Object) { - const appData = getAppData(); + const appData = getAppData() - const url = `http://${appid}.${appData.endPoint}/debug/${functionName}` + const url = `${appData.endPoint}/debug/${functionName}` - try{ + try { const debug_token = await getDebugToken() - const headers ={"authorization":`Bearer ${debug_token}`} - - const result = await axios.post(url,obj,{headers:headers}) + const headers = { "authorization": `Bearer ${debug_token}` } - const response = result.data + const result = await axios.post(url, obj, { headers: headers }) + const response = result.data + if (response.error) { + console.error(response.error) + process.exit(1) + } return response - }catch(err){ - + } catch (err) { + console.error(err.message) process.exit(1) } - + } /** @@ -70,10 +72,10 @@ export async function debugFunction(appid: string,functionName:string,obj:Object * @returns */ -export async function createFunction(appid: string,data:Object) { +export async function createFunction(appid: string, data: Object) { - const url= `/sys-api/apps/${appid}/function/create` + const url = `/sys-api/apps/${appid}/function/create` const obj = { method: "POST", @@ -93,14 +95,14 @@ export async function createFunction(appid: string,data:Object) { * @returns */ -export async function getFunctionByName(appid: string,functionName:string) { - const url= `/sys-api/apps/${appid}/function/getFunction/${functionName}` +export async function getFunctionByName(appid: string, functionName: string) { + const url = `/sys-api/apps/${appid}/function/detail/${functionName}` const obj = { method: "GET", url, } - const result = await requestData(obj) - return result.data + const result = await requestData(obj) + return result.data } /** @@ -112,8 +114,8 @@ export async function getFunctionByName(appid: string,functionName:string) { */ -export async function pushFunction(appid: string,functionName:string,data:object) { - const url= `/sys-api/apps/${appid}/function/updateFunction/${functionName}` +export async function pushFunction(appid: string, functionName: string, data: object) { + const url = `/sys-api/apps/${appid}/function/save/${functionName}` const obj = { method: "POST", @@ -121,8 +123,8 @@ export async function pushFunction(appid: string,functionName:string,data:object data } - const result = await requestData(obj) - return result.data + const result = await requestData(obj) + return result.data } @@ -133,13 +135,13 @@ export async function pushFunction(appid: string,functionName:string,data:object * @returns */ -export async function publishFunction(appid: string,functionName:string) { - const url= `/sys-api/apps/${appid}/function/publishFunction/${functionName}` +export async function publishFunction(appid: string, functionName: string) { + const url = `/sys-api/apps/${appid}/function/publish/${functionName}` const obj = { method: "POST", url } - const result = await requestData(obj) - return result.data + const result = await requestData(obj) + return result.data } \ No newline at end of file diff --git a/packages/cli/src/api/oss.ts b/packages/cli/src/api/oss.ts new file mode 100644 index 0000000000..779d07e53d --- /dev/null +++ b/packages/cli/src/api/oss.ts @@ -0,0 +1,21 @@ +import { requestData } from "./request" + + +/** + * 根据 appid 同步数据 + * @param {string} appid + * @param {string} bucketName + * @returns + */ + +export async function detail(appid: string,bucketName:string) { + + const url = `/sys-api/apps/${appid}/oss/buckets/${bucketName}` + const obj = { + method: "GET", + url, + } + + const result = await requestData(obj) + return result.data +} diff --git a/packages/cli/src/api/request.ts b/packages/cli/src/api/request.ts index 504e5c0c6c..4251b78d40 100644 --- a/packages/cli/src/api/request.ts +++ b/packages/cli/src/api/request.ts @@ -6,74 +6,72 @@ import { getAccessToken } from '../utils/tokens' export const request = axios.create({ - // set baseURL - baseURL: getRemoteServe() + // set baseURL + baseURL: getRemoteServe() }) // http request request.interceptors.request.use( - async (config) => { - - const token = await getAccessToken() - if (token) { - config.headers.Authorization = `Bearer ${token}` - } else { - console.error("please login first") - process.exit(1) - } - return config - }, - (error) => { - // 错误抛到业务代码 - error.data = {} - error.data.msg = '服务器异常,请联系管理员!' - return Promise.resolve(error) - }, + async (config) => { + + const token = await getAccessToken() + if (token) { + config.headers.Authorization = `Bearer ${token}` + } else { + console.error("please login first") + process.exit(1) + } + return config + }, + (error) => { + // 错误抛到业务代码 + error.data = {} + error.data.msg = '服务器异常,请联系管理员!' + return Promise.resolve(error) + }, ) - request.interceptors.response.use( - /** - * If you want to get http information such as headers or status - * Please return response => response - */ - - /** - * Determine the request status by custom code - * Here is just an example - * You can also judge the status by HTTP Status Code - */ - response => { - return response - }, - error => { - const status = error.response.status - - if (status === 401) { - - console.error(error.response.data) - - process.exit(1) - - } - if (status === 403) { - - console.error(error.response.data) - - process.exit(1) - } - if (status === 422) { - - console.error(error.response.data) - - process.exit(1) - } - - // showError(error.message) - return Promise.reject(error) +request.interceptors.response.use( + /** + * If you want to get http information such as headers or status + * Please return response => response + */ + + /** + * Determine the request status by custom code + * Here is just an example + * You can also judge the status by HTTP Status Code + */ + response => { + return response + }, + error => { + const status = error.response.status + + if (status === 401) { + console.error(error.response.data) + process.exit(1) + + } + if (status === 403) { + console.error(error.response.data) + process.exit(1) + } + if (status === 404) { + console.error(error.response.data) + process.exit(1) } - ) + if (status === 422) { + console.error(error.response.data) + process.exit(1) + } + + // showError(error.message) + return Promise.reject(error) + } +) diff --git a/packages/cli/src/api/user.ts b/packages/cli/src/api/user.ts index ad7414a48c..d51c51b595 100644 --- a/packages/cli/src/api/user.ts +++ b/packages/cli/src/api/user.ts @@ -21,7 +21,7 @@ export async function loginApi(server:string,obj:Object) { return false } - return {access_token:response.data.access_token,expire_time:response.data.expire} + return { access_token:response.data.access_token,expire_time:response.data.expire } }catch(err){ diff --git a/packages/cli/src/functions.ts b/packages/cli/src/functions.ts index c0d7fbf640..4212eefbc4 100644 --- a/packages/cli/src/functions.ts +++ b/packages/cli/src/functions.ts @@ -1,79 +1,89 @@ -import { program } from 'commander' -import { pullFunction ,getFunctionByName ,publishFunction } from './api/functions' -import { getAppData,checkFuncNameDir } from './utils/util' -import { handlePullFunctionCommand,handleInvokeFunctionCommand ,handlePushFunctionCommand} from './actions/functionAction' - -const appData = getAppData() - -program -.command('fn-pull') -.argument('[function-name]',"functionname") -.option('-f, --force-overwrite', 'force to file ignore if modified', false) -.action(async ( functionName,options) => { - // pull function - const response =await pullFunction(appData.appid,functionName) - if(response.total){ - await handlePullFunctionCommand(response.data,options) - }else{ - console.log('functions not find') - } -}) - -program -.command('fn-invoke') -.argument('function-name',"functionname") -.argument('[param]','function param','{}') -.action(async ( functionName,param) => { - - try{ - const debugParams= JSON.parse(param) - // check function - checkFuncNameDir(functionName) - await handleInvokeFunctionCommand(appData.appid,functionName,debugParams) - - }catch(err){ - console.error(err.message) - process.exit(1) - - } - -}) - - -program -.command('fn-push') -.argument('function-name',"functionname") -.option('-f, --force-overwrite', 'force to file ignore if modified', false) -.action(async ( functionName,options) => { - // check fucntion - checkFuncNameDir(functionName) - - await handlePushFunctionCommand(appData.appid,functionName,options) - -}) - - -program -.command('fn-publish') -.argument('function-name',"functionname") -.action(async ( functionName) => { - - // get function - const record = await getFunctionByName(appData.appid,functionName) - - if(record.data){ - // publish function - const res = await publishFunction(appData.appid,functionName) - if(res.code==0){ - console.log('publish success') - } - }else{ - console.log('funtion not exist') - } - -}) - - -program.parse(process.argv); +import { Command } from 'commander' +import { pullFunction, getFunctionByName, publishFunction } from './api/functions' +import { getAppData, checkFuncNameDir } from './utils/util' +import { handlePullFunctionCommand, handleInvokeFunctionCommand, handlePushFunctionCommand } from './actions/function' + +export function makeFnCommand() { + + const fn = new Command('fn') + + fn + .command('pull') + .argument('[function-name]', "functionname") + .option('-f, --force-overwrite', 'force to file ignore if modified', false) + .action(async (functionName, options) => { + + const appData = getAppData() + // pull function + const response = await pullFunction(appData.appid, functionName) + if (response.total) { + await handlePullFunctionCommand(response.data, options) + } else { + console.log('functions not find') + } + }) + + fn + .command('invoke') + .argument('function-name', "functionname") + .argument('[param]', 'function param', '{}') + .action(async (functionName, param) => { + const appData = getAppData() + + try { + const debugParams = JSON.parse(param) + // check function + checkFuncNameDir(functionName) + + await handleInvokeFunctionCommand(appData.appid, functionName, debugParams) + + } catch (err) { + console.error(err.message) + process.exit(1) + + } + + }) + + + fn + .command('push') + .argument('function-name', "functionname") + .option('-f, --force-overwrite', 'force to file ignore if modified', false) + .action(async (functionName, options) => { + const appData = getAppData() + // check fucntion + checkFuncNameDir(functionName) + + await handlePushFunctionCommand(appData.appid, functionName, options) + + }) + + + fn + .command('publish') + .argument('function-name', "functionname") + .action(async (functionName) => { + + const appData = getAppData() + + // get function + const record = await getFunctionByName(appData.appid, functionName) + + if (record.data) { + // publish function + const res = await publishFunction(appData.appid, functionName) + if (res.code == 0) { + console.log('publish success') + } + } else { + console.log('funtion not exist') + } + + }) + + return fn + +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a93c881312..37f5ad22e4 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,87 +1,14 @@ #!/usr/bin/env node import { program } from 'commander' -import * as dotenv from 'dotenv' -import { resolve } from 'node:path' -import { handleSyncCommand } from './sync' -import { handleLoginCommand } from './actions/userAction' -import * as fs from 'node:fs' - -// laf-cli sync -program - .command('sync ') - .description(`sync files to bucket`) - .option('-e, --endpoint ', 'oss endpoint, default is https://oss.lafyun.com, could be override by env OSS_ENDPOINT') - .option('-k, --access-key ', 'app oss access-key, default is env OSS_ACCESS_KEY') - .option('-s, --access-secret ', 'app oss access-secret, default is env OSS_ACCESS_SECRET') - .option('-b, --bucket-name ', 'bucket-name, default is env OSS_BUCKET_NAME') - .option('-r, --region ', 'region', 'cn-default') - .option('-d, --dry-run', 'dry-run mode', false) - .option('-f, --force', 'force to updated all files ignore if modified', false) - .option('--env ', `your can specify a env file`, '.env') - .action(async (source, options) => { - dotenv.config({ path: resolve(process.cwd(), options.env) }) - - const endpoint = options.endpoint || process.env.OSS_ENDPOINT || 'https://oss.lafyun.com' - const accessKey = options.accessKey || process.env.OSS_ACCESS_KEY - const accessSecret = options.accessSecret || process.env.OSS_ACCESS_SECRET - const bucketName = options.bucketName || process.env.OSS_BUCKET_NAME - const region = options.region || process.env.OSS_REGION - const dryRun = options.dryRun || false - const force = options.force || false - - if(!endpoint) { - console.error('endpoint is required') - process.exit(1) - } - - if(!accessKey) { - console.error('accessKey is required') - process.exit(1) - } - - if(!accessSecret) { - console.error('accessSecret is required') - process.exit(1) - } - - if(!bucketName) { - console.error('bucketName is required') - process.exit(1) - } - - await handleSyncCommand(source, { endpoint, accessKey, accessSecret, bucketName, dryRun, force, region }) - }) - -program - .command('init') - .description('generate or update .env file') - .option('-e, --endpoint ', 'oss endpoint, default is https://oss.lafyun.com, could be override by env OSS_ENDPOINT') - .option('-k, --access-key ', 'app oss access-key, default is env OSS_ACCESS_KEY') - .option('-s, --access-secret ', 'app oss access-secret, default is env OSS_ACCESS_SECRET') - .option('-b, --bucket-name ', 'bucket-name, default is env OSS_BUCKET_NAME') - .option('-r, --region ', 'region', 'cn-default') - .option('--env ', `the file name to generate`, '.env') - .action(async (options) => { - const envFile = resolve(process.cwd(), options.env) - dotenv.config({ path: envFile}) - - const endpoint = options.endpoint || process.env.OSS_ENDPOINT || 'https://oss.lafyun.com' - const accessKey = options.accessKey || process.env.OSS_ACCESS_KEY || '' - const accessSecret = options.accessSecret || process.env.OSS_ACCESS_SECRET || '' - const bucketName = options.bucketName || process.env.OSS_BUCKET_NAME || '' - const region = options.region || process.env.OSS_REGION - - const content = `OSS_ENDPOINT=${endpoint} -OSS_ACCESS_KEY=${accessKey} -OSS_ACCESS_SECRET=${accessSecret} -OSS_BUCKET_NAME=${bucketName} -OSS_REGION=${region}` - - fs.writeFileSync(envFile, content) - console.log(`Generated: ${envFile}`) - }) - +import { handleLoginCommand } from './actions/user' +import { syncApp } from './api/sync' +import { getApplicationByAppid } from './api/apps' +import { handleInitAppCommand ,handleSyncAppCommand} from './actions/init' +import { appStop, appStart, appRestart } from './api/apps' +import { handleAppListCommand } from './actions/app' +import { makeFnCommand} from './functions' +import { makeOssCommand} from './oss' program .option('-v, --version', 'output version') @@ -95,7 +22,7 @@ program console.log(' npm install -g laf-cli') }) - program +program .command('login') .option('-u, --username ', 'username') .option('-p, --password ', 'password') @@ -120,5 +47,90 @@ program }) + +program + .command('init ') + .option('-s, --sync', 'sync app', false) + .action(async (appid, options) => { + try { + // get app + const result = await getApplicationByAppid(appid) + const appName = result.data.application.name + const endPoint = `${result.data.app_deploy_url_schema}://${appid}.${result.data.app_deploy_host}` + const ossEndpoint = result.data.oss_external_endpoint + + await handleInitAppCommand(appName,appid,endPoint,ossEndpoint) + + // sync app data + if (options.sync) { + //sync app + const data = await syncApp(appid) + + await handleSyncAppCommand(appName,data) + } + } + catch (err) { + console.log(err.message) + } + }) + + +program + .command('list') + .action(async () => { + + await handleAppListCommand() + + }) + +program + .command('stop ') + .option('--env ', `the file name to generate`, '.env') + .action(async (appid) => { + + const response = await appStop(appid) + + if (response.data.result) { + console.log('stop success') + } else { + console.log('stop failed') + } + }) + + +program + .command('start ') + .option('--env ', `the file name to generate`, '.env') + .action(async (appid) => { + + const response = await appStart(appid) + + if (response.data.result) { + console.log('start success') + } else { + console.log('start failed') + } + }) + + +program + .command('restart ') + .option('--env ', `the file name to generate`, '.env') + .action(async (appid) => { + + const response = await appRestart(appid) + + if (response.data.result) { + console.log('restart success') + } else { + console.log('restart failed') + } + }) + +program.addCommand(makeFnCommand()) + +program.addCommand(makeOssCommand()) + + program.parse(process.argv) diff --git a/packages/cli/src/oss.ts b/packages/cli/src/oss.ts new file mode 100644 index 0000000000..c5aa70fb93 --- /dev/null +++ b/packages/cli/src/oss.ts @@ -0,0 +1,48 @@ +import { Command } from 'commander'; +import { detail } from './api/oss' +import { handlePushCommand ,handlePullCommand } from './actions/oss' + +import { getAppData } from "./utils/util" + +export function makeOssCommand() { + + const oss = new Command('oss') + + oss + .command('pull') + .argument('bucket',"bucket") + .argument('out-path',"out-path") + .option('-f, --force-overwrite', 'force to file ignore if modified', false) + .action(async (bucket,outPath,options) => { + + const appData = getAppData() + //get bucket detail + const buckets = await detail(appData.appid,bucket) + options.outPath= outPath + options.bucketName =`${appData.appid}-${bucket}` + options.endpoint =appData.ossEndpoint + await handlePullCommand( buckets.data.credentials,options) + + }); + + oss + .command('push') + .argument('input-path',"input-path") + .argument('bucket',"bucket") + .option('-f, --force-overwrite', 'force to file ignore if modified', false) + + .action(async(inputPath,bucket,options) => { + + const appData = getAppData() + // get bucket detail + const buckets = await detail(appData.appid,bucket) + options.source = inputPath + options.bucketName = `${appData.appid}-${bucket}` + options.endpoint = appData.ossEndpoint + await handlePushCommand( buckets.data.credentials,options) + + }); + + return oss; + +} diff --git a/packages/cli/src/utils/tokens.ts b/packages/cli/src/utils/tokens.ts index c910d8d1d5..1490a58410 100644 --- a/packages/cli/src/utils/tokens.ts +++ b/packages/cli/src/utils/tokens.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs' -import * as path from 'node:path' -import { CREDENTIALS_DIR, AUTH_FILE,LAF_FILE } from '../utils/constants' +import * as path from 'node:path' +import { CREDENTIALS_DIR, AUTH_FILE, LAF_FILE } from '../utils/constants' import { getApplicationByAppid } from '../api/apps' import { CREDENTIALS_DIR,AUTH_FILE} from '../utils/constants' @@ -35,7 +35,7 @@ export async function getDebugToken() { const appFile = path.resolve(process.cwd(), LAF_FILE) const appData = JSON.parse(fs.readFileSync(appFile, 'utf8')) - const response = await getApplicationByAppid(appData.appid); + const response = await getApplicationByAppid(appData.appid) return response.data.debug_token } diff --git a/packages/cli/src/utils/util.ts b/packages/cli/src/utils/util.ts index c6029f7794..6d65034921 100644 --- a/packages/cli/src/utils/util.ts +++ b/packages/cli/src/utils/util.ts @@ -1,59 +1,72 @@ import * as fs from 'node:fs' -import * as path from 'node:path' +import * as path from 'node:path' import { CREDENTIALS_DIR } from './constants' -import { AUTH_FILE,LAF_FILE,FUNCTIONS_DIR } from '../utils/constants' +import { AUTH_FILE, LAF_FILE, FUNCTIONS_DIR } from '../utils/constants' +const AWS = require('aws-sdk') /** * check auth dir */ -export function checkCredentialsDir(){ +export function checkCredentialsDir() { - try{ + try { // check dir - fs.accessSync(CREDENTIALS_DIR, fs.constants.R_OK|fs.constants.W_OK) - }catch(err){ + fs.accessSync(CREDENTIALS_DIR, fs.constants.R_OK | fs.constants.W_OK) + } catch (err) { // mkdir fs.mkdirSync(CREDENTIALS_DIR, { recursive: true }) } - + } /** * check dir * @param {string} dir + * @param {boolean} flag */ -export function checkDir(dir:string){ - try{ +export function checkDir(dir: string, flag: boolean = true) { + try { // check dir - fs.accessSync(dir, fs.constants.R_OK|fs.constants.W_OK) - }catch(err){ + fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK) + } catch (err) { // mkdir - fs.mkdirSync(dir, { recursive: true }) + if (flag) { + fs.mkdirSync(dir, { recursive: true }) + } else { + + console.error('dir not exist') + process.exit(1) + } + } } +/** + * get all funtions in app functions dir + * @returns + */ /** * get all funtions in app functions dir * @returns */ -export function getAppFunctions(dir:string){ +export function getAppFunctions(dir: string) { let arrFiles = [] const files = fs.readdirSync(dir) for (let i = 0; i < files.length; i++) { - const item = files[i] + const item = files[i] - const itemDir = path.resolve(dir,item) + const itemDir = path.resolve(dir, item) - const stat = fs.lstatSync(itemDir) - if (stat.isDirectory() === true) { - arrFiles.push(item) - } + const stat = fs.lstatSync(itemDir) + if (stat.isDirectory() === true) { + arrFiles.push(item) + } } - return arrFiles; + return arrFiles } @@ -62,13 +75,13 @@ export function getAppFunctions(dir:string){ * @param {string} functionName */ -export function checkFuncNameDir(functionName:string){ +export function checkFuncNameDir(functionName: string) { const functionsDir = path.resolve(process.cwd(), FUNCTIONS_DIR) checkDir(functionsDir) const functions = getAppFunctions(functionsDir) - if(functionName){ - if(!functions.includes(functionName)){ + if (functionName) { + if (!functions.includes(functionName)) { console.error('function not exist') process.exit(1) } @@ -82,17 +95,17 @@ export function checkFuncNameDir(functionName:string){ * @returns */ -export function getRemoteServe(){ +export function getRemoteServe() { - try{ + try { // check dir - fs.accessSync(CREDENTIALS_DIR, fs.constants.R_OK|fs.constants.W_OK) - }catch(err){ - console.error("please login first 3") + fs.accessSync(CREDENTIALS_DIR, fs.constants.R_OK | fs.constants.W_OK) + } catch (err) { + console.error("please login first") process.exit(1) } - const authData = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')); - return authData.remote + const authData = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')) + return authData.remote } @@ -101,19 +114,38 @@ export function getRemoteServe(){ * @returns */ -export function getAppData(){ +export function getAppData() { - try{ + try { const appFile = path.resolve(process.cwd(), LAF_FILE) // check file - fs.accessSync(appFile, fs.constants.R_OK|fs.constants.W_OK) + fs.accessSync(appFile, fs.constants.R_OK | fs.constants.W_OK) const appData = JSON.parse(fs.readFileSync(appFile, 'utf8')) return appData - }catch(err){ + } catch (err) { console.error("cant find laf.json") process.exit(1) } } - \ No newline at end of file +/** + * get s3 client + * @param endpoint + * @param credentials + * @returns + */ +export function getS3Client(endpoint: string, credentials: any) { + + return new AWS.S3({ + accessKeyId: credentials.accessKeyId, + secretAccessKey: credentials.secretAccessKey, + sessionToken: credentials.sessionToken, + endpoint: endpoint, + s3ForcePathStyle: true, + signatureVersion: 'v4', + region: 'us-east-1' + }) + +} + diff --git a/packages/system-server/src/handler/function/index.ts b/packages/system-server/src/handler/function/index.ts index 0d5fbb63a2..4b2030b940 100644 --- a/packages/system-server/src/handler/function/index.ts +++ b/packages/system-server/src/handler/function/index.ts @@ -7,12 +7,12 @@ import { Router } from "express" import { handleCreateFunction } from "./create" -import { handleGetAllFunctionTags, handleGetFunctionById,handleGetFunctionByName, handleGetFunctionHistory, handleGetFunctions, handleGetPublishedFunctions } from "./get" +import { handleGetAllFunctionTags, handleGetFunctionById, handleGetFunctionByName, handleGetFunctionHistory, handleGetFunctions, handleGetPublishedFunctions } from "./get" import { handleGetFunctionLogs } from "./logs" import { handlePublishFunctions, handlePublishOneFunction, handlePublishOneFunctionByName } from "./publish" import { handleRemoveFunctionById } from "./remove" import { handleCreateTrigger, handleRemoveTrigger, handleUpdateTrigger } from "./trigger" -import { handleCompileFunctionCode, handleUpdateFunction, handleUpdateFunctionCode,handleUpdateFunctionCodeByName } from "./update" +import { handleCompileFunctionCode, handleUpdateFunction, handleUpdateFunctionCode, handleUpdateFunctionCodeByName } from "./update" export const FunctionRouter = Router() @@ -108,16 +108,16 @@ FunctionRouter.delete('/:func_id/triggers/:trigger_id', handleRemoveTrigger) /** * get function by name */ - FunctionRouter.get('/getFunction/:func_name', handleGetFunctionByName) +FunctionRouter.get('/detail/:func_name', handleGetFunctionByName) - /** - * update function by name - */ - FunctionRouter.post('/updateFunction/:func_name', handleUpdateFunctionCodeByName) +/** +* update function by name +*/ +FunctionRouter.post('/save/:func_name', handleUpdateFunctionCodeByName) /** * publish function by name */ -FunctionRouter.post('/publishFunction/:func_name', handlePublishOneFunctionByName) \ No newline at end of file +FunctionRouter.post('/publish/:func_name', handlePublishOneFunctionByName)