diff --git a/cli/.prettierrc b/cli/.prettierrc new file mode 100644 index 0000000000..5a6fd01584 --- /dev/null +++ b/cli/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "semi": false +} \ No newline at end of file diff --git a/cli/package-lock.json b/cli/package-lock.json index 752f9f90c8..deeed1eb5f 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -1,18 +1,20 @@ { "name": "laf-cli", - "version": "1.0.0", + "version": "0.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "laf-cli", - "version": "1.0.0", + "version": "0.0.1", "license": "ISC", "dependencies": { "axios": "^1.2.1", "class-transformer": "^0.5.1", "cli-table3": "^0.6.3", "commander": "^9.3.0", + "dayjs": "^1.11.7", + "node-emoji": "^1.11.0", "reflect-metadata": "^0.1.13", "typescript": "^4.7.4", "yaml": "^2.1.3" @@ -21,7 +23,6 @@ "laf": "dist/main.js" }, "devDependencies": { - "@types/cli-table2": "^0.2.3", "@types/mime": "^2.0.3", "@types/node": "^17.0.31" } @@ -35,12 +36,6 @@ "node": ">=0.1.90" } }, - "node_modules/@types/cli-table2": { - "version": "0.2.3", - "resolved": "https://registry.npmmirror.com/@types/cli-table2/-/cli-table2-0.2.3.tgz", - "integrity": "sha512-D4DqsA0Gcpqvra31Ou3pEiD+EcH0rtxa27VSH0aOTdzpDqDU+SenTxsL3BZS29KoP8CPja9Lq2U7HCD5amuHCw==", - "dev": true - }, "node_modules/@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-2.0.3.tgz", @@ -114,6 +109,12 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/dayjs": { + "version": "1.11.7", + "resolved": "http://maven.metaapp.cn/repository/npm-group/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==", + "license": "MIT" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -161,6 +162,12 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "http://maven.metaapp.cn/repository/npm-group/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -180,6 +187,15 @@ "node": ">= 0.6" } }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "http://maven.metaapp.cn/repository/npm-group/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -242,12 +258,6 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "optional": true }, - "@types/cli-table2": { - "version": "0.2.3", - "resolved": "https://registry.npmmirror.com/@types/cli-table2/-/cli-table2-0.2.3.tgz", - "integrity": "sha512-D4DqsA0Gcpqvra31Ou3pEiD+EcH0rtxa27VSH0aOTdzpDqDU+SenTxsL3BZS29KoP8CPja9Lq2U7HCD5amuHCw==", - "dev": true - }, "@types/mime": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-2.0.3.tgz", @@ -307,6 +317,11 @@ "resolved": "https://registry.npmmirror.com/commander/-/commander-9.4.1.tgz", "integrity": "sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==" }, + "dayjs": { + "version": "1.11.7", + "resolved": "http://maven.metaapp.cn/repository/npm-group/dayjs/-/dayjs-1.11.7.tgz", + "integrity": "sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -337,6 +352,11 @@ "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, + "lodash": { + "version": "4.17.21", + "resolved": "http://maven.metaapp.cn/repository/npm-group/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -350,6 +370,14 @@ "mime-db": "1.52.0" } }, + "node-emoji": { + "version": "1.11.0", + "resolved": "http://maven.metaapp.cn/repository/npm-group/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "requires": { + "lodash": "^4.17.21" + } + }, "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", diff --git a/cli/package.json b/cli/package.json index 38db3da84d..52f5fd93ea 100644 --- a/cli/package.json +++ b/cli/package.json @@ -25,7 +25,6 @@ "url": "https://github.com/labring/laf/issues" }, "devDependencies": { - "@types/cli-table2": "^0.2.3", "@types/mime": "^2.0.3", "@types/node": "^17.0.31" }, @@ -34,6 +33,8 @@ "class-transformer": "^0.5.1", "cli-table3": "^0.6.3", "commander": "^9.3.0", + "dayjs": "^1.11.7", + "node-emoji": "^1.11.0", "reflect-metadata": "^0.1.13", "typescript": "^4.7.4", "yaml": "^2.1.3" diff --git a/cli/src/action/application/index.ts b/cli/src/action/application/index.ts new file mode 100644 index 0000000000..8071dae805 --- /dev/null +++ b/cli/src/action/application/index.ts @@ -0,0 +1,93 @@ +import { applicationControllerFindAll, applicationControllerFindOne } from "../../api/v1/application" +import * as Table from 'cli-table3'; +import { ApplicationConfig, existApplicationConfig, writeApplicationConfig } from "../../config/application" +import * as path from 'node:path' +import * as fs from 'node:fs' +import { FUNCTIONS_DIRECTORY_NAME, GLOBAL_FILE, PACKAGE_FILE, RESPONSE_FILE, TEMPLATE_DIR, TSCONFIG_FILE, TYPE_DIR } from "../../common/constant" +import { ensureDirectory } from "../../util/file" +import { readSystemConfig } from "../../config/system" +import { update as dependencyUpdate } from "../dependency" +import { refreshSecretConfig } from "../../config/secret" +import { getEmoji } from "../../util/print"; + + + + +export async function list() { + const table = new Table({ + head: ['appid', 'name', 'region', 'bundle', 'runtime', 'phase'], + }) + const data = await applicationControllerFindAll(); + for (let item of data) { + table.push([item.appid, item.name, item.regionName, item.bundleName, item.runtimeName, item.phase]) + } + console.log(table.toString()); +} + +export async function init(appid: string, options: { sync: boolean }) { + if (existApplicationConfig()) { + console.log(`${getEmoji('❌')} The laf.yaml file already exists in the current directory. Please change the directory or delete the laf.yaml file`) + return + } + + const data = await applicationControllerFindOne(appid); + + const config: ApplicationConfig = { + name: data.name, + appid: data.appid, + regionName: data.regionName, + bundleName: data.bundleName, + runtimeName: data.runtimeName, + createdAt: data.createdAt, + } + // generate application invoke address + const systemConfig = readSystemConfig() + const invokeUrl = systemConfig.remoteServer.replace('api', config.appid) + config.invokeUrl = invokeUrl + + writeApplicationConfig(config) + + // init function + initFunction() + + // init secret + refreshSecretConfig() + + if (options.sync) { + // TODO: sync + } + console.log(`${getEmoji('🚀')} application ${data.name} init success`) + console.log(`${getEmoji('👉')} please run 'npm install' install dependencies`) +} + +function initFunction() { + // if not exist,create functions directory + ensureDirectory(path.join(process.cwd(), FUNCTIONS_DIRECTORY_NAME)) + + const typeDir = path.resolve(process.cwd(), TYPE_DIR) + ensureDirectory(typeDir) + + // from template dir + const templateDir = path.resolve(__dirname, '../../../', TEMPLATE_DIR) + + // generate global.d.ts + const fromGlobalFile = path.resolve(templateDir, GLOBAL_FILE) + const outGlobalFile = path.resolve(typeDir, GLOBAL_FILE) + fs.writeFileSync(outGlobalFile, fs.readFileSync(fromGlobalFile, 'utf-8')) + + // generate response.d.ts + const fromResponseFile = path.resolve(templateDir, RESPONSE_FILE) + const outResponseFile = path.resolve(TYPE_DIR, RESPONSE_FILE) + fs.writeFileSync(outResponseFile, fs.readFileSync(fromResponseFile, 'utf-8')) + + // generate package.json + const fromPackageFile = path.resolve(templateDir, PACKAGE_FILE) + const outPackageFile = path.resolve(process.cwd(), PACKAGE_FILE) + fs.writeFileSync(outPackageFile, fs.readFileSync(fromPackageFile, 'utf-8')) + dependencyUpdate() + + // generate tsconfig.json + const fromTsConfigFile = path.resolve(templateDir, TSCONFIG_FILE) + const outTsConfigFile = path.resolve(process.cwd(), TSCONFIG_FILE) + fs.writeFileSync(outTsConfigFile, fs.readFileSync(fromTsConfigFile, 'utf-8')) +} \ No newline at end of file diff --git a/cli/src/action/auth/index.ts b/cli/src/action/auth/index.ts new file mode 100644 index 0000000000..338071cb7f --- /dev/null +++ b/cli/src/action/auth/index.ts @@ -0,0 +1,36 @@ +import { authControllerPat2Token } from "../../api/v1/authentication" +import { Pat2TokenDto } from "../../api/v1/data-contracts" +import { DEFAULT_REMOTE_SERVER, TOKEN_EXPIRE } from "../../common/constant" +import { removeSystemConfig, SystemConfig, writeSystemConfig } from "../../config/system" +import { getEmoji } from "../../util/print" + + +export async function login(pat, options) { + + let systemConfig: SystemConfig = { + remoteServer: DEFAULT_REMOTE_SERVER, + } + writeSystemConfig(systemConfig) + + const patDto: Pat2TokenDto = { + pat: pat, + } + const token = await authControllerPat2Token(patDto) + const timestamp = Date.parse(new Date().toString()) / 1000 + systemConfig = { + token: token, + tokenExpire: timestamp + TOKEN_EXPIRE, + pat: pat, + remoteServer: DEFAULT_REMOTE_SERVER, + } + if (options.remote) { + systemConfig.remoteServer = options.remote + } + writeSystemConfig(systemConfig) + console.log(`${getEmoji('🎉')} login success`) +} + +export async function logout() { + removeSystemConfig() + console.log(`${getEmoji('👋')} logout success`) +} \ No newline at end of file diff --git a/cli/src/action/dependency/index.ts b/cli/src/action/dependency/index.ts new file mode 100644 index 0000000000..6096901c83 --- /dev/null +++ b/cli/src/action/dependency/index.ts @@ -0,0 +1,21 @@ +import * as path from "node:path" +import * as fs from "node:fs" +import { dependencyControllerGetDependencies } from "../../api/v1/application" +import { PACKAGE_FILE } from "../../common/constant" +import { readApplicationConfig } from "../../config/application" + + +export async function update() { + const appConfig = readApplicationConfig() + const dependencies = await dependencyControllerGetDependencies(appConfig.appid) + + // TODO: update dependencies to package.json + const packagePath = path.resolve(process.cwd(), PACKAGE_FILE) + let packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8")) + const devDependencies = {} + for (let dependency of dependencies) { + devDependencies[dependency.name] = dependency.spec + } + packageJson.devDependencies = devDependencies + fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)) +} \ No newline at end of file diff --git a/cli/src/action/function/index.ts b/cli/src/action/function/index.ts new file mode 100644 index 0000000000..2f4ec12fe5 --- /dev/null +++ b/cli/src/action/function/index.ts @@ -0,0 +1,146 @@ +import { CompileFunctionDto, CreateFunctionDto, UpdateFunctionDto } from "../../api/v1/data-contracts" +import { functionControllerCompile, functionControllerCreate, functionControllerFindAll, functionControllerFindOne, functionControllerUpdate, logControllerGetLogs } from "../../api/v1/function" +import { readApplicationConfig } from "../../config/application" +import { FunctionConfig, readFunctionConfig, writeFunctionConfig } from "../../config/function" +import * as path from "node:path" +import * as fs from "node:fs" +import * as Table from 'cli-table3' +import { formatDate } from "../../util/format" +import { readSecretConfig } from "../../config/secret" +import { invokeFunction } from "../../api/debug" +import { exist } from "../../util/file" +import { getEmoji } from "../../util/print" + + + +export async function create(funcName: string, options: { websocket: boolean, methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD")[], tags: string[], description: string }) { + const appConfig = readApplicationConfig() + const createDto: CreateFunctionDto = { + name: funcName, + description: options.description, + websocket: options.websocket, + methods: options.methods, + code: `\nimport cloud from '@lafjs/cloud'\n\nexports.main = async function (ctx: FunctionContext) {\n console.log('Hello World')\n return { data: 'hi, laf' }\n}\n`, + tags: options.tags, + } + await functionControllerCreate(appConfig.appid, createDto) + pullOne(funcName) + console.log(`${getEmoji('✅')} function ${funcName} created`) +} + + +export async function list() { + const appConfig = readApplicationConfig() + const funcs = await functionControllerFindAll(appConfig.appid) + const table = new Table({ + head: ['name', 'desc', 'websocket', 'methods', 'tags', 'updatedAt'], + }) + for (let func of funcs) { + table.push([func.name, func.description, func.websocket, func.methods.join(','), func.tags.join(','), formatDate(func.updatedAt)]) + } + console.log(table.toString()) +} + +async function pull(funcName: string) { + const appConfig = readApplicationConfig() + const func = await functionControllerFindOne(appConfig.appid, funcName) + const funcConfig: FunctionConfig = { + name: func.name, + description: func.description, + websocket: func.websocket, + methods: func.methods, + tags: func.tags, + } + writeFunctionConfig(func.name, funcConfig) + const codePath = path.join(process.cwd(), 'functions', func.name + '.ts') + fs.writeFileSync(codePath, func.source.code) +} + +export async function pullAll() { + const appConfig = readApplicationConfig() + const funcs = await functionControllerFindAll(appConfig.appid) + for (let func of funcs) { + await pull(func.name) + console.log(`${getEmoji('✅')} function ${func.name} pulled`) + } +} + + +export async function pullOne(funcName: string) { + await pull(funcName) + console.log(`${getEmoji('✅')} function ${funcName} pulled`) +} + +async function push(funcName: string) { + const appConfig = readApplicationConfig() + const funcConfig = readFunctionConfig(funcName) + const codePath = path.join(process.cwd(), 'functions', funcName + '.ts') + const code = fs.readFileSync(codePath, 'utf-8') + + const updateDto: UpdateFunctionDto = { + description: funcConfig.description || '', + websocket: funcConfig.websocket, + methods: funcConfig.methods as any, + code, + tags: funcConfig.tags, + } + await functionControllerUpdate(appConfig.appid, funcName, updateDto) +} + +export async function pushAll() { + const appConfig = readApplicationConfig() + const funcs = await functionControllerFindAll(appConfig.appid) + for (let func of funcs) { + await push(func.name) + console.log(`${getEmoji('✅')} function ${func.name} pushed`) + } +} + +export async function pushOne(funcName: string) { + await push(funcName) + console.log(`${getEmoji('✅')} function ${funcName} pushed`) +} + +export async function exec(funcName: string, options: {log: string, requestId: boolean}) { + // compile code + const codePath = path.join(process.cwd(), 'functions', funcName + '.ts') + if (!exist(codePath)) { + console.error(`${getEmoji('❌')} function ${funcName} not found, please pull or create it!`) + process.exit(1) + } + const code = fs.readFileSync(codePath, 'utf-8') + const compileDto: CompileFunctionDto = { + code, + } + const appConfig = readApplicationConfig() + const func = await functionControllerCompile(appConfig.appid, funcName, compileDto) + // invoke debug + const secretConfig = readSecretConfig() + const res = await invokeFunction(appConfig.invokeUrl, secretConfig?.functionSecretConfig?.debugToken, funcName, func) + + // print requestId + if (options.requestId) { + console.log(`requestId: ${res.requestId}`) + } + + // print response + console.log(res.res) + + // print log + if (options.log) { + await printLog(appConfig.appid, res.requestId, options.log) + } +} + + +async function printLog(appid: string, requestId: string, limit: string) { + const data = await logControllerGetLogs({ + appid, + requestId, + limit: limit, + }) + for (let log of data.list) { + console.log(`[${formatDate(log.createdAt)}] ${log.data}`) + } +} + diff --git a/cli/src/actions/application/application.ts b/cli/src/actions/application/application.ts deleted file mode 100644 index 6688c62f4c..0000000000 --- a/cli/src/actions/application/application.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { getWorkDir, writeYamlFile, ensureDirectory } from '../../utils/path'; -import * as path from 'node:path' -import * as fs from 'node:fs' -import { getApplicationByAppid, listApplication } from '../../apis/application'; -import * as Table from 'cli-table3'; -import { ApplicationMetadata } from '../../templates/application'; -import { APPLICATION_METADATA_FILE_NAME, FUNCTIONS_DIRECTORY_NAME } from '../../utils/constant' - - -export async function handleInitApplication(appid: string, options: { sync: boolean }) { - const applicationYamlPath = path.join(getWorkDir(), APPLICATION_METADATA_FILE_NAME) - if (fs.existsSync(applicationYamlPath)) { - console.log('The application configuration file already exists in the current directory, unable to initialize the application') - return - } - const res = await getApplicationByAppid(appid); - const applicationMetadata: ApplicationMetadata = { - appid: res.data.appid, - name: res.data.name, - regionName: res.data.regionName, - bundleName: res.data.bundleName, - runtimeName: res.data.runtimeName, - } - writeYamlFile(applicationYamlPath, applicationMetadata); - - // init directory - ensureDirectory(path.join(getWorkDir(), FUNCTIONS_DIRECTORY_NAME)) - - - // if sync is true, load remote data in local - if (options.sync) { - - } - -} - -export async function handleListApplication() { - const table = new Table({ - head: ['appid', 'name', 'region', 'bundle', 'runtime', 'phase'], - }) - const res = await listApplication(); - for (let app of res.data) { - table.push([app.appid, app.name, app.regionName, app.bundleName, app.runtimeName, app.phase]) - } - console.log(table.toString()); -} \ No newline at end of file diff --git a/cli/src/actions/auth/auth.ts b/cli/src/actions/auth/auth.ts deleted file mode 100644 index 1134fbff0d..0000000000 --- a/cli/src/actions/auth/auth.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ensureDirectory, getConfigDir, writeYamlFile } from "../../utils/path"; -import * as path from 'node:path' -import * as fs from 'node:fs' -import { CONFIG_FILE_NAME } from "../../utils/constant"; -import { getConfigData } from "../../utils/token"; - -export async function handleLogin(token: string, options: { remote: string }) { - let configData = getConfigData() - // TODO check token - configData.accessToken = token; - if (options.remote !== '') { - configData.remoteServer = options.remote - } - const configDir = getConfigDir() - ensureDirectory(configDir) - const configPath = path.join(configDir, CONFIG_FILE_NAME); - writeYamlFile(configPath, configData); - console.log("login success") -} - -export async function handleLogout() { - const configDir = getConfigDir(); - const configPath = path.join(configDir, CONFIG_FILE_NAME); - fs.rmSync(configPath); - console.log("logout success") -} \ No newline at end of file diff --git a/cli/src/api/debug.ts b/cli/src/api/debug.ts new file mode 100644 index 0000000000..591cd471e2 --- /dev/null +++ b/cli/src/api/debug.ts @@ -0,0 +1,54 @@ +import axios, { AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse } from "axios"; + +export async function invokeFunction(invokeUrl: string, token: string, funcName: string, data: any): Promise<{ res: any; requestId: string; }> { + const header: AxiosRequestHeaders | any = { + 'x-laf-debug-token': token, + 'x-laf-func-data': JSON.stringify(data), + }; + const res = await request({ url: invokeUrl + '/' + funcName, method: "GET", headers: header }) + return { + res: res.data, + requestId: res.headers['request-id'], + } +} + + +const request = axios.create({ + baseURL: "/", + withCredentials: true, + timeout: 30000, +}) + +// request interceptor +request.interceptors.request.use( + (config: AxiosRequestConfig) => { + + let _headers: AxiosRequestHeaders | any = { + "Content-Type": "application/json", + }; + config.headers = { + ..._headers, + ...config.headers, + }; + return config; + }, + (error) => { + console.log("exec error", error); + }, +) + + +// response interceptor +request.interceptors.response.use( + (response: AxiosResponse) => { + return response + }, + (error) => { + if (axios.isCancel(error)) { + console.log("repeated request: " + error.message); + process.exit(1) + } else { + return Promise.reject(error); + } + }, +) \ No newline at end of file diff --git a/cli/src/api/v1/application.ts b/cli/src/api/v1/application.ts new file mode 100644 index 0000000000..065a8712ad --- /dev/null +++ b/cli/src/api/v1/application.ts @@ -0,0 +1,245 @@ +import { request, RequestParams } from "../../util/request"; +import { + ApplicationControllerCreateData, + ApplicationControllerFindAllData, + ApplicationControllerFindOneData, + ApplicationControllerUpdateData, + CreateApplicationDto, + CreateDependencyDto, + CreateEnvironmentDto, + UpdateApplicationDto, + UpdateDependencyDto, +} from "./data-contracts"; + +/** + * No description + * + * @tags Application + * @name ApplicationControllerCreate + * @summary Create a new application + * @request POST:/v1/applications + * @secure + */ +export async function applicationControllerCreate( + data: CreateApplicationDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/applications`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name ApplicationControllerFindAll + * @summary Get user application list + * @request GET:/v1/applications + * @secure + */ +export async function applicationControllerFindAll( + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/applications`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name ApplicationControllerFindOne + * @summary Get an application by appid + * @request GET:/v1/applications/{appid} + * @secure + */ +export async function applicationControllerFindOne( + appid: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/applications/${appid}`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name ApplicationControllerUpdate + * @summary Update an application + * @request PATCH:/v1/applications/{appid} + * @secure + */ +export async function applicationControllerUpdate( + appid: string, + data: UpdateApplicationDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/applications/${appid}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name ApplicationControllerRemove + * @summary Delete an application + * @request DELETE:/v1/applications/{appid} + * @secure + */ +export async function applicationControllerRemove(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/applications/${appid}`, + method: "DELETE", + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name EnvironmentVariableControllerAdd + * @summary Set a environment variable (create/update) + * @request POST:/v1/apps/{appid}/environments + * @secure + */ +export async function environmentVariableControllerAdd( + appid: string, + data: CreateEnvironmentDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/environments`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name EnvironmentVariableControllerGet + * @summary Get environment variables + * @request GET:/v1/apps/{appid}/environments + * @secure + */ +export async function environmentVariableControllerGet(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/apps/${appid}/environments`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name EnvironmentVariableControllerDelete + * @summary Delete an environment variable by name + * @request DELETE:/v1/apps/{appid}/environments/{name} + * @secure + */ +export async function environmentVariableControllerDelete( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/environments/${name}`, + method: "DELETE", + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name DependencyControllerAdd + * @summary Add application dependencies + * @request POST:/v1/apps/{appid}/dependencies + * @secure + */ +export async function dependencyControllerAdd( + appid: string, + data: CreateDependencyDto[], + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/dependencies`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name DependencyControllerUpdate + * @summary Update application dependencies + * @request PATCH:/v1/apps/{appid}/dependencies + * @secure + */ +export async function dependencyControllerUpdate( + appid: string, + data: UpdateDependencyDto[], + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/dependencies`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name DependencyControllerGetDependencies + * @summary Get application dependencies + * @request GET:/v1/apps/{appid}/dependencies + * @secure + */ +export async function dependencyControllerGetDependencies( + appid: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/dependencies`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Application + * @name DependencyControllerRemove + * @summary Remove a dependency + * @request DELETE:/v1/apps/{appid}/dependencies/{name} + * @secure + */ +export async function dependencyControllerRemove( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/dependencies/${name}`, + method: "DELETE", + ...configParams, + }); +} diff --git a/cli/src/api/v1/authentication.ts b/cli/src/api/v1/authentication.ts new file mode 100644 index 0000000000..b8d84000d2 --- /dev/null +++ b/cli/src/api/v1/authentication.ts @@ -0,0 +1,133 @@ +import { request, RequestParams } from "../../util/request"; +import { AuthControllerCode2TokenParams, CreatePATDto, Pat2TokenDto } from "./data-contracts"; + +/** + * No description + * + * @tags Authentication + * @name AuthControllerGetSigninUrl + * @summary Redirect to login page + * @request GET:/v1/login + */ +export async function authControllerGetSigninUrl(configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/login`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name AuthControllerGetSignupUrl + * @summary Redirect to register page + * @request GET:/v1/register + */ +export async function authControllerGetSignupUrl(configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/register`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name AuthControllerCode2Token + * @summary Get user token by auth code + * @request GET:/v1/code2token + */ +export async function authControllerCode2Token( + query: AuthControllerCode2TokenParams, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/code2token`, + method: "GET", + params: query, + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name AuthControllerPat2Token + * @summary Get user token by PAT + * @request POST:/v1/pat2token + */ +export async function authControllerPat2Token(data: Pat2TokenDto, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/pat2token`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name AuthControllerGetProfile + * @summary Get current user profile + * @request GET:/v1/profile + * @secure + */ +export async function authControllerGetProfile(configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/profile`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name PatControllerCreate + * @summary Create a PAT + * @request POST:/v1/pats + * @secure + */ +export async function patControllerCreate(data: CreatePATDto, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/pats`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name PatControllerFindAll + * @summary List PATs + * @request GET:/v1/pats + * @secure + */ +export async function patControllerFindAll(configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/pats`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Authentication + * @name PatControllerRemove + * @summary Delete a PAT + * @request DELETE:/v1/pats/{id} + * @secure + */ +export async function patControllerRemove(id: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/pats/${id}`, + method: "DELETE", + ...configParams, + }); +} diff --git a/cli/src/api/v1/data-contracts.ts b/cli/src/api/v1/data-contracts.ts new file mode 100644 index 0000000000..e29533b168 --- /dev/null +++ b/cli/src/api/v1/data-contracts.ts @@ -0,0 +1,189 @@ +export interface CreateFunctionDto { + /** Function name is unique in the application */ + name: string; + description?: string; + websocket: boolean; + methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD")[]; + /** The source code of the function */ + code: string; + tags?: string[]; +} + +export interface ResponseUtil { + error?: string; + data?: object; +} + +export interface UpdateFunctionDto { + description?: string; + websocket: boolean; + methods: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD")[]; + /** The source code of the function */ + code: string; + tags?: string[]; +} + +export interface CompileFunctionDto { + /** The source code of the function */ + code: string; +} + +export interface CreateApplicationDto { + name: string; + /** @default "Running" */ + state?: "Running" | "Stopped"; + region: string; + bundleName: string; + runtimeName: string; +} + +export interface UpdateApplicationDto { + name?: string; + state?: "Running" | "Stopped" | "Restarting"; +} + +export interface CreateEnvironmentDto { + name: string; + value: string; +} + +export interface CreateBucketDto { + /** The short name of the bucket which not contain the appid */ + shortName: string; + policy: "readwrite" | "readonly" | "private"; +} + +export interface UpdateBucketDto { + policy: "readwrite" | "readonly" | "private"; +} + +export interface CreateCollectionDto { + name: string; +} + +export interface Collection { + name: string; + type: string; + options: object; + info: object; + idIndex: object; +} + +export interface UpdateCollectionDto { + validatorSchema?: object; + validationLevel?: string; +} + +export interface CreatePolicyDto { + name: string; +} + +export interface UpdatePolicyDto { + injector: string; +} + +export interface CreatePolicyRuleDto { + collectionName: string; + value: string; +} + +export interface UpdatePolicyRuleDto { + value: string; +} + +export type CreateWebsiteDto = object; + +export type UpdateWebsiteDto = object; + +export interface Pat2TokenDto { + /** + * PAT + * @example "laf_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + */ + pat: string; +} + +export interface UserProfileDto { + uid: string; + openid: string; + avatar: string; + name: string; + /** @format date-time */ + createdAt: string; + /** @format date-time */ + updatedAt: string; +} + +export interface UserDto { + id: string; + email: string; + username: string; + phone: string; + profile: UserProfileDto; + /** @format date-time */ + createdAt: string; + /** @format date-time */ + updatedAt: string; +} + +export interface CreatePATDto { + name: string; + /** @min 60 */ + expiresIn: number; +} + +export interface CreateDependencyDto { + name: string; + spec: string; +} + +export interface UpdateDependencyDto { + name: string; + spec: string; +} + +export interface CreateTriggerDto { + desc: string; + cron: string; + target: string; +} + +export type AppControllerGetRuntimesData = any; + +export type ApplicationControllerCreateData = any; + +export type ApplicationControllerFindAllData = any; + +export type ApplicationControllerFindOneData = any; + +export type ApplicationControllerUpdateData = any; + +export type RegionControllerGetRegionsData = any; + +export type DatabaseControllerProxyData = any; + +export type WebsitesControllerCreateData = any; + +export type WebsitesControllerFindAllData = any; + +export type WebsitesControllerFindOneData = any; + +export type WebsitesControllerUpdateData = any; + +export type WebsitesControllerRemoveData = any; + +export interface AuthControllerCode2TokenParams { + code: string; +} + +export interface LogControllerGetLogsParams { + /** The request id. Optional */ + requestId?: string; + /** The function name. Optional */ + functionName?: string; + /** The limit number, default is 10 */ + limit?: string; + /** The page number, default is 1 */ + page?: string; + appid: string; +} diff --git a/cli/src/api/v1/database.ts b/cli/src/api/v1/database.ts new file mode 100644 index 0000000000..c92ce82ffa --- /dev/null +++ b/cli/src/api/v1/database.ts @@ -0,0 +1,294 @@ +import { request, RequestParams } from "../../util/request"; +import { + CreateCollectionDto, + CreatePolicyDto, + CreatePolicyRuleDto, + DatabaseControllerProxyData, + UpdateCollectionDto, + UpdatePolicyDto, + UpdatePolicyRuleDto, +} from "./data-contracts"; + +/** + * No description + * + * @tags Database + * @name CollectionControllerCreate + * @summary Create a new collection in database + * @request POST:/v1/apps/{appid}/collections + * @secure + */ +export async function collectionControllerCreate( + appid: string, + data: CreateCollectionDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/collections`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name CollectionControllerFindAll + * @summary Get collection list of an application + * @request GET:/v1/apps/{appid}/collections + * @secure + */ +export async function collectionControllerFindAll(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/apps/${appid}/collections`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name CollectionControllerFindOne + * @summary Get collection by name + * @request GET:/v1/apps/{appid}/collections/{name} + * @secure + */ +export async function collectionControllerFindOne( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/collections/${name}`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name CollectionControllerUpdate + * @summary Update a collection + * @request PATCH:/v1/apps/{appid}/collections/{name} + * @secure + */ +export async function collectionControllerUpdate( + appid: string, + name: string, + data: UpdateCollectionDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/collections/${name}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name CollectionControllerRemove + * @summary Delete a collection by its name + * @request DELETE:/v1/apps/{appid}/collections/{name} + * @secure + */ +export async function collectionControllerRemove( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/collections/${name}`, + method: "DELETE", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyControllerCreate + * @summary Create database policy + * @request POST:/v1/apps/{appid}/policies + * @secure + */ +export async function policyControllerCreate( + appid: string, + data: CreatePolicyDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyControllerFindAll + * @summary Get database policy list + * @request GET:/v1/apps/{appid}/policies + * @secure + */ +export async function policyControllerFindAll(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/apps/${appid}/policies`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyControllerUpdate + * @summary Update database policy + * @request PATCH:/v1/apps/{appid}/policies/{name} + * @secure + */ +export async function policyControllerUpdate( + appid: string, + name: string, + data: UpdatePolicyDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies/${name}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyControllerRemove + * @summary Remove a database policy + * @request DELETE:/v1/apps/{appid}/policies/{name} + * @secure + */ +export async function policyControllerRemove( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies/${name}`, + method: "DELETE", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name DatabaseControllerProxy + * @summary The database proxy for database management + * @request POST:/v1/apps/{appid}/databases/proxy + * @secure + */ +export async function databaseControllerProxy( + appid: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/databases/proxy`, + method: "POST", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyRuleControllerCreate + * @summary Create database policy rule + * @request POST:/v1/apps/{appid}/policies/{name}/rules + * @secure + */ +export async function policyRuleControllerCreate( + appid: string, + name: string, + data: CreatePolicyRuleDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies/${name}/rules`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyRuleControllerFindAll + * @summary Get database policy rules + * @request GET:/v1/apps/{appid}/policies/{name}/rules + * @secure + */ +export async function policyRuleControllerFindAll( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies/${name}/rules`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyRuleControllerUpdate + * @summary Update database policy rule by collection name + * @request PATCH:/v1/apps/{appid}/policies/{name}/rules/{collection} + * @secure + */ +export async function policyRuleControllerUpdate( + appid: string, + name: string, + collection: string, + data: UpdatePolicyRuleDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies/${name}/rules/${collection}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Database + * @name PolicyRuleControllerRemove + * @summary Remove a database policy rule by collection name + * @request DELETE:/v1/apps/{appid}/policies/{name}/rules/{collection} + * @secure + */ +export async function policyRuleControllerRemove( + appid: string, + name: string, + collection: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/policies/${name}/rules/${collection}`, + method: "DELETE", + ...configParams, + }); +} diff --git a/cli/src/api/v1/function.ts b/cli/src/api/v1/function.ts new file mode 100644 index 0000000000..500010c1ec --- /dev/null +++ b/cli/src/api/v1/function.ts @@ -0,0 +1,144 @@ +import { request, RequestParams } from "../../util/request"; +import { CompileFunctionDto, CreateFunctionDto, LogControllerGetLogsParams, UpdateFunctionDto } from "./data-contracts"; + +/** + * No description + * + * @tags Function + * @name FunctionControllerCreate + * @summary Create a new function + * @request POST:/v1/apps/{appid}/functions + * @secure + */ +export async function functionControllerCreate( + appid: string, + data: CreateFunctionDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/functions`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Function + * @name FunctionControllerFindAll + * @summary Query function list of an app + * @request GET:/v1/apps/{appid}/functions + * @secure + */ +export async function functionControllerFindAll(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/apps/${appid}/functions`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Function + * @name FunctionControllerFindOne + * @summary Get a function by its name + * @request GET:/v1/apps/{appid}/functions/{name} + * @secure + */ +export async function functionControllerFindOne( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/functions/${name}`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Function + * @name FunctionControllerUpdate + * @summary Update a function + * @request PATCH:/v1/apps/{appid}/functions/{name} + * @secure + */ +export async function functionControllerUpdate( + appid: string, + name: string, + data: UpdateFunctionDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/functions/${name}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Function + * @name FunctionControllerRemove + * @summary Delete a function + * @request DELETE:/v1/apps/{appid}/functions/{name} + * @secure + */ +export async function functionControllerRemove( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/functions/${name}`, + method: "DELETE", + ...configParams, + }); +} +/** + * No description + * + * @tags Function + * @name FunctionControllerCompile + * @summary Compile a function + * @request POST:/v1/apps/{appid}/functions/{name}/compile + * @secure + */ +export async function functionControllerCompile( + appid: string, + name: string, + data: CompileFunctionDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/functions/${name}/compile`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Function + * @name LogControllerGetLogs + * @summary Get function logs + * @request GET:/v1/apps/{appid}/logs/functions + * @secure + */ +export async function logControllerGetLogs( + { appid, ...query }: LogControllerGetLogsParams, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/logs/functions`, + method: "GET", + params: query, + ...configParams, + }); +} diff --git a/cli/src/api/v1/public.ts b/cli/src/api/v1/public.ts new file mode 100644 index 0000000000..07e3d41ad7 --- /dev/null +++ b/cli/src/api/v1/public.ts @@ -0,0 +1,37 @@ +import { request, RequestParams } from "../../util/request"; +import { AppControllerGetRuntimesData, RegionControllerGetRegionsData } from "./data-contracts"; + +/** + * No description + * + * @tags Public + * @name AppControllerGetRuntimes + * @summary Get application runtime list + * @request GET:/v1/runtimes + */ +export async function appControllerGetRuntimes( + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/runtimes`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Public + * @name RegionControllerGetRegions + * @summary Get region list + * @request GET:/v1/regions + */ +export async function regionControllerGetRegions( + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/regions`, + method: "GET", + ...configParams, + }); +} diff --git a/cli/src/api/v1/storage.ts b/cli/src/api/v1/storage.ts new file mode 100644 index 0000000000..9f3dd8e089 --- /dev/null +++ b/cli/src/api/v1/storage.ts @@ -0,0 +1,102 @@ +import { request, RequestParams } from "../../util/request"; +import { CreateBucketDto, UpdateBucketDto } from "./data-contracts"; + +/** + * No description + * + * @tags Storage + * @name BucketControllerCreate + * @summary Create a new bucket + * @request POST:/v1/apps/{appid}/buckets + * @secure + */ +export async function bucketControllerCreate( + appid: string, + data: CreateBucketDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/buckets`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Storage + * @name BucketControllerFindAll + * @summary Get bucket list of an app + * @request GET:/v1/apps/{appid}/buckets + * @secure + */ +export async function bucketControllerFindAll(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/apps/${appid}/buckets`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Storage + * @name BucketControllerFindOne + * @summary Get a bucket by name + * @request GET:/v1/apps/{appid}/buckets/{name} + * @secure + */ +export async function bucketControllerFindOne( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/buckets/${name}`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Storage + * @name BucketControllerUpdate + * @summary Update a bucket + * @request PATCH:/v1/apps/{appid}/buckets/{name} + * @secure + */ +export async function bucketControllerUpdate( + appid: string, + name: string, + data: UpdateBucketDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/buckets/${name}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Storage + * @name BucketControllerRemove + * @summary Delete a bucket + * @request DELETE:/v1/apps/{appid}/buckets/{name} + * @secure + */ +export async function bucketControllerRemove( + appid: string, + name: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/buckets/${name}`, + method: "DELETE", + ...configParams, + }); +} diff --git a/cli/src/api/v1/trigger.ts b/cli/src/api/v1/trigger.ts new file mode 100644 index 0000000000..0d85656fc5 --- /dev/null +++ b/cli/src/api/v1/trigger.ts @@ -0,0 +1,60 @@ +import { request, RequestParams } from "../../util/request"; +import { CreateTriggerDto } from "./data-contracts"; + +/** + * No description + * + * @tags Trigger + * @name TriggerControllerCreate + * @summary Create a cron trigger + * @request POST:/v1/apps/{appid}/triggers + * @secure + */ +export async function triggerControllerCreate( + appid: string, + data: CreateTriggerDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/triggers`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Trigger + * @name TriggerControllerFindAll + * @summary Get trigger list of an application + * @request GET:/v1/apps/{appid}/triggers + * @secure + */ +export async function triggerControllerFindAll(appid: string, configParams: RequestParams = {}): Promise { + return request({ + url: `/v1/apps/${appid}/triggers`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Trigger + * @name TriggerControllerRemove + * @summary Remove a cron trigger + * @request DELETE:/v1/apps/{appid}/triggers/{id} + * @secure + */ +export async function triggerControllerRemove( + appid: string, + id: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/triggers/${id}`, + method: "DELETE", + ...configParams, + }); +} diff --git a/cli/src/api/v1/website.ts b/cli/src/api/v1/website.ts new file mode 100644 index 0000000000..467111c759 --- /dev/null +++ b/cli/src/api/v1/website.ts @@ -0,0 +1,113 @@ +import { request, RequestParams } from "../../util/request"; +import { + CreateWebsiteDto, + UpdateWebsiteDto, + WebsitesControllerCreateData, + WebsitesControllerFindAllData, + WebsitesControllerFindOneData, + WebsitesControllerRemoveData, + WebsitesControllerUpdateData, +} from "./data-contracts"; + +/** + * No description + * + * @tags Website + * @name WebsitesControllerCreate + * @summary TODO - ⌛️ + * @request POST:/v1/apps/{appid}/websites + * @secure + */ +export async function websitesControllerCreate( + appid: string, + data: CreateWebsiteDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/websites`, + method: "POST", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Website + * @name WebsitesControllerFindAll + * @summary TODO - ⌛️ + * @request GET:/v1/apps/{appid}/websites + * @secure + */ +export async function websitesControllerFindAll( + appid: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/websites`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Website + * @name WebsitesControllerFindOne + * @summary TODO - ⌛️ + * @request GET:/v1/apps/{appid}/websites/{id} + * @secure + */ +export async function websitesControllerFindOne( + id: string, + appid: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/websites/${id}`, + method: "GET", + ...configParams, + }); +} +/** + * No description + * + * @tags Website + * @name WebsitesControllerUpdate + * @summary TODO - ⌛️ + * @request PATCH:/v1/apps/{appid}/websites/{id} + * @secure + */ +export async function websitesControllerUpdate( + id: string, + appid: string, + data: UpdateWebsiteDto, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/websites/${id}`, + method: "PATCH", + data: data, + ...configParams, + }); +} +/** + * No description + * + * @tags Website + * @name WebsitesControllerRemove + * @summary TODO - ⌛️ + * @request DELETE:/v1/apps/{appid}/websites/{id} + * @secure + */ +export async function websitesControllerRemove( + id: string, + appid: string, + configParams: RequestParams = {}, +): Promise { + return request({ + url: `/v1/apps/${appid}/websites/${id}`, + method: "DELETE", + ...configParams, + }); +} diff --git a/cli/src/apis/application.ts b/cli/src/apis/application.ts deleted file mode 100644 index 0e13816d11..0000000000 --- a/cli/src/apis/application.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { requestData } from "../utils/request" - -/** - * Get application by appid - * @param {string} appid - * @returns - */ - export async function getApplicationByAppid(appid: string) { - const res = await requestData({ - url: `/v1/applications/${appid}`, - method: 'get' - }) - return res.data - } - -/** - * Get the application list - * @returns - */ - export async function listApplication() { - const url = `/v1/applications` - const obj = { - method: "GET", - url - } - const result = await requestData(obj) - return result.data -} \ No newline at end of file diff --git a/cli/src/command/application/index.ts b/cli/src/command/application/index.ts new file mode 100644 index 0000000000..d48e4e8950 --- /dev/null +++ b/cli/src/command/application/index.ts @@ -0,0 +1,21 @@ +import { Command, program } from "commander" +import { init, list } from "../../action/application" + +export function command(): Command { + const cmd = program.command('app') + + cmd.command('init ') + .description('Initialize application') + .option('-s, --sync', 'Sync application data', false) + .action((appid, options) => { + init(appid, options) + }) + + cmd.command('list') + .description('List application') + .action(() => { + list() + }) + + return cmd +} \ No newline at end of file diff --git a/cli/src/commands/auth/auth.ts b/cli/src/command/auth/index.ts similarity index 51% rename from cli/src/commands/auth/auth.ts rename to cli/src/command/auth/index.ts index f28132bcc6..c53180aa6c 100644 --- a/cli/src/commands/auth/auth.ts +++ b/cli/src/command/auth/index.ts @@ -1,21 +1,21 @@ import { program, Command } from 'commander' -import { handleLogin, handleLogout } from '../../actions/auth/auth' +import { login, logout } from '../../action/auth' export function loginCommand(): Command { - const login = program.command('login ') + const cmd = program.command('login ') .description('login client') .option('-r, --remote [value]', 'remote server address', '') - .action((token, options) => { - handleLogin(token, options) + .action((pat, options) => { + login(pat, options) }) - return login + return cmd } export function logoutCommand(): Command { - const logout = program.command('logout') + const cmd = program.command('logout') .description('logout client') .action(() => { - handleLogout() + logout() }) - return logout + return cmd } \ No newline at end of file diff --git a/cli/src/command/function/index.ts b/cli/src/command/function/index.ts new file mode 100644 index 0000000000..84f191df19 --- /dev/null +++ b/cli/src/command/function/index.ts @@ -0,0 +1,62 @@ +import { Command, program } from "commander" +import { create, exec, list, pullAll, pullOne, pushAll, pushOne } from "../../action/function" +import { checkApplication, checkFunctionDebugToken } from "../../common/hook" + +export function command(): Command { + const cmd = program.command('func') + .hook('preAction', () => { + checkApplication() + }) + + cmd.command('create ') + .description('Create function') + .option('-w --websocket', 'enable websocket', true) + .option('-m --methods ', 'http methods', ['GET', 'POST']) + .option('-t --tags ', 'tags', []) + .option('-d --description ', 'description', '') + .action((funcName, options) => { + create(funcName, options) + }) + + cmd.command('list') + .description('List application') + .action(() => { + list() + }) + + cmd.command('pull') + .argument('[funcName]', 'funcName') + .description('Pull function, if funcName does not exist, pull all') + .action((funcName) => { + if (funcName) { + pullOne(funcName) + } else { + pullAll() + } + }) + + cmd.command('push') + .argument('[FuncName]', 'funcName') + .description('Push function, if funcName does not exist, push all') + .action((funcName) => { + if (funcName) { + pushOne(funcName) + } else { + pushAll() + } + }) + + cmd.command('exec ') + .description('Exec function') + .option('-l --log ', 'print log') + .option('-r --requestId', 'print requestId', false) + .hook('preAction', () => { + checkFunctionDebugToken() + }) + .action((funcName, options) => { + exec(funcName, options) + }) + + + return cmd +} \ No newline at end of file diff --git a/cli/src/commands/application/application.ts b/cli/src/commands/application/application.ts deleted file mode 100644 index bc9a4d9475..0000000000 --- a/cli/src/commands/application/application.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { program, Command } from 'commander' - -import {handleInitApplication, handleListApplication} from '../../actions/application/application' - -export function applicationCommand(): Command { - const app = program.command('app') - - app - .command('init ') - .description('Initialize application') - .option('-s, --sync', 'Sync application', "false") - .action((appid, options) => { - handleInitApplication(appid, options) - }) - - app.command('list') - .description('List application') - .action(() => { - handleListApplication() - }) - - return app -} - - diff --git a/cli/src/common/constant.ts b/cli/src/common/constant.ts new file mode 100644 index 0000000000..84a8c6e694 --- /dev/null +++ b/cli/src/common/constant.ts @@ -0,0 +1,27 @@ + +// directory and metadata file name +export const CONFIG_FILE_NAME = 'config.yaml' +export const SECRET_FILE_NAME = '.secret.yaml' +export const APPLICATION_CONFIG_FILE_NAME = 'laf.yaml' +export const FUNCTIONS_DIRECTORY_NAME = 'functions' +export const FUNCTIONS_CONFIG_FILE_SUFFIX_NAME = '.meta.yaml' +export const COLLECTIONS_DIRECTORY_NAME = 'collections' +export const COLLECTIONS_CONFIG_FILE_SUFFIX_NAME = '.meta.yaml' + +// token expire time 7 days +export const TOKEN_EXPIRE = 3600 * 24 * 7 + +// debug token expire time 7 days +export const DEBUG_TOKEN_EXPIRE = 3600 * 24 * 7 + +// remote server config +// export const DEFAULT_REMOTE_SERVER = 'http://api.dev.laf.run' +export const DEFAULT_REMOTE_SERVER = 'http://api.dev.cloudrun.top' + +// template file +export const TEMPLATE_DIR = 'template' +export const TYPE_DIR = 'types' +export const GLOBAL_FILE = 'global.d.ts' +export const PACKAGE_FILE = 'package.json' +export const RESPONSE_FILE = 'response.d.ts' +export const TSCONFIG_FILE = 'tsconfig.json' \ No newline at end of file diff --git a/cli/src/common/hook.ts b/cli/src/common/hook.ts new file mode 100644 index 0000000000..e72416b132 --- /dev/null +++ b/cli/src/common/hook.ts @@ -0,0 +1,22 @@ +import { existApplicationConfig } from "../config/application" +import { existSecretConfig, readSecretConfig, refreshSecretConfig } from "../config/secret" + +export function checkApplication() { + if (!existApplicationConfig()) { + console.error('Please run "laf app init" to initialize the application first') + process.exit(1) + } +} + +export function checkFunctionDebugToken() { + if (!existSecretConfig()) { + refreshSecretConfig() + return + } + const secretConfig = readSecretConfig() + const { debugToken, debugTokenExpire } = secretConfig.functionSecretConfig + let timestamp = Date.parse(new Date().toString()) / 1000 + if (!debugToken || debugTokenExpire < timestamp) { + refreshSecretConfig() + } +} \ No newline at end of file diff --git a/cli/src/config/application.ts b/cli/src/config/application.ts new file mode 100644 index 0000000000..12a4eed67e --- /dev/null +++ b/cli/src/config/application.ts @@ -0,0 +1,34 @@ +import * as path from "path"; +import { APPLICATION_CONFIG_FILE_NAME } from "../common/constant"; +import { exist, loadYamlFile, remove, writeYamlFile } from "../util/file"; + +// ApplicationConfig is the configuration for an application. +export interface ApplicationConfig { + name: string; + appid: string; + regionName?: string; + bundleName?: string; + runtimeName?: string; + invokeUrl?: string; + createdAt?: string; +} + +export function readApplicationConfig(): ApplicationConfig { + const configPath = path.join(process.cwd(), APPLICATION_CONFIG_FILE_NAME) + return loadYamlFile(configPath) +} + +export function writeApplicationConfig(config: ApplicationConfig) { + const configPath = path.join(process.cwd(), APPLICATION_CONFIG_FILE_NAME) + writeYamlFile(configPath, config) +} + +export function existApplicationConfig(): boolean { + const configPath = path.join(process.cwd(), APPLICATION_CONFIG_FILE_NAME) + return exist(configPath); +} + +export function removeApplicationConfig() { + const configPath = path.join(process.cwd(), APPLICATION_CONFIG_FILE_NAME) + remove(configPath) +} diff --git a/cli/src/config/function.ts b/cli/src/config/function.ts new file mode 100644 index 0000000000..55072ca58f --- /dev/null +++ b/cli/src/config/function.ts @@ -0,0 +1,31 @@ +import * as path from "path"; +import { FUNCTIONS_CONFIG_FILE_SUFFIX_NAME } from "../common/constant"; +import { exist, loadYamlFile, remove, writeYamlFile } from "../util/file"; + +export interface FunctionConfig { + name: string; + description?: string; + tags?: string[]; + websocket?: boolean; + methods: string[]; +} + +export function readFunctionConfig(funcName: string): FunctionConfig { + const funcConfigPath = path.join(process.cwd(), 'functions', funcName + FUNCTIONS_CONFIG_FILE_SUFFIX_NAME) + return loadYamlFile(funcConfigPath) +} + +export function writeFunctionConfig(funcName: string, config: FunctionConfig) { + const funcConfigPath = path.join(process.cwd(), 'functions', funcName + FUNCTIONS_CONFIG_FILE_SUFFIX_NAME) + writeYamlFile(funcConfigPath, config) +} + +export function existFunctionConfig(funcName: string): boolean { + const funcConfigPath = path.join(process.cwd(), 'functions', funcName + FUNCTIONS_CONFIG_FILE_SUFFIX_NAME) + return exist(funcConfigPath) +} + +export function removeFunctionConfig(funcName: string) { + const funcConfigPath = path.join(process.cwd(), 'functions', funcName + FUNCTIONS_CONFIG_FILE_SUFFIX_NAME) + remove(funcConfigPath) +} \ No newline at end of file diff --git a/cli/src/config/secret.ts b/cli/src/config/secret.ts new file mode 100644 index 0000000000..60273f3c67 --- /dev/null +++ b/cli/src/config/secret.ts @@ -0,0 +1,43 @@ +import * as path from "path" +import { applicationControllerFindOne } from "../api/v1/application" +import { DEBUG_TOKEN_EXPIRE, SECRET_FILE_NAME } from "../common/constant" +import { exist, loadYamlFile, writeYamlFile } from "../util/file" +import { readApplicationConfig } from "./application" + +export interface secretConfig { + functionSecretConfig: functionSecretConfig +} + +export interface functionSecretConfig { + debugToken: string + debugTokenExpire: number +} + +export function readSecretConfig(): secretConfig { + const configPath = path.join(process.cwd(), SECRET_FILE_NAME) + return loadYamlFile(configPath) +} + +export function writeSecretConfig(config: secretConfig) { + const configPath = path.join(process.cwd(), SECRET_FILE_NAME) + writeYamlFile(configPath, config) +} + +export function existSecretConfig(): boolean { + const configPath = path.join(process.cwd(), SECRET_FILE_NAME) + return exist(configPath) +} + +export async function refreshSecretConfig() { + const appConfig = readApplicationConfig() + const app = await applicationControllerFindOne(appConfig.appid) + let timestamp = Date.parse(new Date().toString()) / 1000 + + const secretConfig = { + functionSecretConfig: { + debugToken: app.function_debug_token, + debugTokenExpire: timestamp + DEBUG_TOKEN_EXPIRE, + } + } + writeSecretConfig(secretConfig) +} \ No newline at end of file diff --git a/cli/src/config/system.ts b/cli/src/config/system.ts new file mode 100644 index 0000000000..fcbfb9d32e --- /dev/null +++ b/cli/src/config/system.ts @@ -0,0 +1,51 @@ +import * as path from "path" +import { authControllerPat2Token } from "../api/v1/authentication" +import { Pat2TokenDto } from "../api/v1/data-contracts" +import { CONFIG_FILE_NAME, TOKEN_EXPIRE } from "../common/constant" +import { loadYamlFile, writeYamlFile, exist, remove, ensureDirectory } from "../util/file" + +//SystemConfig is the configuration for the config file +export interface SystemConfig { + remoteServer?: string + token?: string + tokenExpire?: number + pat?: string +} + +export function readSystemConfig(): SystemConfig { + const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.laf', CONFIG_FILE_NAME) + return loadYamlFile(configPath) +} + +export function writeSystemConfig(config: SystemConfig) { + const directoryPath = path.join(process.env.HOME || process.env.USERPROFILE, '.laf') + ensureDirectory(directoryPath) + const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.laf', CONFIG_FILE_NAME) + writeYamlFile(configPath, config) +} + +export function existSystemConfig(): boolean { + const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.laf', CONFIG_FILE_NAME) + return exist(configPath) +} + +export function removeSystemConfig() { + const configPath = path.join(process.env.HOME || process.env.USERPROFILE, '.laf', CONFIG_FILE_NAME) + remove(configPath) +} + +export async function refreshToken(): Promise { + const systemConfig = readSystemConfig() + const timestamp = Date.parse(new Date().toString()) / 1000 + if (timestamp > systemConfig.tokenExpire) { + const patDto: Pat2TokenDto = { + pat: systemConfig.pat, + } + const token = await authControllerPat2Token(patDto) + systemConfig.token = token + systemConfig.tokenExpire = timestamp + TOKEN_EXPIRE + writeSystemConfig(systemConfig) + return token + } + return systemConfig.token +} \ No newline at end of file diff --git a/cli/src/main.ts b/cli/src/main.ts index da8f2b3e5b..9e9d198641 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -1,6 +1,7 @@ import { Command } from 'commander' -import { applicationCommand } from './commands/application/application' -import { loginCommand, logoutCommand } from './commands/auth/auth' +import { command as applicationCommand } from './command/application/' +import { command as functionCommand } from './command/function/' +import { loginCommand, logoutCommand } from './command/auth' const program = new Command() @@ -11,8 +12,13 @@ program console.log(version) }) -program.addCommand(applicationCommand()) program.addCommand(loginCommand()) program.addCommand(logoutCommand()) +program.addCommand(applicationCommand()) +program.addCommand(functionCommand()) program.parse(process.argv) + + + + diff --git a/cli/src/templates/application.ts b/cli/src/templates/application.ts deleted file mode 100644 index f6032835d7..0000000000 --- a/cli/src/templates/application.ts +++ /dev/null @@ -1,16 +0,0 @@ - -export interface ApplicationMetadata { - - name: string; - - appid: string; - - regionName?: string; - - bundleName?: string; - - runtimeName?: string; - - createdAt?: string; - -} \ No newline at end of file diff --git a/cli/src/templates/config.ts b/cli/src/templates/config.ts deleted file mode 100644 index 3d303f0498..0000000000 --- a/cli/src/templates/config.ts +++ /dev/null @@ -1,8 +0,0 @@ - -export interface ConfigMetadata { - - remoteServer: string; - - accessToken: string; - -} \ No newline at end of file diff --git a/cli/src/util/file.ts b/cli/src/util/file.ts new file mode 100644 index 0000000000..558579539c --- /dev/null +++ b/cli/src/util/file.ts @@ -0,0 +1,30 @@ +import * as fs from 'node:fs' +import { parse, stringify } from 'yaml'; + +export function exist(fp: string): boolean { + return fs.existsSync(fp) +} + +export function remove(fp: string) { + fs.unlinkSync(fp) +} + +export function ensureDirectory(dir: string) { + try { + fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK) + } catch (err) { + fs.mkdirSync(dir, { recursive: true }) + } +} + + +export function loadYamlFile(filePath: string): any { + const metadataStr = fs.readFileSync(filePath, 'utf-8') + const yamlData = parse(metadataStr) + return yamlData +} + +export function writeYamlFile(filePath: string, data: any) { + const yamlData = stringify(data) + fs.writeFileSync(filePath, yamlData) +} \ No newline at end of file diff --git a/cli/src/util/format.ts b/cli/src/util/format.ts new file mode 100644 index 0000000000..95ca708bad --- /dev/null +++ b/cli/src/util/format.ts @@ -0,0 +1,18 @@ +import * as dayjs from "dayjs"; + +export function formatDate( + date?: string | number | Date | dayjs.Dayjs | null | undefined, + format = "YYYY-MM-DD HH:mm:ss", +) { + return dayjs(date).format(format); +} + +export function formatSize(size: number) { + const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + let i = 0; + while (size >= 1024) { + size /= 1024; + i++; + } + return size.toFixed(2) + " " + units[i]; +} \ No newline at end of file diff --git a/cli/src/util/print.ts b/cli/src/util/print.ts new file mode 100644 index 0000000000..530e0ffae9 --- /dev/null +++ b/cli/src/util/print.ts @@ -0,0 +1,6 @@ +import * as emoji from 'node-emoji' + +export function getEmoji(value: any) { + return emoji.get(emoji.find(value).key) +} + diff --git a/cli/src/util/request.ts b/cli/src/util/request.ts new file mode 100644 index 0000000000..570da5b31f --- /dev/null +++ b/cli/src/util/request.ts @@ -0,0 +1,87 @@ +// http.ts +import axios, { AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse } from "axios"; +import { existSystemConfig, readSystemConfig, refreshToken } from "../config/system"; + +export const request = axios.create({ + baseURL: "/", + withCredentials: true, + timeout: 30000, +}); + +// request interceptor +request.interceptors.request.use( + async (config: AxiosRequestConfig) => { + + let _headers: AxiosRequestHeaders | any = { + "Content-Type": "application/json", + }; + + // load remote server and token + if (existSystemConfig() && config.url?.startsWith("/v1/")) { + let { remoteServer, token, tokenExpire } = readSystemConfig(); + if (config.url?.indexOf('pat2token') === -1) { + const timestamp = Date.parse(new Date().toString()) / 1000; + if (tokenExpire < timestamp) { + token = await refreshToken() + } + } + config.url = remoteServer + config.url; + _headers.Authorization = 'Bearer ' + token; + } + + config.headers = { + ..._headers, + ...config.headers, + }; + return config; + }, + (error) => { + error.data = {}; + error.data.msg = "The server is abnormal, please contact the administrator!"; + console.log("request error", error); + }, +); + +// response interceptor +request.interceptors.response.use( + (response: AxiosResponse) => { + const { data } = response; + if (data.error == null) { + return data.data; + } + console.error(data.error); + process.exit(1) + }, + (error) => { + if (axios.isCancel(error)) { + console.log("repeated request: " + error.message); + process.exit(1) + } else { + // handle error code + console.log(error) + const { status, data } = error.response; + if (status === 400) { + console.log("Bad request!") + console.log(data.message) + process.exit(1) + } + else if (status === 401) { + console.log("please first login") + process.exit(1) + } + else if (status == 403) { + console.log("Forbidden resource!") + process.exit(1) + } + else if (status === 503) { + console.log("The server is abnormal, please contact the administrator!") + process.exit(1) + } + return Promise.reject(error); + } + }, +); + +export type RequestParams = any; + + diff --git a/cli/src/utils/constant.ts b/cli/src/utils/constant.ts deleted file mode 100644 index 14b38ebe58..0000000000 --- a/cli/src/utils/constant.ts +++ /dev/null @@ -1,14 +0,0 @@ - -export const CONFIG_FILE_NAME = 'config.yaml'; - -export const APPLICATION_METADATA_FILE_NAME = 'laf.yaml'; - -export const FUNCTIONS_DIRECTORY_NAME = 'functions'; - -export const FUNCTIONS_METADATA_FILE_SUFFIX_NAME = '.meta.yaml'; - -export const COLLECTIONS_DIRECTORY_NAME = 'collections'; - -export const COLLECTIONS_METADATA_FILE_SUFFIX_NAME = '.meta.yaml'; - -export const DEFAULT_REMOTE_SERVER = 'http://api.dev.laf.run'; \ No newline at end of file diff --git a/cli/src/utils/path.ts b/cli/src/utils/path.ts deleted file mode 100644 index a220d6bb57..0000000000 --- a/cli/src/utils/path.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as fs from 'node:fs' -import * as path from 'node:path' -import { stringify, parse } from 'yaml' -import { ConfigMetadata } from '../templates/config'; -import { CONFIG_FILE_NAME, DEFAULT_REMOTE_SERVER } from './constant'; - -export function getWorkDir() { - return process.cwd() -} - -export function getConfigDir() { - return path.join(process.env.HOME || process.env.USERPROFILE, '/.laf') -} - -export function ensureDirectory(dir: string) { - try { - fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK) - } catch (err) { - fs.mkdirSync(dir, { recursive: true }) - } -} - -export function ensureSystemConfig() { - const configDir = getConfigDir(); - ensureDirectory(configDir); - const configData: ConfigMetadata = { - remoteServer: DEFAULT_REMOTE_SERVER, - accessToken: null, - } - writeYamlFile(path.join(configDir, CONFIG_FILE_NAME), configData); -} - -export function exist(fp: string): boolean { - return fs.existsSync(fp); -} - -export function loadYamlFile(filePath: string): any { - const metadataStr = fs.readFileSync(filePath, 'utf-8') - const yamlData = parse(metadataStr); - return yamlData; -} - -export function writeYamlFile(filePath: string, data: any) { - const yamlData = stringify(data); - fs.writeFileSync(filePath, yamlData); -} \ No newline at end of file diff --git a/cli/src/utils/request.ts b/cli/src/utils/request.ts deleted file mode 100644 index 9a1aaa45ad..0000000000 --- a/cli/src/utils/request.ts +++ /dev/null @@ -1,82 +0,0 @@ -import axios from 'axios' -import { getRemoteServer } from './token' -import { getAccessToken } from './token' - - -export const request = axios.create({ - // set baseURL - baseURL: getRemoteServer() -}) - -// http request -request.interceptors.request.use( - async (config) => { - const token = await getAccessToken() - if (token) { - config.headers = { - ...config.headers, - Authorization: `Bearer ${token}` - }; - } else { - console.error("please login first: `laf login `") - process.exit(1) - } - return config - }, - (error) => { - error.data = {} - error.data.msg = 'The server is abnormal, please contact the administrator!' - 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 === 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) - } -) - - - -/** - * @param {Object} obj - */ -export function requestData(obj: object) { - return request.request(obj) -} diff --git a/cli/src/utils/token.ts b/cli/src/utils/token.ts deleted file mode 100644 index 14a049b44e..0000000000 --- a/cli/src/utils/token.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ConfigMetadata } from "../templates/config" -import { CONFIG_FILE_NAME, DEFAULT_REMOTE_SERVER } from "./constant"; -import { getConfigDir, loadYamlFile } from "./path"; -import * as path from 'node:path' -import * as fs from 'node:fs' - -export function getConfigData(): ConfigMetadata { - const configPath = path.join(getConfigDir(), CONFIG_FILE_NAME); - if (!fs.existsSync(configPath)) { - return { - remoteServer: DEFAULT_REMOTE_SERVER, - accessToken: null, - } - } - return loadYamlFile(configPath); -} - -export function getAccessToken(): string { - return getConfigData()?.accessToken -} - -export function getRemoteServer(): string { - return getConfigData()?.remoteServer -} \ No newline at end of file diff --git a/cli/template/global.d.ts b/cli/template/global.d.ts new file mode 100644 index 0000000000..16fe0bf961 --- /dev/null +++ b/cli/template/global.d.ts @@ -0,0 +1,95 @@ +declare class FunctionConsole { + private _logs + get logs(): any[] + log(...params: any[]): void +} + +interface File { + fieldname: string + originalname: string + encoding: string + mimetype: string + size: number + destination: string + filename: string + path: string +} + + +/** + * 云函数调用入参 + */ +interface FunctionContext { + /** + * auth 对象解析自 JWT Token Payload + */ + auth?: { + uid?: string + } + + /** + * 上传到云函数的文件 + */ + files?: File[] + + /** + * HTTP headers + */ + headers?: IncomingHttpHeaders + + /** + * HTTP Query 参数 (URL 参数),JSON 对象 + */ + query?: any + + /** + * HTTP Body 参数, JSON 对象 + */ + body?: any + + /** + * Trigger 调用时为触发器所带参数 + */ + params?: any + + /** + * HTTP Request ID + */ + requestId?: string + + /** + * 调用方法:GET | POST | PUT | DELETE | TRIGGER + */ + method?: string + + /** + * Express Response 对象 + */ + response: HttpResponse + + /** + * WebSocket 对象 + */ + socket?: WebSocket +} + +interface IModule { + exports: IExports +} + +interface IExports { + /** + * 主函数,云函数的入口函数 + */ + main: (ctx: FunctionContext) => any +} + +declare const module: IModule +declare const exports: IExports +declare const console: FunctionConsole +declare const global: typeof globalThis + +/** + * 主函数,云函数的入口函数 + */ +declare function main(ctx: FunctionContext): any \ No newline at end of file diff --git a/cli/template/package.json b/cli/template/package.json new file mode 100644 index 0000000000..429a90ce06 --- /dev/null +++ b/cli/template/package.json @@ -0,0 +1,6 @@ +{ + "name": "laf-app", + "description": "the app engine service of lat", + "dependencies": {}, + "devDependencies": {} +} \ No newline at end of file diff --git a/cli/template/response.d.ts b/cli/template/response.d.ts new file mode 100644 index 0000000000..eb68d9c7a1 --- /dev/null +++ b/cli/template/response.d.ts @@ -0,0 +1,522 @@ +declare class OutgoingMessage extends Stream { + readonly req: IncomingMessage + chunkedEncoding: boolean + shouldKeepAlive: boolean + useChunkedEncodingByDefault: boolean + sendDate: boolean + /** + * @deprecated Use 'writableEnded' instead. + */ + // finished: boolean; + /** + * Read-only. 'true' if the headers were sent, otherwise 'false'. + * @since v0.9.3 + */ + readonly headersSent: boolean + /** + * Aliases of 'outgoingMessage.socket' + * @since v0.3.0 + * @deprecated Since v15.12.0 - Use 'socket' instead. + */ + // readonly connection: Socket | null; + /** + * Reference to the underlying socket. Usually, users will not want to access + * this property. + * + * After calling 'outgoingMessage.end()', this property will be nulled. + * @since v0.3.0 + */ + readonly socket: Socket | null + constructor() + /** + * occurs, Same as binding to the 'timeout' event. + * + * Once a socket is associated with the message and is connected,'socket.setTimeout()' will be called with 'msecs' as the first parameter. + * @since v0.9.12 + * @param callback Optional function to be called when a timeout + */ + setTimeout(msecs: number, callback?: () => void): this + /** + * Sets a single header value for the header object. + * @since v0.4.0 + * @param name Header name + * @param value Header value + */ + setHeader(name: string, value: number | string | ReadonlyArray): this + /** + * Gets the value of HTTP header with the given name. If such a name doesn't + * exist in message, it will be 'undefined'. + * @since v0.4.0 + * @param name Name of header + */ + getHeader(name: string): number | string | string[] | undefined + /** + * Returns a shallow copy of the current outgoing headers. Since a shallow + * copy is used, array values may be mutated without additional calls to + * various header-related HTTP module methods. The keys of the returned + * object are the header names and the values are the respective header + * values. All header names are lowercase. + * + * The object returned by the 'outgoingMessage.getHeaders()' method does + * not prototypically inherit from the JavaScript Object. This means that + * typical Object methods such as 'obj.toString()', 'obj.hasOwnProperty()', + * and others are not defined and will not work. + * + * outgoingMessage.setHeader('Foo', 'bar'); + * outgoingMessage.setHeader('Set-Cookie', ['foo=bar', 'bar=baz']); + * + * const headers = outgoingMessage.getHeaders(); + * // headers === { foo: 'bar', 'set-cookie': ['foo=bar', 'bar=baz'] } + * + * @since v8.0.0 + */ + getHeaders(): OutgoingHttpHeaders + /** + * Returns an array of names of headers of the outgoing outgoingMessage. All + * names are lowercase. + * @since v8.0.0 + */ + getHeaderNames(): string[] + /** + * Returns 'true' if the header identified by 'name' is currently set in the + * outgoing headers. The header name is case-insensitive. + * + * const hasContentType = outgoingMessage.hasHeader('content-type'); + * + * @since v8.0.0 + */ + hasHeader(name: string): boolean + /** + * Removes a header that is queued for implicit sending. + * + * outgoingMessage.removeHeader('Content-Encoding'); + * + * @since v0.4.0 + */ + removeHeader(name: string): void + /** + * Adds HTTP trailers (headers but at the end of the message) to the message. + * + * Trailers are **only** be emitted if the message is chunked encoded. If not, + * the trailer will be silently discarded. + * + * HTTP requires the 'Trailer' header to be sent to emit trailers, + * with a list of header fields in its value, e.g. + * + * message.writeHead(200, { 'Content-Type': 'text/plain', + * 'Trailer': 'Content-MD5' }); + * message.write(fileData); + * message.addTrailers({ 'Content-MD5': '7895bf4b8828b55ceaf47747b4bca667' }); + * message.end(); + * + * Attempting to set a header field name or value that contains invalid characters + * will result in a 'TypeError' being thrown. + * @since v0.3.0 + */ + addTrailers(headers: OutgoingHttpHeaders | ReadonlyArray<[string, string]>): void + /** + * Compulsorily flushes the message headers + * + * For efficiency reason, Node.js normally buffers the message headers + * until 'outgoingMessage.end()' is called or the first chunk of message data + * is written. It then tries to pack the headers and data into a single TCP + * packet. + * + * It is usually desired (it saves a TCP round-trip), but not when the first + * data is not sent until possibly much later. 'outgoingMessage.flushHeaders()' bypasses the optimization and kickstarts the request. + * @since v1.6.0 + */ + flushHeaders(): void +} + + +// declare class ServerResponse extends OutgoingMessage{ +declare class ServerResponse { + statusCode: number + statusMessage: string + + constructor(req: IncomingMessage) + + assignSocket(socket: Socket): void + detachSocket(socket: Socket): void + // https://github.com/nodejs/node/blob/master/test/parallel/test-http-write-callbacks.js#L53 + // no args in writeContinue callback + writeContinue(callback?: () => void): void + writeHead(statusCode: number, reasonPhrase?: string, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this + writeHead(statusCode: number, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this + writeProcessing(): void +} + + +interface HttpResponse extends ServerResponse { + /** + * Set status 'code'. + */ + status(code: StatusCode): this + + /** + * Set the response HTTP status code to 'statusCode' and send its string representation as the response body. + * @link http://expressjs.com/4x/api.html#res.sendStatus + * + * Examples: + * + * res.sendStatus(200); // equivalent to res.status(200).send('OK') + * res.sendStatus(403); // equivalent to res.status(403).send('Forbidden') + * res.sendStatus(404); // equivalent to res.status(404).send('Not Found') + * res.sendStatus(500); // equivalent to res.status(500).send('Internal Server Error') + */ + sendStatus(code: StatusCode): this + + /** + * Set Link header field with the given 'links'. + * + * Examples: + * + * res.links({ + * next: 'http://api.example.com/users?page=2', + * last: 'http://api.example.com/users?page=5' + * }); + */ + links(links: any): this + + /** + * Send a response. + * + * Examples: + * + * res.send(new Buffer('wahoo')); + * res.send({ some: 'json' }); + * res.send('

some html

'); + * res.status(404).send('Sorry, cant find that'); + */ + send: Send + + /** + * Send JSON response. + * + * Examples: + * + * res.json(null); + * res.json({ user: 'tj' }); + * res.status(500).json('oh noes!'); + * res.status(404).json('I dont have that'); + */ + json: Send + + /** + * Send JSON response with JSONP callback support. + * + * Examples: + * + * res.jsonp(null); + * res.jsonp({ user: 'tj' }); + * res.status(500).jsonp('oh noes!'); + * res.status(404).jsonp('I dont have that'); + */ + jsonp: Send + + /** + * Transfer the file at the given 'path'. + * + * Automatically sets the _Content-Type_ response header field. + * The callback 'fn(err)' is invoked when the transfer is complete + * or when an error occurs. Be sure to check 'res.headersSent' + * if you wish to attempt responding, as the header and some data + * may have already been transferred. + * + * Options: + * + * - 'maxAge' defaulting to 0 (can be string converted by 'ms') + * - 'root' root directory for relative filenames + * - 'headers' object of headers to serve with file + * - 'dotfiles' serve dotfiles, defaulting to false; can be '"allow"' to send them + * + * Other options are passed along to 'send'. + * + * Examples: + * + * The following example illustrates how 'res.sendFile()' may + * be used as an alternative for the 'static()' middleware for + * dynamic situations. The code backing 'res.sendFile()' is actually + * the same code, so HTTP cache support etc is identical. + * + * app.get('/user/:uid/photos/:file', function(req, res){ + * var uid = req.params.uid + * , file = req.params.file; + * + * req.user.mayViewFilesFrom(uid, function(yes){ + * if (yes) { + * res.sendFile('/uploads/' + uid + '/' + file); + * } else { + * res.send(403, 'Sorry! you cant see that.'); + * } + * }); + * }); + * + * @api public + */ + sendFile(path: string, fn?: Errback): void + sendFile(path: string, options: any, fn?: Errback): void + + /** + * @deprecated Use sendFile instead. + */ + // sendfile(path: string): void; + /** + * @deprecated Use sendFile instead. + */ + // sendfile(path: string, options: any): void; + /** + * @deprecated Use sendFile instead. + */ + // sendfile(path: string, fn: Errback): void; + /** + * @deprecated Use sendFile instead. + */ + // sendfile(path: string, options: any, fn: Errback): void; + + /** + * Transfer the file at the given 'path' as an attachment. + * + * Optionally providing an alternate attachment 'filename', + * and optional callback 'fn(err)'. The callback is invoked + * when the data transfer is complete, or when an error has + * ocurred. Be sure to check 'res.headersSent' if you plan to respond. + * + * The optional options argument passes through to the underlying + * res.sendFile() call, and takes the exact same parameters. + * + * This method uses 'res.sendfile()'. + */ + download(path: string, fn?: Errback): void + download(path: string, filename: string, fn?: Errback): void + download(path: string, filename: string, options: any, fn?: Errback): void + + /** + * Set _Content-Type_ response header with 'type' through 'mime.lookup()' + * when it does not contain "/", or set the Content-Type to 'type' otherwise. + * + * Examples: + * + * res.type('.html'); + * res.type('html'); + * res.type('json'); + * res.type('application/json'); + * res.type('png'); + */ + contentType(type: string): this + + /** + * Set _Content-Type_ response header with 'type' through 'mime.lookup()' + * when it does not contain "/", or set the Content-Type to 'type' otherwise. + * + * Examples: + * + * res.type('.html'); + * res.type('html'); + * res.type('json'); + * res.type('application/json'); + * res.type('png'); + */ + type(type: string): this + + /** + * Respond to the Acceptable formats using an 'obj' + * of mime-type callbacks. + * + * This method uses 'req.accepted', an array of + * acceptable types ordered by their quality values. + * When "Accept" is not present the _first_ callback + * is invoked, otherwise the first match is used. When + * no match is performed the server responds with + * 406 "Not Acceptable". + * + * Content-Type is set for you, however if you choose + * you may alter this within the callback using 'res.type()' + * or 'res.set('Content-Type', ...)'. + * + * res.format({ + * 'text/plain': function(){ + * res.send('hey'); + * }, + * + * 'text/html': function(){ + * res.send('

hey

'); + * }, + * + * 'appliation/json': function(){ + * res.send({ message: 'hey' }); + * } + * }); + * + * In addition to canonicalized MIME types you may + * also use extnames mapped to these types: + * + * res.format({ + * text: function(){ + * res.send('hey'); + * }, + * + * html: function(){ + * res.send('

hey

'); + * }, + * + * json: function(){ + * res.send({ message: 'hey' }); + * } + * }); + * + * By default Express passes an 'Error' + * with a '.status' of 406 to 'next(err)' + * if a match is not made. If you provide + * a '.default' callback it will be invoked + * instead. + */ + format(obj: any): this + + /** + * Set _Content-Disposition_ header to _attachment_ with optional 'filename'. + */ + attachment(filename?: string): this + + /** + * Set header 'field' to 'val', or pass + * an object of header fields. + * + * Examples: + * + * res.set('Foo', ['bar', 'baz']); + * res.set('Accept', 'application/json'); + * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' }); + * + * Aliased as 'res.header()'. + */ + set(field: any): this + set(field: string, value?: string | string[]): this + + header(field: any): this + header(field: string, value?: string | string[]): this + + // Property indicating if HTTP headers has been sent for the response. + headersSent: boolean + + /** Get value for header 'field'. */ + get(field: string): string + + /** Clear cookie 'name'. */ + clearCookie(name: string, options?: any): this + + /** + * Set cookie 'name' to 'val', with the given 'options'. + * + * Options: + * + * - 'maxAge' max-age in milliseconds, converted to 'expires' + * - 'signed' sign the cookie + * - 'path' defaults to "/" + * + * Examples: + * + * // "Remember Me" for 15 minutes + * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); + * + * // save as above + * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) + */ + cookie(name: string, val: string, options: CookieOptions): this + cookie(name: string, val: any, options: CookieOptions): this + cookie(name: string, val: any): this + + /** + * Set the location header to 'url'. + * + * The given 'url' can also be the name of a mapped url, for + * example by default express supports "back" which redirects + * to the _Referrer_ or _Referer_ headers or "/". + * + * Examples: + * + * res.location('/foo/bar').; + * res.location('http://example.com'); + * res.location('../login'); // /blog/post/1 -> /blog/login + * + * Mounting: + * + * When an application is mounted and 'res.location()' + * is given a path that does _not_ lead with "/" it becomes + * relative to the mount-point. For example if the application + * is mounted at "/blog", the following would become "/blog/login". + * + * res.location('login'); + * + * While the leading slash would result in a location of "/login": + * + * res.location('/login'); + */ + location(url: string): this + + /** + * Redirect to the given 'url' with optional response 'status' + * defaulting to 302. + * + * The resulting 'url' is determined by 'res.location()', so + * it will play nicely with mounted apps, relative paths, + * '"back"' etc. + * + * Examples: + * + * res.redirect('/foo/bar'); + * res.redirect('http://example.com'); + * res.redirect(301, 'http://example.com'); + * res.redirect('http://example.com', 301); + * res.redirect('../login'); // /blog/post/1 -> /blog/login + */ + redirect(url: string): void + redirect(status: number, url: string): void + redirect(url: string, status: number): void + + /** + * Render 'view' with the given 'options' and optional callback 'fn'. + * When a callback function is given a response will _not_ be made + * automatically, otherwise a response of _200_ and _text/html_ is given. + * + * Options: + * + * - 'cache' boolean hinting to the engine it should cache + * - 'filename' filename of the view being rendered + */ + render(view: string, options?: object, callback?: (err: Error, html: string) => void): void + render(view: string, callback?: (err: Error, html: string) => void): void + + locals: Locals + + charset: string + + /** + * Adds the field to the Vary response header, if it is not there already. + * Examples: + * + * res.vary('User-Agent').render('docs'); + * + */ + vary(field: string): this + + // app: Application; + + /** + * Appends the specified value to the HTTP response header field. + * If the header is not already set, it creates the header with the specified value. + * The value parameter can be a string or an array. + * + * Note: calling res.set() after res.append() will reset the previously-set header value. + * + * @since 4.11.0 + */ + append(field: string, value?: string[] | string): this + + /** + * After middleware.init executed, Response will contain req property + * See: express/lib/middleware/init.js + */ + // req?: Request; +} \ No newline at end of file diff --git a/cli/template/tsconfig.json b/cli/template/tsconfig.json new file mode 100644 index 0000000000..543c2a96a6 --- /dev/null +++ b/cli/template/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "allowJs": false, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "charset": "utf8", + "declaration": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "importHelpers": false, + "module": "commonjs", + "declarationMap": true, + // "declarationDir": "types", + "noEmitOnError": false, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist", + "pretty": true, + "removeComments": true, + "stripInternal": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "target": "es2017", + "alwaysStrict": true, + "lib": [ + "es2015", + "es6" + ], + }, + "include": [ + "functions/**/*", + "types/**/*", + ], + "exclude": [ + "tests", + ], +} \ No newline at end of file