usf-cli 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/usf-cli.iml" filepath="$PROJECT_DIR$/.idea/usf-cli.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
package/LICENSE.md ADDED
@@ -0,0 +1,6 @@
1
+ usf-cli copyright © 2023 Unity Technologies
2
+
3
+ Your use of the Unity Technologies ("Unity") services known as "usf-cli" are subject to the UOS Terms of Service linked to and copied immediately below.
4
+ [Unity Online Services TOS](https://uos.unity.cn/policy/tos)
5
+
6
+ Your use of the usf-cli constitutes your acceptance of such terms. Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # Develop UOS Func Stateless with usf-cli
2
+
3
+ ## Install usf-cli
4
+ ```bash
5
+ npm install -g git+https://e.coding.net/unitychina/uos/usf-cli.git
6
+ ```
7
+
8
+ ## Create project
9
+ ```bash
10
+ mkdir stateless-function
11
+ cd stateless-function
12
+ ```
13
+
14
+ ## Init project
15
+ ```bash
16
+ usf-cli -i
17
+ # or
18
+ npm init
19
+ ```
20
+
21
+ ## Creat function
22
+ ```bash
23
+ # create default function
24
+ usf-cli -c --f=helloWorld
25
+ # or create function with crud template
26
+ usf-cli -c --f=helloCrud --t=crud_mysql
27
+ ```
28
+
29
+ ## Edit function & event data
30
+ ```bash
31
+ vim ./helloWorld/index.js
32
+ # or open it in IDE
33
+ ```
34
+ ```bash
35
+ vim ./.uos_test/helloWorld_event.json
36
+ # or open it in IDE
37
+ ```
38
+
39
+ ## Install Dependency (If necessary)
40
+ ```bash
41
+ npm install .
42
+ ```
43
+
44
+ ## Run function locally
45
+ ```bash
46
+ # with default event data json(./uos_test/helloWorld_event.json)
47
+ usf-cli -r --f=helloWorld
48
+ # or with specified event data json(e.g. you have created a test.json in your project dir)
49
+ usf-cli -r --f=helloWorld --e=./test.json
50
+ ```
package/index.js ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ import minimist from "minimist";
3
+ import { dynamicRunner } from "./utils/dynamic-runner/index.js";
4
+ import { create, loader } from "./utils/function-construct/index.js";
5
+ import { createRequire } from "module";
6
+ import { spawn } from "child_process";
7
+ import path from "path";
8
+ import {
9
+ recordAuthInfoToFile,
10
+ getAuthInfoFromFile,
11
+ deployFunctionsToPlatform,
12
+ } from "./utils/uploader/index.js";
13
+
14
+ const parseArguments = () => {
15
+ const args = minimist(process.argv, {
16
+ string: [
17
+ "dir",
18
+ "function",
19
+ "f",
20
+ "event",
21
+ "e",
22
+ "template",
23
+ "t",
24
+ "id",
25
+ "secret",
26
+ ],
27
+ boolean: [
28
+ "v",
29
+ "version",
30
+ "h",
31
+ "help",
32
+ "i",
33
+ "init",
34
+ "c",
35
+ "create",
36
+ "run",
37
+ "r",
38
+ "a",
39
+ "auth",
40
+ "d",
41
+ "deploy",
42
+ "installDependency",
43
+ ],
44
+ });
45
+ return {
46
+ init: args.i || args.init,
47
+ version: args.v || args.version,
48
+ help: args.h || args.help || process.argv.length === 2,
49
+ create: args.c || args.c,
50
+ run: args.r || args.run,
51
+ dir: args.dir || process.cwd(),
52
+ functionName: args.function || args.f || undefined,
53
+ event: args.e || args.event,
54
+ template: args.t || args.template,
55
+ auth: args.auth || args.a,
56
+ deploy: args.d || args.deploy,
57
+ id: args.id,
58
+ secret: args.secret,
59
+ install: args.installDependency || false,
60
+ };
61
+ };
62
+
63
+ const showVersion = function () {
64
+ const require = createRequire(import.meta.url);
65
+ console.log(require("./package.json").version);
66
+ };
67
+
68
+ const showHelp = function () {
69
+ console.log(`Usage of usf-cli: usf-cli [option]
70
+
71
+ Usage:
72
+ -v get the version of usf-cli
73
+ -h list the instruction of usf-cli
74
+ -i init function project
75
+ -c --f=<function-name> create function <function-name> & its test event data template
76
+ -c --f=<function-name> --t=<template> create function <function-name> with specified template code (see Supported template code) & its test event data template
77
+ -r --f=<function-name> run function <function-name> with default event data(./uos_test/<function-name>_event.json)
78
+ -r --f=<function-name> --e=<event-dir> run function <function-name> with specified event data
79
+ -a --id=<app-id> --secret=<service-secret> set app id and service secret for this project (used for generating auth token)
80
+ -d upload current stateless functions to UOS platform with UOS auth and deploy automatically
81
+
82
+ Supported template code:
83
+ crud_mongo template function code for uos crud(mongo db version)
84
+ crud_mysql template function code for uos crud(mysql db version)
85
+ crud_postgresql template function code for uos crud(postgresql db version)
86
+ crud_redis template function code for uos crud(redis version)
87
+ `);
88
+ };
89
+
90
+ const initProject = function () {
91
+ spawn("npm", ["init"], {
92
+ shell: true,
93
+ stdio: "inherit",
94
+ });
95
+ };
96
+
97
+ const createFunction = function () {
98
+ if (!this.dir) {
99
+ console.error(
100
+ `please specify the directory of stateless project with "--dir"`
101
+ );
102
+ return;
103
+ }
104
+
105
+ if (!this.functionName) {
106
+ console.error(`please specify the function name with "--f"`);
107
+ return;
108
+ }
109
+ create(this.dir, this.functionName, this.template);
110
+ };
111
+
112
+ const runFunction = async function () {
113
+ if (!this.dir) {
114
+ console.error(
115
+ `please specify the directory of stateless project with "--dir"`
116
+ );
117
+ return;
118
+ }
119
+
120
+ if (!this.functionName) {
121
+ console.error(`please specify the function name with "--f"`);
122
+ return;
123
+ }
124
+ let filePath = this.event
125
+ ? path.resolve(this.event)
126
+ : path.join(this.dir, `.uos_test/${this.functionName}_event.json`);
127
+ const event = (await loader(filePath)) || {};
128
+ dynamicRunner(`${this.dir}/${this.functionName}`, event);
129
+ };
130
+
131
+ const setAuthInfo = async function () {
132
+ if (!this.id) {
133
+ console.error(
134
+ `Please specify app id with "--id".\n\nIf not sure about app id, you can go to uos dev portal(https://uos.unity.cn/apps) to find your apps and get app id.`
135
+ );
136
+ return;
137
+ }
138
+ if (!this.secret) {
139
+ console.error(
140
+ `please specify app service secret with "--secret".\n\nIf not sure about app service secret, you can go to uos dev portal(https://uos.unity.cn/apps) to find your apps and get app service secret in setting tab of app detail page.`
141
+ );
142
+ return;
143
+ }
144
+ recordAuthInfoToFile(this.id, this.secret);
145
+ };
146
+
147
+ const deployFunction = async function () {
148
+ if (!this.dir) {
149
+ console.error(
150
+ `please specify the directory of stateless project with "--dir"`
151
+ );
152
+ return;
153
+ }
154
+ const auth = (await getAuthInfoFromFile()) || {};
155
+ if (!auth.id || !auth.secret) {
156
+ console.error(
157
+ `Please specify app id and secret with "usf-cli -a --id=<app-id> --secret=<service-secret>".\n\nIf not sure about app id and secret, you can go to uos dev portal(https://uos.unity.cn/apps) to find your apps and get app id and app service secret in setting tab of app detail page..`
158
+ );
159
+ return;
160
+ }
161
+ deployFunctionsToPlatform(this.dir, auth, this.install);
162
+ };
163
+
164
+ const main = async function () {
165
+ const args = parseArguments();
166
+
167
+ const commandMap = {
168
+ version: showVersion,
169
+ help: showHelp,
170
+ init: initProject.bind(args),
171
+ create: createFunction.bind(args),
172
+ run: runFunction.bind(args),
173
+ auth: setAuthInfo.bind(args),
174
+ deploy: deployFunction.bind(args),
175
+ };
176
+
177
+ for (let command in commandMap) {
178
+ if (args[command]) {
179
+ commandMap[command]();
180
+ return;
181
+ }
182
+ }
183
+
184
+ showHelp();
185
+ };
186
+ main();
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "usf-cli",
3
+ "version": "1.1.3",
4
+ "description": "usf-cli helps uos stateless function developers with local running, boosting their development efficiency.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "usf-cli": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "test"
11
+ },
12
+ "keywords": [
13
+ "cli"
14
+ ],
15
+ "type": "module",
16
+ "author": "salanx.shi@uos",
17
+ "license": "ISC",
18
+ "dependencies": {
19
+ "axios": "^1.7.2",
20
+ "child_process": "^1.0.2",
21
+ "cos-nodejs-sdk-v5": "^2.14.4",
22
+ "jszip": "^3.10.1",
23
+ "minimist": "^1.2.8",
24
+ "module": "^1.1.0",
25
+ "path": "^0.12.7",
26
+ "read-pkg-up": "^11.0.0",
27
+ "semver": "^7.6.1",
28
+ "url": "^0.11.3"
29
+ }
30
+ }
@@ -0,0 +1,36 @@
1
+ exports.main = async (event, context) => {
2
+ context.database = require('func-stateless-mongodb-sdk-nodejs').database;
3
+ // If you do not specify a databaseId, the sdk will automatically search for and connect to an available database.
4
+ // 如果没有指定 databaseId 的话,sdk 会自动获取可用的数据库进行连接。
5
+ // Please note: if uos is authorized to store your database password in an encrypted form, this SDK will automatically fill in the password, if not authorized, you will need to specify the database password in your code
6
+ // 注意:如果uos被授权以加密形式存储您的数据库密码,本sdk将自动填入密码,如果未被授权,您需要在代码中指定数据库密码。
7
+ // Please note: if there are multiple available mongo databases, you must use the databaseId to specify the desired database you wish to connect to.
8
+ // 注意:如果有多个可用的 mongo 数据库的话,请在代码中指定要用于连接的数据库的 databaseId
9
+ const databaseId = '';
10
+ const databasePassword = '';
11
+ try {
12
+ const database = await context.database(databaseId, databasePassword);
13
+ const client = await database.connection();
14
+ try {
15
+ const db = await client.db('mongo');
16
+ const collection = await db.createCollection('myCollection');
17
+
18
+ const insertResult = await collection.insertOne({
19
+ name: 'Alice',
20
+ age: 25,
21
+ });
22
+ console.log('db insert result:', insertResult.acknowledged);
23
+
24
+ const findResult = await collection.findOne({ name: 'Alice' });
25
+ console.log('db query documents:', findResult);
26
+ } catch (error) {
27
+ console.error(error);
28
+ }
29
+
30
+ //test end pool
31
+ await database.endConnection();
32
+ console.log('closed the connection pool');
33
+ } catch (error) {
34
+ console.error(error);
35
+ }
36
+ };
@@ -0,0 +1,60 @@
1
+ exports.main = async (event, context) => {
2
+ context.database = require('func-stateless-mysql-sdk-nodejs').database;
3
+ // If you do not specify a databaseId, the system will automatically search for and connect to an available database.
4
+ // 如果没有指定 databaseId 的话,sdk 会自动获取可用的数据库进行连接。
5
+ // Please note: if uos is authorized to store your database password in an encrypted form, this SDK will automatically fill in the password, if not authorized, you will need to specify the database password in your code.
6
+ // 注意:如果uos被授权以加密形式存储您的数据库密码,本sdk将自动填入密码,如果未被授权,您需要在代码中指定数据库密码。
7
+ // Please note: if there are multiple available mysql databases, you must use the databaseId to specify the desired database you wish to connect to.
8
+ // 注意:如果有多个可用的 mysql 数据库的话,请在代码中指定要用于连接的数据库的 databaseId
9
+ const databaseId = '';
10
+ const databasePassword = '';
11
+ try {
12
+ const database = await context.database(databaseId, databasePassword);
13
+ const connection = await database.connection();
14
+ try {
15
+ const databaseName = 'test';
16
+ await connection.query(
17
+ `CREATE DATABASE IF NOT EXISTS \`${databaseName}\``
18
+ );
19
+ console.log(`Database ${databaseName} ensured.`);
20
+
21
+ await connection.query(`USE \`${databaseName}\``);
22
+ console.log(`Using database ${databaseName}`);
23
+
24
+ const createTableQuery = `
25
+ CREATE TABLE IF NOT EXISTS users (
26
+ id INT AUTO_INCREMENT PRIMARY KEY,
27
+ name VARCHAR(255) NOT NULL,
28
+ email VARCHAR(255) NOT NULL UNIQUE
29
+ )
30
+ `;
31
+ await connection.query(createTableQuery);
32
+ console.log('table created successfully');
33
+
34
+ const insertQuery = `INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com')`;
35
+ await connection.query(insertQuery);
36
+ console.log('rows inserted successfully');
37
+
38
+ const [rows] = await connection.query('SELECT * FROM users');
39
+ console.log('selected rows:', rows);
40
+
41
+ const deleteTableQuery = `
42
+ DROP TABLE IF EXISTS users
43
+ `;
44
+ await connection.query(deleteTableQuery);
45
+ console.log('table deleted successfully');
46
+ } catch (error) {
47
+ console.error('error while setting up the database:', error);
48
+ } finally {
49
+ // Please note: mysql connection should be released manually if you successfully check it out, regardless of whether there was an error with the queries you ran on the client.
50
+ // 注意:mysql 客户端需要被手动释放,无论查询过程是否发生错误
51
+ await connection.release();
52
+ }
53
+
54
+ //test end pool
55
+ await database.endConnection();
56
+ console.log('closed the connection pool');
57
+ } catch (error) {
58
+ console.error(error);
59
+ }
60
+ };
@@ -0,0 +1,30 @@
1
+ exports.main = async (event, context) => {
2
+ context.database = require('func-stateless-postgresql-sdk-nodejs').database;
3
+ // If you do not specify a databaseId, the system will automatically search for and connect to an available database.
4
+ // 如果没有指定 databaseId 的话,sdk 会自动获取可用的数据库进行连接。
5
+ // Please note: if uos is authorized to store your database password in an encrypted form, this SDK will automatically fill in the password, if not authorized, you will need to specify the database password in your code
6
+ // 注意:如果uos被授权以加密形式存储您的数据库密码,本sdk将自动填入密码,如果未被授权,您需要在代码中指定数据库密码。
7
+ // Please note: if there are multiple available postgresql databases, you must use the databaseId to specify the desired database you wish to connect to.
8
+ // 注意:如果有多个可用的 postgresql 数据库的话,请在代码中指定要用于连接的数据库的 databaseId
9
+ const databaseId = '';
10
+ const databasePassword = '';
11
+ try {
12
+ const database = await context.database(databaseId, databasePassword);
13
+ const connection = await database.connection();
14
+ try {
15
+ const result = await connection.query('SELECT tablename FROM pg_tables');
16
+ console.log('db query result:', result.rows);
17
+ } catch (error) {
18
+ console.error('failed to query data, error:' + error);
19
+ } finally {
20
+ // Please note: psql client should be released manually if you successfully check it out, regardless of whether there was an error with the queries you ran on the client.
21
+ // 注意:postgresql 客户端需要被手动释放,无论查询过程是否发生错误
22
+ await connection.release();
23
+ }
24
+ // test end pool
25
+ await database.endConnection();
26
+ console.log('closed the connection pool');
27
+ } catch (error) {
28
+ console.error(error);
29
+ }
30
+ };
@@ -0,0 +1,32 @@
1
+ exports.main = async (event, context) => {
2
+ context.database = require('func-stateless-redis-sdk-nodejs').database;
3
+ // If you do not specify a databaseId, the system will automatically search for and connect to an available database.
4
+ // 如果没有指定 databaseId 的话,sdk 会自动获取可用的数据库进行连接。
5
+ // Please note: if uos is authorized to store your database password in an encrypted form, this SDK will automatically fill in the password, if not authorized, you will need to specify the database password in your code
6
+ // 注意:如果uos被授权以加密形式存储您的数据库密码,本sdk将自动填入密码,如果未被授权,您需要在代码中指定数据库密码。
7
+ // Please note: if there are multiple available redis, you must use the databaseId to specify the desired database you wish to connect to.
8
+ // 注意:如果有多个可用的 redis 的话,请在代码中指定要用于连接的 databaseId
9
+ const databaseId = '';
10
+ const databasePassword = '';
11
+ try {
12
+ const database = await context.database(databaseId, databasePassword);
13
+ const client = await database.connection();
14
+ try {
15
+ await client.set('someKey', 'someValue');
16
+ const result = await client.get('someKey');
17
+ console.log('db query result:', result);
18
+ } catch (error) {
19
+ console.error('failed to query data, error:' + error);
20
+ } finally {
21
+ // Please note: redis client should be released manually if you successfully check it out, regardless of whether there was an error with the queries you ran on the client.
22
+ // 注意:redis客户端需要被手动释放,无论查询过程是否发生错误
23
+ await client.release();
24
+ }
25
+
26
+ // test end pool
27
+ await database.endConnection();
28
+ console.log('closed the connection pool');
29
+ } catch (error) {
30
+ console.error(error);
31
+ }
32
+ };
@@ -0,0 +1,9 @@
1
+ exports.main = async function (event, context) {
2
+ const { httpMethod,headers, body } = event;
3
+
4
+ console.log('Http method:', httpMethod);
5
+ console.log('Http headers:', JSON.stringify(headers));
6
+ console.log('Request body', JSON.stringify(body));
7
+
8
+ return 'hello world';
9
+ };
@@ -0,0 +1,89 @@
1
+ import path from "path";
2
+ import semver from "semver";
3
+ import { readPackageUp } from "read-pkg-up";
4
+ import { pathToFileURL } from "url";
5
+ import { createRequire } from "module";
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+ function getFunctionModulePath(codeLocation) {
10
+ try {
11
+ return require.resolve(codeLocation);
12
+ } catch (ex) {}
13
+
14
+ try {
15
+ return require.resolve(codeLocation + "/index.js");
16
+ } catch (ex) {}
17
+
18
+ return null;
19
+ }
20
+
21
+ const dynamicImport = new Function("modulePath", "return import(modulePath)");
22
+
23
+ async function isEsModule(modulePath) {
24
+ const ext = path.extname(modulePath);
25
+ if (ext === ".mjs") {
26
+ return true;
27
+ }
28
+ if (ext === ".cjs") {
29
+ return false;
30
+ }
31
+
32
+ const pkg = await readPackageUp({
33
+ cwd: path.dirname(modulePath),
34
+ normalize: false,
35
+ });
36
+ return pkg?.packageJson.type === "module";
37
+ }
38
+
39
+ const dynamicRunner = async function (path, event = {}, entry = "main") {
40
+ const functionModulePath = getFunctionModulePath(path);
41
+ if (functionModulePath === null) {
42
+ console.error(`'${path}' is not a loadable module.`);
43
+ return;
44
+ }
45
+
46
+ let functionModule;
47
+ const esModule = await isEsModule(functionModulePath);
48
+ if (esModule) {
49
+ if (semver.lt(process.version, MIN_NODE_VERSION_ESMODULES)) {
50
+ console.error(
51
+ `Cannot load ES Module on Node.js ${process.version}. ` +
52
+ `Please upgrade to Node.js v${MIN_NODE_VERSION_ESMODULES} and up.`
53
+ );
54
+ return null;
55
+ }
56
+ const fpath = pathToFileURL(functionModulePath);
57
+ functionModule = await dynamicImport(fpath.href);
58
+ } else {
59
+ functionModule = require(functionModulePath);
60
+ }
61
+ const userFunction = entry.split(".").reduce((code, functionTargetPart) => {
62
+ if (typeof code === "undefined") {
63
+ return undefined;
64
+ } else {
65
+ return code[functionTargetPart];
66
+ }
67
+ }, functionModule);
68
+
69
+ if (typeof userFunction === "undefined") {
70
+ console.error(`Function '${path}' not found.`);
71
+ return;
72
+ }
73
+
74
+ if (typeof userFunction !== "function") {
75
+ console.error(
76
+ `'${path}/${entry}' needs to be of type function. Got: ` +
77
+ `${typeof userFunction}`
78
+ );
79
+ return;
80
+ }
81
+
82
+ console.log(`Function '${path}/${entry}' is running:`);
83
+ console.log("\n=================Event:");
84
+ console.log(event);
85
+ const res = await userFunction(event, {});
86
+ console.log(`\n=================Return:\n${JSON.stringify(res)}`);
87
+ };
88
+
89
+ export { dynamicRunner };
@@ -0,0 +1,120 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { spawn } from "child_process";
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const event = {
10
+ body: "",
11
+ headerParameters: {},
12
+ headers: {},
13
+ httpMethod: "GET",
14
+ isBase64Encoded: false,
15
+ pathParameters: {},
16
+ queryString: {},
17
+ queryStringParameters: {},
18
+ };
19
+
20
+ const repository = {
21
+ crud_mongo: "func-stateless-mongodb-sdk-nodejs",
22
+ crud_mysql: "func-stateless-mysql-sdk-nodejs",
23
+ crud_postgresql: "func-stateless-postgresql-sdk-nodejs",
24
+ crud_redis: "func-stateless-redis-sdk-nodejs",
25
+ };
26
+
27
+ const makeFile = async function (dir, folderName, fileName, overwrite=false) {
28
+ const folder = path.join(dir, folderName);
29
+ if (!fs.existsSync(folder)) {
30
+ fs.mkdirSync(folder);
31
+ }
32
+ const file = path.join(folder, fileName);
33
+ if (!overwrite && fs.existsSync(file)) {
34
+ console.log(
35
+ `"./${folderName}/${fileName}" is existed. Running 'vim ./${folderName}/${fileName}' to edit it.\n`
36
+ );
37
+ throw new Error("File is existed");
38
+ }
39
+ return file;
40
+ };
41
+
42
+ const createFile = async function (dir, folderName, fileName, conent) {
43
+ try {
44
+ const file = await makeFile(dir, folderName, fileName);
45
+ fs.writeFile(file, conent, (err) => {
46
+ if (err) {
47
+ console.error(`Fail to init './${folderName}${fileName}':\n`, err);
48
+ return;
49
+ }
50
+ console.log(
51
+ `"./${folderName}${fileName}" has inited. Running 'vim ./${folderName}${fileName}' to edit it.\n`
52
+ );
53
+ });
54
+ } catch (e) {
55
+ console.log(e);
56
+ }
57
+ };
58
+
59
+ const copyFile = async function (dir, folderName, fileName, template) {
60
+ try {
61
+ const file = await makeFile(dir, folderName, fileName);
62
+ const templatePath = path.resolve(
63
+ __dirname,
64
+ `../../templates/${template}.js`
65
+ );
66
+ if (!fs.existsSync(templatePath)) {
67
+ console.log("template is not found");
68
+ return;
69
+ }
70
+ fs.copyFile(templatePath, file, (err) => {
71
+ if (err) {
72
+ console.error(`Fail to init function with template:\n`, err);
73
+ return;
74
+ }
75
+ console.log(
76
+ `"./${folderName}${fileName}" data has inited. Running 'vim ./${folderName}${fileName}' to edit it.\n`
77
+ );
78
+
79
+ // auto install dependency of stateless sdk
80
+ if (repository[template]) {
81
+ spawn("npm", ["install", repository[template]], {
82
+ shell: true,
83
+ stdio: "inherit",
84
+ });
85
+ }
86
+ });
87
+ } catch (e) {
88
+ console.log(e);
89
+ }
90
+ };
91
+
92
+ const create = async function (dir, functionName, template) {
93
+ template = template || "default";
94
+ copyFile(dir, `${functionName}/`, `index.js`, template);
95
+ createFile(
96
+ dir,
97
+ ".uos_test/",
98
+ `${functionName}_event.json`,
99
+ JSON.stringify(event, null, 2)
100
+ );
101
+ };
102
+
103
+ const loader = function (filePath) {
104
+ return new Promise((resolve, reject) => {
105
+ try {
106
+ fs.readFile(filePath, "utf8", (err, data) => {
107
+ if (err) {
108
+ resolve(undefined);
109
+ return;
110
+ }
111
+
112
+ resolve(JSON.parse(data));
113
+ });
114
+ } catch (e) {
115
+ resolve(undefined);
116
+ }
117
+ });
118
+ };
119
+
120
+ export { create, loader, makeFile };
@@ -0,0 +1,359 @@
1
+ import { makeFile, loader } from "../function-construct/index.js";
2
+ import os from "os";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import JSZip from "jszip";
6
+ import axios from "axios";
7
+ import COS from "cos-nodejs-sdk-v5";
8
+
9
+ const igonreList = [
10
+ ".DS_Store",
11
+ ".idea",
12
+ ".vscode",
13
+ ".git",
14
+ "package-lock.json",
15
+ ".uos_test",
16
+ ];
17
+ let hasPackageJson = false;
18
+ let hasNodeModules = false;
19
+
20
+ const recordAuthInfoToFile = async function (id, secret) {
21
+ const tempDir = os.tmpdir();
22
+ const auth = { id: id, secret: secret };
23
+ try {
24
+ const file = await makeFile(tempDir, ".uos", "auth.json", true);
25
+ fs.writeFile(file, JSON.stringify(auth, null, 2), (err) => {
26
+ if (err) {
27
+ console.error(`Fail to record auth info.`);
28
+ return;
29
+ }
30
+ console.log(
31
+ `Auth info is recorded.\n\nIf stateless functions are completed, you can run "usf-cli -u" to upload them to UOS platform with UOS auth and deploy automatically.`
32
+ );
33
+ });
34
+ } catch (e) {
35
+ console.log(e);
36
+ }
37
+ };
38
+
39
+ const getAuthInfoFromFile = async function () {
40
+ const tempDir = os.tmpdir();
41
+ const authPath = path.join(tempDir, ".uos", "auth.json");
42
+ const authInfo = await loader(authPath);
43
+
44
+ return authInfo;
45
+ };
46
+
47
+ const convertToValidVariableName = function (folderName) {
48
+ // 移除非法字符并确保以合法字符开头
49
+ let validName = folderName.replace(/[^a-zA-Z0-9_$]/g, "_");
50
+ if (/^[0-9_$]/.test(validName)) {
51
+ validName = "_" + validName; // 如果以数字、下划线或美元符号开头,则添加下划线作为前缀
52
+ }
53
+ return validName;
54
+ };
55
+
56
+ const formatHandler = function (name, entry) {
57
+ return `const { main: ${name} } = require("./${entry}/index.js");\n\nexports.${name}_handler = ${name};\n\n`;
58
+ };
59
+
60
+ const zipDirectory = async function (entries, zip, dir, level = 0) {
61
+ const files = fs.readdirSync(dir);
62
+
63
+ for (const file of files) {
64
+ const filePath = path.join(dir, file);
65
+ const stats = fs.statSync(filePath);
66
+ if (igonreList.includes(file)) {
67
+ continue;
68
+ }
69
+ if (level === 0 && file === "package.json") {
70
+ hasPackageJson = true;
71
+ }
72
+ if (level === 0 && file === "node_modules") {
73
+ hasNodeModules = true;
74
+ }
75
+ if (stats.isDirectory()) {
76
+ const isEntry = await zipDirectory(
77
+ entries,
78
+ zip.folder(file),
79
+ filePath,
80
+ level + 1
81
+ );
82
+ if (isEntry) {
83
+ entries[file] = convertToValidVariableName(file);
84
+ }
85
+ } else {
86
+ const content = fs.readFileSync(filePath);
87
+ zip.file(file, content);
88
+ }
89
+ }
90
+ return level === 1 && files.includes("index.js");
91
+ };
92
+
93
+ const creatZip = async function (dir) {
94
+ const zip = new JSZip();
95
+ const entries = {};
96
+
97
+ dir = dir || process.cwd();
98
+
99
+ await zipDirectory(entries, zip, dir);
100
+ let entryFile = "";
101
+ for (let entry in entries) {
102
+ entryFile += formatHandler(entries[entry], entry);
103
+ }
104
+ zip.file(path.relative(dir, "index.js"), entryFile);
105
+ const zipContent = await zip.generateAsync({ type: "nodebuffer" });
106
+
107
+ return { entries, zipContent };
108
+ };
109
+
110
+ const getToken = async function (auth) {
111
+ const res = await axios.get(
112
+ "https://func.unity.cn/v1/func-stateless/functions/" +
113
+ auth.id +
114
+ "/get-transfer-token",
115
+ {
116
+ headers: {
117
+ Authorization: "Basic " + btoa(auth.id + ":" + auth.secret),
118
+ },
119
+ }
120
+ );
121
+ return res.data;
122
+ };
123
+
124
+ const uploadZipToCos = function (cosToken, zipContent) {
125
+ const cos = new COS({
126
+ getAuthorization: function (options, callback) {
127
+ callback({
128
+ TmpSecretId: cosToken.tmpSecretId,
129
+ TmpSecretKey: cosToken.tmpSecretKey,
130
+ SecurityToken: cosToken.token,
131
+ StartTime: cosToken.startTime,
132
+ ExpiredTime: Math.ceil(Date.parse(cosToken.expiredTime) / 1000),
133
+ });
134
+ },
135
+ });
136
+ let taskId;
137
+ return new Promise((resolve, reject) => {
138
+ cos.putObject(
139
+ {
140
+ Bucket: cosToken.bucket,
141
+ Region: cosToken.region,
142
+ Key: cosToken.objectName + `/${path.basename(process.cwd())}.zip`,
143
+ Body: zipContent,
144
+ onTaskReady: function (taskId) {
145
+ taskId = taskId;
146
+ },
147
+ },
148
+ function (err, data) {
149
+ if (err) {
150
+ reject(err);
151
+ } else {
152
+ taskId = null;
153
+ setTimeout(() => {
154
+ resolve();
155
+ }, 500);
156
+ }
157
+ }
158
+ );
159
+ });
160
+ };
161
+
162
+ const createScf = async function (auth, entries) {
163
+ let payLoad = {
164
+ scfList: [],
165
+ };
166
+
167
+ for (let entry in entries) {
168
+ payLoad.scfList.push({
169
+ name: entries[entry],
170
+ zipName: path.basename(process.cwd()),
171
+ handler: `index.${entries[entry]}_handler`,
172
+ });
173
+ }
174
+
175
+ await axios.post(
176
+ "https://func.unity.cn/v1/func-stateless/functions/" + auth.id + "/scf",
177
+ payLoad,
178
+ {
179
+ headers: {
180
+ Authorization: "Basic " + btoa(auth.id + ":" + auth.secret),
181
+ },
182
+ }
183
+ );
184
+ };
185
+
186
+ const loadingAnimation = function (loadingLabel) {
187
+ const frames = ["-", "\\", "|", "/"];
188
+ let i = 0;
189
+
190
+ const interval = setInterval(() => {
191
+ process.stdout.write(`\r${frames[i]} ${loadingLabel}...`);
192
+ i = (i + 1) % frames.length;
193
+ }, 100);
194
+
195
+ return interval;
196
+ };
197
+
198
+ const checkFunctionStatus = function (auth) {
199
+ return new Promise((resolve, reject) => {
200
+ const getFunctionInfo = async () => {
201
+ try {
202
+ const res = await axios.get(
203
+ "https://func.unity.cn/v1/func-stateless/functions/" + auth.id,
204
+ {
205
+ headers: {
206
+ Authorization: "Basic " + btoa(auth.id + ":" + auth.secret),
207
+ },
208
+ }
209
+ );
210
+
211
+ if (res?.data?.functionInfo?.status === "createFailed") {
212
+ reject();
213
+ } else if (res?.data?.functionInfo?.status === "creating") {
214
+ setTimeout(getFunctionInfo, 3000);
215
+ } else {
216
+ resolve(res?.data?.functionInfo);
217
+ }
218
+ } catch (e) {
219
+ reject(e);
220
+ }
221
+ };
222
+ getFunctionInfo();
223
+ });
224
+ };
225
+
226
+ const updateInstallDependency = async function (install = false, auth) {
227
+ let payload = {
228
+ functionInfo: {
229
+ options: {
230
+ installDependency: install ? "TRUE" : "FALSE",
231
+ },
232
+ },
233
+ };
234
+
235
+ await axios.put(
236
+ "https://func.unity.cn/v1/func-stateless/functions/" + auth.id,
237
+ payload,
238
+ {
239
+ headers: {
240
+ Authorization: "Basic " + btoa(auth.id + ":" + auth.secret),
241
+ },
242
+ }
243
+ );
244
+ };
245
+
246
+ const stepWrapper = async function (stepHandler, args, loading, done) {
247
+ let id = loadingAnimation(loading);
248
+ try {
249
+ const res = await stepHandler(...args);
250
+ clearInterval(id);
251
+ console.log(`\r>>> Success to ${done}!`);
252
+ return res;
253
+ } catch (e) {
254
+ clearInterval(id);
255
+ console.error(`\r>>> Failed to ${done}: \n`, e.response || e);
256
+ process.exit(1);
257
+ }
258
+ };
259
+
260
+ const listFunctions = async function (version, auth) {
261
+ try {
262
+ const params = {
263
+ version: version,
264
+ };
265
+ const res = await axios.get(
266
+ `https://func.unity.cn/v1/func-stateless/functions/${auth.id}/scf`,
267
+ {
268
+ params: params,
269
+ headers: {
270
+ Authorization: "Basic " + btoa(auth.id + ":" + auth.secret),
271
+ },
272
+ }
273
+ );
274
+ const scfs = res.data.scfs || [];
275
+ console.log(
276
+ `\n====== Successfully deployed ${scfs.length} function(s). Current Online Func version is: ${version} ======\n`
277
+ );
278
+
279
+ for (const scf of scfs) {
280
+ console.log(`Function Name: ${scf.name}\nFunction URL: ${scf.url}\n`);
281
+ }
282
+ console.log(`============ Deployment completed ============\n`);
283
+ } catch (error) {
284
+ console.log(error.response);
285
+ }
286
+ };
287
+
288
+ const deployFunctionsToPlatform = async function (dir, auth, install = false) {
289
+ hasNodeModules = false;
290
+ hasPackageJson = false;
291
+
292
+ console.log(`\n============ Deployment starts ============\n`);
293
+
294
+ const { entries, zipContent } = await stepWrapper(
295
+ creatZip,
296
+ [dir],
297
+ "Packaging current project",
298
+ "package current project"
299
+ );
300
+
301
+ if (!hasNodeModules && !hasPackageJson) {
302
+ console.error("!!! Can't find package.json & node_modules/ in your project.");
303
+ process.exit(1);
304
+ }
305
+
306
+ if (hasNodeModules ^ hasPackageJson) {
307
+ install = hasPackageJson;
308
+ }
309
+
310
+ await stepWrapper(
311
+ updateInstallDependency,
312
+ [install, auth],
313
+ `Changing 'Online Install Dependency' status to ${
314
+ install ? "TRUE" : "FALSE"
315
+ }`,
316
+ `change 'Online Install Dependency' status to ${install ? "TRUE" : "FALSE"}`
317
+ );
318
+
319
+ if (install) {
320
+ igonreList.push("node_modules");
321
+ }
322
+
323
+ // const zipFilePath = path.join(process.cwd(), `/${path.basename(process.cwd())}.zip`);
324
+ // fs.writeFileSync(zipFilePath, zipContent);
325
+
326
+ const cosToken = await stepWrapper(
327
+ getToken,
328
+ [auth],
329
+ "Getting cos token",
330
+ "get cos token"
331
+ );
332
+
333
+ await stepWrapper(
334
+ uploadZipToCos,
335
+ [cosToken, zipContent],
336
+ "Uploading function project to cos",
337
+ "upload function project to cos"
338
+ );
339
+
340
+ await stepWrapper(
341
+ createScf,
342
+ [auth, entries],
343
+ "Updating Func Stateless",
344
+ "update Func Stateless"
345
+ );
346
+
347
+ const functionInfo = await stepWrapper(
348
+ checkFunctionStatus,
349
+ [auth],
350
+ "Deploying functions",
351
+ "deploy functions"
352
+ );
353
+
354
+ if (functionInfo.currentVersion) {
355
+ await listFunctions(functionInfo.currentVersion, auth);
356
+ }
357
+ };
358
+
359
+ export { recordAuthInfoToFile, getAuthInfoFromFile, deployFunctionsToPlatform };