1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const command_1 = require("@oclif/command");
|
4 | const path = require("path");
|
5 | const get_port_1 = require("get-port");
|
6 | const fs = require("fs");
|
7 | const tsNode = require("ts-node");
|
8 | const http = require("http");
|
9 | const knex = require("knex");
|
10 | const jsonc_parser_1 = require("jsonc-parser");
|
11 | const base_command_1 = require("../base-command");
|
12 | const filename = process.env.BEARER_LOCAL_DATABASE || ':memory:';
|
13 | class Invoke extends base_command_1.default {
|
14 | constructor() {
|
15 | super(...arguments);
|
16 | this.ensureJson = (maybeJson) => {
|
17 | try {
|
18 | JSON.parse(maybeJson);
|
19 | }
|
20 | catch (e) {
|
21 | this.error('Invalid JSON provided');
|
22 | }
|
23 | };
|
24 | this.getFileContent = (filePath) => {
|
25 | const location = path.resolve(filePath);
|
26 | if (filePath) {
|
27 | return fs.existsSync(location)
|
28 | ? fs.readFileSync(location, { encoding: 'utf8' })
|
29 | : this.error(`File not found: ${location}`);
|
30 | }
|
31 | return undefined;
|
32 | };
|
33 | this._startServer = async (port) => {
|
34 | return new Promise((resolve, reject) => {
|
35 | const _server = http
|
36 | .createServer((request, response) => {
|
37 | this.debug('Incoming request');
|
38 | let body = '';
|
39 | request.on('data', chunk => {
|
40 | body += chunk;
|
41 | });
|
42 | request.on('end', async () => {
|
43 | request.method;
|
44 | try {
|
45 | this.debug('method: %s body: %s', request.method, body || '{}');
|
46 | response.setHeader('Connection', 'close');
|
47 | switch (request.method) {
|
48 | case 'GET': {
|
49 | const referenceId = request.url.split('/').reverse()[0];
|
50 | const existingData = await this.getData(referenceId);
|
51 | this.debug('lookup id: %s data:%j', referenceId, existingData);
|
52 | const payload = { Item: { referenceId, ReadAllowed: true } };
|
53 | if (existingData) {
|
54 | payload.Item.data = existingData;
|
55 | }
|
56 | this.debug('GET sending %j', payload);
|
57 | response.write(JSON.stringify(payload));
|
58 | break;
|
59 | }
|
60 | case 'POST':
|
61 | case 'PUT': {
|
62 | const { referenceId, data } = JSON.parse(body);
|
63 | await this.putData(referenceId, data);
|
64 | this.debug('persisting data %j', data);
|
65 | const returnedData = { Item: Object.assign({}, data) };
|
66 | response.write(JSON.stringify(returnedData));
|
67 | }
|
68 | }
|
69 | }
|
70 | catch (e) {
|
71 | this.debug('error:', e);
|
72 | }
|
73 | response.end();
|
74 | });
|
75 | })
|
76 | .listen(port, () => {
|
77 | this.debug('started');
|
78 | resolve(_server);
|
79 | });
|
80 | });
|
81 | };
|
82 | this.getData = async (referenceId) => {
|
83 | const db = await this.db();
|
84 | const rows = await db
|
85 | .table('records')
|
86 | .select('data')
|
87 | .where({ referenceId })
|
88 | .limit(1);
|
89 | if (rows[0]) {
|
90 | return JSON.parse(rows[0].data);
|
91 | }
|
92 | return {};
|
93 | };
|
94 | this.putData = async (referenceId, data) => {
|
95 | const db = await this.db();
|
96 | db.table('records')
|
97 | .where({ referenceId })
|
98 | .delete();
|
99 | await db.table('records').insert({ referenceId, data: JSON.stringify(data) });
|
100 | };
|
101 | }
|
102 | async run() {
|
103 | const { args, flags } = this.parse(Invoke);
|
104 | const funcName = args.Function_Name;
|
105 | this.debug('start invoking: %j', funcName);
|
106 | tsNode.register({
|
107 | project: path.join(this.locator.srcFunctionsDir, 'tsconfig.json')
|
108 | });
|
109 | this.debug('ts-node registered');
|
110 | let func;
|
111 | try {
|
112 | func = require(path.join(this.locator.srcFunctionsDir, funcName)).default;
|
113 | }
|
114 | catch (e) {
|
115 | const funcName = args.Function_Name;
|
116 | if (e.code === 'MODULE_NOT_FOUND') {
|
117 | this.error(`"${funcName}" function does not exist. `);
|
118 | }
|
119 | this.error(e.message);
|
120 | }
|
121 | this.debug('required');
|
122 | const body = flags.data || (flags.file && this.getFileContent(flags.file)) || '{}';
|
123 | this.debug('Injected body, %j', body);
|
124 | const context = this.getFunctionContext();
|
125 | this.ensureJson(body);
|
126 | this.debug('calling');
|
127 | const port = await get_port_1.default();
|
128 | const bearerBaseURL = `http://localhost:${port}`;
|
129 | process.env.bearerBaseURL = bearerBaseURL;
|
130 | this.debug('starting');
|
131 | const server = await this._startServer(port);
|
132 | const datum = await func.init()({
|
133 | body,
|
134 | context: Object.assign({}, context, { bearerBaseURL, isBackend: true
|
135 | })
|
136 | });
|
137 | server.close();
|
138 | if (this._db) {
|
139 | this._db.destroy();
|
140 | }
|
141 |
|
142 | console.log(JSON.stringify(datum, null, 2));
|
143 | }
|
144 | getFunctionContext() {
|
145 | const localConfig = this.locator.localConfigPath;
|
146 | const context = {};
|
147 | if (fs.existsSync(localConfig)) {
|
148 | const rawConfig = fs.readFileSync(localConfig, { encoding: 'utf8' });
|
149 | const parsed = jsonc_parser_1.parse(rawConfig);
|
150 | const { setup } = parsed || { setup: null };
|
151 | this.debug('local config: %j', parsed);
|
152 | if (setup && setup.auth) {
|
153 | context.authAccess = setup.auth;
|
154 | }
|
155 | }
|
156 | else {
|
157 | this.debug('no local config found');
|
158 | context.authAccess = {};
|
159 | }
|
160 | return context;
|
161 | }
|
162 | async db() {
|
163 | if (!this._db) {
|
164 | this._db = knex({
|
165 | dialect: 'sqlite3',
|
166 | connection: {
|
167 | filename
|
168 | },
|
169 | pool: { min: 0 },
|
170 | useNullAsDefault: true,
|
171 | debug: /bearer:/.test(process.env.DEBUG || '')
|
172 | });
|
173 | await this._db.schema
|
174 | .hasTable('records')
|
175 | .then(async (exists) => {
|
176 | if (!exists) {
|
177 | await this._db.schema.createTable('records', (table) => {
|
178 | table.index(['referenceId'], 'ref_id_idx');
|
179 | table.string('referenceId');
|
180 | table.text('data');
|
181 | });
|
182 | }
|
183 | })
|
184 | .catch(() => this._db.schema.createTable('records', (table) => {
|
185 | table.index(['referenceId'], 'ref_id_idx');
|
186 | table.string('referenceId');
|
187 | table.text('data');
|
188 | }));
|
189 | }
|
190 | return this._db;
|
191 | }
|
192 | }
|
193 | Invoke.description = 'invoke function locally';
|
194 | Invoke.flags = Object.assign({}, base_command_1.default.flags, { data: command_1.flags.string({ char: 'd' }), file: command_1.flags.string({ char: 'f' }) });
|
195 | Invoke.args = [{ name: 'Function_Name', required: true }];
|
196 | exports.default = Invoke;
|