@faable/faable 1.2.6 → 1.2.8

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.
@@ -1,26 +1,35 @@
1
+ import { __decorate } from 'tslib';
1
2
  import { prepare_client } from './client.js';
2
3
 
3
- const wrap_error = async (prom) => {
4
- try {
5
- const res = await prom;
6
- return res.data;
7
- }
8
- catch (error) {
9
- const e = error;
10
- if (e.isAxiosError) {
11
- const res = e.response;
12
- if (res) {
13
- throw new Error(`API Error ${res.status}: ${res?.data.message}`);
4
+ function handleError(message) {
5
+ return function (target, propertyKey, descriptor) {
6
+ const method = descriptor.value;
7
+ descriptor.value = async function (...args) {
8
+ try {
9
+ return await method.bind(this).apply(target, args);
14
10
  }
15
- else {
16
- throw new Error(`API Error:${e.message}`);
11
+ catch (error) {
12
+ const e = error;
13
+ if (e.isAxiosError) {
14
+ const res = e.response;
15
+ if (res) {
16
+ throw new Error(`FaableApi ${e.config.url} ${res.status}: ${res?.data.message}`);
17
+ }
18
+ else {
19
+ throw new Error(`FaableApi ${e.message}`);
20
+ }
21
+ }
22
+ throw error;
17
23
  }
18
- }
19
- throw error;
20
- }
24
+ };
25
+ };
26
+ }
27
+ const paginate = async (res) => {
28
+ const items = (await res).results;
29
+ return items;
21
30
  };
22
- const paginate = async (data) => {
23
- const items = (await data).results;
31
+ const data = async (res) => {
32
+ const items = (await res).data;
24
33
  return items;
25
34
  };
26
35
  class FaableApi {
@@ -32,14 +41,23 @@ class FaableApi {
32
41
  return new FaableApi(config);
33
42
  }
34
43
  async list() {
35
- return paginate(wrap_error(this.client.get(`/app`)));
44
+ return paginate(data(this.client.get(`/app`)));
36
45
  }
37
46
  async getBySlug(slug) {
38
- return wrap_error(this.client.get(`/app/slug/${slug}`));
47
+ return data(this.client.get(`/app/slug/${slug}`));
39
48
  }
40
49
  async getRegistry(app_id) {
41
- return wrap_error(this.client.get(`/app/${app_id}/registry`));
50
+ return data(this.client.get(`/app/${app_id}/registry`));
42
51
  }
43
52
  }
53
+ __decorate([
54
+ handleError()
55
+ ], FaableApi.prototype, "list", null);
56
+ __decorate([
57
+ handleError()
58
+ ], FaableApi.prototype, "getBySlug", null);
59
+ __decorate([
60
+ handleError()
61
+ ], FaableApi.prototype, "getRegistry", null);
44
62
 
45
63
  export { FaableApi };
@@ -1,11 +1,10 @@
1
1
  import { FaableApi } from './FaableApi.js';
2
- import { authenticateOAuthApp } from './authenticateOAuthApp.js';
2
+ import { apikey } from './strategies/apikey.js';
3
3
 
4
4
  const context = async () => {
5
- const authStrategy = authenticateOAuthApp;
6
5
  const { config } = await import('./userdir_config.js');
7
6
  return {
8
- api: FaableApi.create({ authStrategy, auth: config }),
7
+ api: FaableApi.create({ authStrategy: apikey, auth: config }),
9
8
  };
10
9
  };
11
10
 
@@ -0,0 +1,15 @@
1
+ const apikey = (config) => {
2
+ const { apikey } = config;
3
+ if (!apikey) {
4
+ throw new Error("Missing apikey.");
5
+ }
6
+ return {
7
+ headers: async () => {
8
+ return {
9
+ Authorization: `Basic ${Buffer.from(`${apikey}:`).toString("base64")}`,
10
+ };
11
+ },
12
+ };
13
+ };
14
+
15
+ export { apikey };
@@ -11,10 +11,10 @@ if (fs.existsSync(credentials_path)) {
11
11
  // console.log(creds);
12
12
  config.clientId = creds.clientId;
13
13
  config.clientSecret = creds.clientSecret;
14
+ config.apikey = creds.apikey;
14
15
  }
15
- if (process.env.FAABLE_CLIENT_ID && process.env.FAABLE_CLIENT_SECRET) {
16
- config.clientId = process.env.FAABLE_CLIENT_ID;
17
- config.clientSecret = process.env.FAABLE_CLIENT_SECRET;
18
- }
16
+ config.clientId = process.env.FAABLE_CLIENT_ID || config.clientId;
17
+ config.clientSecret = process.env.FAABLE_CLIENT_SECRET || config.clientSecret;
18
+ config.apikey = process.env.FAABLE_APIKEY || config.apikey;
19
19
 
20
20
  export { config };
@@ -27,13 +27,8 @@ const configure = {
27
27
  const response = await prompts([
28
28
  {
29
29
  type: "text",
30
- name: "clientId",
31
- message: "What is your Faable clientId?",
32
- },
33
- {
34
- type: "text",
35
- name: "clientSecret",
36
- message: "What is your Faable clientSecret?",
30
+ name: "apikey",
31
+ message: "What is your Faable ApiKey?",
37
32
  },
38
33
  ]);
39
34
  await fs.ensureDir(faable_home);
@@ -2,7 +2,8 @@ import fs from 'fs-extra';
2
2
  import path__default from 'path';
3
3
  import { log } from '../../log.js';
4
4
 
5
- const analyze_package = async ({ workdir }) => {
5
+ const analyze_package = async (params) => {
6
+ const workdir = params.workdir;
6
7
  const package_file = path__default.join(path__default.resolve(workdir), "package.json");
7
8
  log.info(`Loading config from package.json...`);
8
9
  const pkg = await fs.readJSON(package_file);
@@ -11,16 +12,13 @@ const analyze_package = async ({ workdir }) => {
11
12
  throw new Error("Missing start script");
12
13
  }
13
14
  // Check if build is required to run
14
- let hasBuild = false;
15
- if (pkg?.scripts?.build) {
16
- log.info(`Build step: ${pkg?.scripts?.build}`);
17
- hasBuild = true;
18
- }
19
- else {
15
+ const build_script = params.build_script || "build";
16
+ let build = pkg?.scripts[build_script];
17
+ if (!build) {
20
18
  log.info(`No build script found`);
21
19
  }
22
20
  return {
23
- hasBuild,
21
+ build_script,
24
22
  };
25
23
  };
26
24
 
@@ -0,0 +1,17 @@
1
+ import { log } from '../../log.js';
2
+ import { cmd } from './cmd.js';
3
+
4
+ const build_project = async (args) => {
5
+ const cwd = args?.cwd || process.cwd();
6
+ const build_script = args?.build_script || "build";
7
+ args.app;
8
+ log.info(`⚡️ Running build script [${build_script}]...`);
9
+ const timeout = 1000 * 60 * 10; // 10 minute timeout
10
+ await cmd("yarn", ["run", build_script], {
11
+ timeout,
12
+ cwd,
13
+ enableOutput: true,
14
+ });
15
+ };
16
+
17
+ export { build_project };
@@ -1,3 +1,4 @@
1
+ import { log } from '../../log.js';
1
2
  import { cmd } from './cmd.js';
2
3
  import fs from 'fs-extra';
3
4
  import Handlebars from 'handlebars';
@@ -22,15 +23,26 @@ Handlebars.registerHelper("escape", function (variable) {
22
23
  // Docker template file
23
24
  const docker_template = Handlebars.compile(dockerfile);
24
25
  const entrypoint_template = Handlebars.compile(entrypoint);
25
- const build_docker = async (props) => {
26
- const { app_name, workdir, template_context } = props;
26
+ const bundle_docker = async (props) => {
27
+ const { app, workdir, template_context } = props;
27
28
  const entrypoint_custom = entrypoint_template(template_context);
28
29
  const dockerfile = docker_template({
29
30
  ...template_context,
30
31
  entry_script: entrypoint_custom,
31
32
  });
32
- // console.log(dockerfile);
33
- await cmd("/bin/bash", ["-c", `docker build -t app ${workdir} -f-<<EOF\n${dockerfile}\nEOF`], { enableOutput: true });
33
+ log.info(`📦 Packaging inside a docker image`);
34
+ const tagname = app.id;
35
+ const timeout = 10 * 60 * 1000; // 10 minute timeout
36
+ const command = [
37
+ "-c",
38
+ `docker build -t ${tagname} ${workdir} -f -<<EOF\n${dockerfile}\nEOF`,
39
+ ];
40
+ console.log(command.join(" "));
41
+ await cmd("/bin/bash", command, { timeout, enableOutput: true });
42
+ log.info(`⚙️ Image ready [tag:${tagname}]`);
43
+ return {
44
+ tagname,
45
+ };
34
46
  };
35
47
 
36
- export { build_docker };
48
+ export { bundle_docker };
@@ -1,10 +1,15 @@
1
1
  import { spawn } from 'promisify-child-process';
2
2
 
3
- const cmd = async (cmd, args, params = { enableOutput: false }) => {
4
- const { enableOutput } = params;
3
+ const cmd = async (cmd, args, params) => {
4
+ // Defaults
5
+ const enableOutput = params?.enableOutput || false;
6
+ const timeout = params?.timeout;
7
+ const cwd = params?.cwd;
5
8
  const child = spawn(cmd, args, {
6
9
  encoding: "utf8",
7
10
  stdio: enableOutput ? "inherit" : "pipe",
11
+ timeout,
12
+ cwd,
8
13
  });
9
14
  const out_data = [];
10
15
  child.stderr?.on("data", (data) => {
@@ -0,0 +1,39 @@
1
+ import { log } from '../../log.js';
2
+ import { bundle_docker } from './bundle_docker.js';
3
+ import { upload_tag } from './upload_tag.js';
4
+ import { check_environment } from './check_environment.js';
5
+ import { analyze_package } from './analyze_package.js';
6
+ import { context } from '../../api/context.js';
7
+ import { build_project } from './build_project.js';
8
+
9
+ const deploy_command = async (args) => {
10
+ const app_slug = args.app_slug;
11
+ const workdir = args.workdir || process.cwd();
12
+ if (!app_slug) {
13
+ throw new Error("Missing app name");
14
+ }
15
+ // Check if we can build docker images
16
+ await check_environment();
17
+ // Get registry data from api.faable.com
18
+ const { api } = await context();
19
+ const app = await api.getBySlug(app_slug);
20
+ log.info(`🚀 Preparing to build ${app.name} [${app.id}]`);
21
+ // Analyze package.json to check if build is needed
22
+ const { build_script } = await analyze_package({ workdir });
23
+ if (build_script) {
24
+ await build_project({ app, build_script });
25
+ }
26
+ // Bundle project inside a docker image
27
+ const { tagname } = await bundle_docker({
28
+ app,
29
+ workdir,
30
+ template_context: {
31
+ from: "node:18.12.0-slim",
32
+ start_script: "start",
33
+ },
34
+ });
35
+ // Upload to Faable registry
36
+ await upload_tag({ app, api, tagname });
37
+ };
38
+
39
+ export { deploy_command };
@@ -1,10 +1,5 @@
1
- import { log } from '../../log.js';
2
- import { build_docker } from './build_docker.js';
3
- import { upload_tag } from './upload_tag.js';
4
1
  import fs from 'fs-extra';
5
- import { check_environment } from './check_environment.js';
6
- import { analyze_package } from './analyze_package.js';
7
- import { context } from '../../api/context.js';
2
+ import { deploy_command } from './deploy_command.js';
8
3
 
9
4
  const deploy = {
10
5
  command: "deploy [app_name]",
@@ -23,39 +18,10 @@ const deploy = {
23
18
  alias: "w",
24
19
  type: "string",
25
20
  description: "Working directory",
26
- default: process.cwd(),
27
21
  })
28
22
  .showHelpOnFail(false);
29
23
  },
30
- handler: async (args) => {
31
- const { app_name, workdir } = args;
32
- if (!app_name) {
33
- throw new Error("Missing app_name");
34
- }
35
- // Check environment is ready
36
- await check_environment();
37
- // Get registry data from api.faable.com
38
- const { api } = await context();
39
- const app = await api.getBySlug(app_name);
40
- log.info(`⚡️ Building app "${app.name}"`);
41
- // Analyze package.json
42
- const { hasBuild } = await analyze_package({ workdir });
43
- // Build docker image
44
- await build_docker({
45
- app_name,
46
- workdir,
47
- template_context: {
48
- from: "node:18.12.0-slim",
49
- build_script: hasBuild && "build",
50
- start_script: "start",
51
- },
52
- });
53
- log.info(`⚙️ Image ready. Uploading...`);
54
- // Upload using api.faable.com data
55
- const registry = await api.getRegistry(app.id);
56
- await upload_tag({ registry });
57
- log.info(`✅ Upload completed.`);
58
- },
24
+ handler: async (args) => deploy_command(args),
59
25
  };
60
26
 
61
27
  export { deploy };
@@ -1,16 +1,20 @@
1
+ import { log } from '../../log.js';
1
2
  import { cmd } from './cmd.js';
2
3
 
3
- const upload_tag = async (props) => {
4
- //faablecloud#${ctx.faable_user}+deployment
5
- const { user, password, hostname, image } = props.registry;
4
+ const upload_tag = async (args) => {
5
+ const { api, app, tagname } = args;
6
+ log.info(`Uploading ${tagname}`);
7
+ const registry = await api.getRegistry(app.id);
6
8
  // Registry login
9
+ const { user, password, hostname, image } = registry;
7
10
  const docker_login_cmd = `echo "${password}" | docker login --username ${user} --password-stdin ${hostname}`;
8
11
  await cmd("/bin/bash", ["-c", docker_login_cmd]);
9
12
  // Tag image for production
10
13
  const image_tag = `${hostname}/${image}`;
11
- await cmd("docker", ["tag", "app", image_tag]);
14
+ await cmd("docker", ["tag", tagname, image_tag]);
12
15
  // Upload the image to faable registry
13
16
  await cmd("docker", ["push", image_tag]);
17
+ log.info(`✅ Upload completed.`);
14
18
  };
15
19
 
16
20
  export { upload_tag };
package/dist/log.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import pino from 'pino';
2
+ import 'pino-pretty';
2
3
 
3
4
  //import * as core from "@actions/core";
4
5
  const log = pino({
package/package.json CHANGED
@@ -6,8 +6,10 @@
6
6
  "fs-extra": "^11.1.0",
7
7
  "handlebars": "^4.7.7",
8
8
  "pino": "^8.11.0",
9
+ "pino-pretty": "^9.4.0",
9
10
  "promisify-child-process": "^4.1.1",
10
11
  "prompts": "^2.4.2",
12
+ "tslib": "^2.4.1",
11
13
  "yargs": "^17.6.2"
12
14
  },
13
15
  "bin": {
@@ -24,5 +26,5 @@
24
26
  "access": "public"
25
27
  },
26
28
  "homepage": "https://github.com/faablecloud/faable#readme",
27
- "version": "1.2.6"
29
+ "version": "1.2.8"
28
30
  }
@@ -7,17 +7,9 @@ WORKDIR /faable/app
7
7
  # Environment variables for runtime
8
8
  ENV PORT=80
9
9
  ENV NODE_ENV=production
10
+ ENV START_SCRIPT=start
10
11
 
11
12
  # Copy Usercode
12
- COPY ./ /faable/app
13
+ COPY . .
13
14
 
14
- RUN echo '{{{escape entry_script}}}' >> entrypoint.sh
15
-
16
- # Build the project if requested
17
- {{#if build_script}}
18
- RUN echo "Running build command: {{build_script}}"
19
- RUN yarn run {{build_script}}
20
- {{/if}}
21
-
22
-
23
- CMD ["/bin/sh", "/faable/app/entrypoint.sh","{{start_script}}"]
15
+ CMD ["/bin/sh", "./entrypoint.sh"]
@@ -5,4 +5,4 @@ NPM_VERSION=$(npm --version)
5
5
  YARN_VERSION=$(yarn --version)
6
6
 
7
7
  echo "Faable Cloud · [node $NODE_VERSION] [npm $NPM_VERSION] [yarn $YARN_VERSION]"
8
- yarn run {{start_script}}
8
+ npm run $START_SCRIPT
@@ -1,35 +0,0 @@
1
- import axios from 'axios';
2
-
3
- const authenticateOAuthApp = (creds) => {
4
- const { clientId, clientSecret } = creds;
5
- if (!clientId || !clientSecret) {
6
- throw new Error("Missing credentials. Run faable configure first.");
7
- }
8
- const client = axios.create({ baseURL: "https://api-auth.app.faable.com" });
9
- let cache_token;
10
- const fetch_token = async () => {
11
- // Send client credentials in POST body. Can also be sent as basic auth header.
12
- const res = await client.post("/oauth/token", {
13
- client_id: clientId,
14
- client_secret: clientSecret,
15
- grant_type: "client_credentials",
16
- });
17
- //console.log(`Fetched Token!`);
18
- // Cache the token
19
- cache_token = res.data;
20
- return cache_token;
21
- };
22
- return {
23
- headers: async () => {
24
- // TODO: Check token is not expired
25
- const { token_type, access_token } = cache_token
26
- ? cache_token
27
- : await fetch_token();
28
- return {
29
- Authorization: `${token_type} ${access_token}`,
30
- };
31
- },
32
- };
33
- };
34
-
35
- export { authenticateOAuthApp };